[
  {
    "path": "LICENSE",
    "content": " Copyright 2013 Red Folder Consultancy Ltd\n    \n Licensed under the Apache License, Version 2.0 (the \"License\");   \n you may not use this file except in compliance with the License.   \n You may obtain a copy of the License at       \n  \n http://www.apache.org/licenses/LICENSE-2.0   \n \n Unless required by applicable law or agreed to in writing, software   \n distributed under the License is distributed on an \"AS IS\" BASIS,   \n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   \n See the License for the specific language governing permissions and   \n limitations under the License."
  },
  {
    "path": "README.md",
    "content": "# Important Notice\nPlease be aware that I'm no longer actively maintaining this plugin.\n\nI believe the problem it solved is now better handled with PWA technologies like Service Worker and Push Notifications.\n\nI would also consider technologies such as Phonegap & Cordova to largely to have been eclipsed by PWAs.\n\nPlease keep the above in mind before actively using this plugin.\n\n# Background Service Plugin Core (bgs-core)\n\n## Quick summary\nThis repository provides the core functionality for the Background Service Plugin for Cordova.  The functionality is not a complete plugin - it is intended to be extended by developers to create their own background service.\n\nAn example background service is provided in the https://github.com/Red-Folder/bgs-sample repository.\n\n## Getting started\nTo understand how to create your own background service it is advised that you read the following articles:\n\n* [Using the MyService Sample] (https://github.com/Red-Folder/bgs-core/wiki/Using-the-MyService-Sample)\n* [Build your own Background Service] (https://github.com/Red-Folder/bgs-core/wiki/Build-your-own-Background-Service)\n* [Build your own plugin] (https://github.com/Red-Folder/bgs-core/wiki/Build-your-own-plugin)\n\nFurther documentation can be found at https://github.com/Red-Folder/bgs-core/wiki\n\n## Questions and Support\nIf you have problems, then please log an issue against the bgs-core repository (https://github.com/Red-Folder/bgs-core/issues).\n\nAs the plugin is updated (for new features or fixes) I will tweet via @FolderRed and blog http://red-folder.blogspot.co.uk/\n\n## Older version\nThis version of the plugin is intended to work with Cordova 3.0.0+\n\nThe older version of the plugin is available at https://github.com/Red-Folder/Cordova-Plugin-BackgroundService.  Please note however that this older repository is unlikely to be maintained going forwards.  It is advised that you move to Cordova 3.x.x as soon as possible.\n\n## Spread the love\n\nIf you find the Background Service Plugin useful then spread the love.\n\nAll the work I do on the Plugin is done in my spare time - time I would otherwise be spending taking my wife out for a nice meal or helping my lad find vinyl records (he's currently very much into The Smiths, Fleetwood Mac and Kate Bush).\n\nThe Plugin is free and will always remain free. I will continue to develop, maintain and distribute the Plugin under the MIT License.\n\nhttps://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=E64TCFQ3NLHZ8\n\n## Licence\nCopyright 2013 Red Folder Consultancy Ltd\n    \nLicensed under the Apache License, Version 2.0 (the \"License\");   \nyou may not use this file except in compliance with the License.   \nYou may obtain a copy of the License at       \n  \nhttp://www.apache.org/licenses/LICENSE-2.0   \n \nUnless required by applicable law or agreed to in writing, software   \ndistributed under the License is distributed on an \"AS IS\" BASIS,   \nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   \nSee the License for the specific language governing permissions and   \nlimitations under the License.\n"
  },
  {
    "path": "aidl/android/BackgroundServiceApi.aidl",
    "content": "package com.red_folder.phonegap.plugin.backgroundservice;   \n\nimport com.red_folder.phonegap.plugin.backgroundservice.BackgroundServiceListener;\n\ninterface BackgroundServiceApi {  \n\tString getLatestResult();     \n\t\n\tvoid addListener(BackgroundServiceListener listener);     \n\t\n\tvoid removeListener(BackgroundServiceListener listener); \n\t\n\tboolean isTimerEnabled();\n\t\n\tvoid enableTimer(int milliseconds);\n\t\n\tvoid disableTimer();\n\t\n\tString getConfiguration();\n\t\n\tvoid setConfiguration(String configuration);\n\t\n\tint getTimerMilliseconds();\n\t\n\tvoid run();\n} \n"
  },
  {
    "path": "aidl/android/BackgroundServiceListener.aidl",
    "content": "package com.red_folder.phonegap.plugin.backgroundservice;   \n\ninterface BackgroundServiceListener {     \n\tvoid handleUpdate(); \n\tString getUniqueID();\n} \n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"com.red_folder.phonegap.plugin.backgroundservice\",\n  \"version\": \"2.0.0\",\n  \"description\": \"Framework code that allows the development and operation of an Android Background Service.\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/Red-Folder/bgs-core.git\"\n  },\n  \"keywords\": [\n    \"background\",\n    \"cordova\"\n  ],\n  \"platforms\": [\n    \"android\"\n  ],\n  \"engines\": [\n    {\n      \"name\": \"cordova\",\n      \"version\": \">=3.0.0\"\n    }\n  ],\n  \"author\": \"Red-Folder\",\n  \"license\": \"Apache 2.0\",\n  \"bugs\": {\n    \"url\": \"https://github.com/Red-Folder/bgs-core/issues\"\n  },\n  \"homepage\": \"https://github.com/Red-Folder/bgs-core#readme\"\n}\n"
  },
  {
    "path": "plugin.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<plugin xmlns=\"http://apache.org/cordova/ns/plugins/1.0\"\n        xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        id=\"com.red_folder.phonegap.plugin.backgroundservice\"\n        version=\"2.0.0\"\n>\n\n    <engines>\n        <engine name=\"cordova\" version=\">=3.0.0\"/>\n    </engines>\n\n    <name>Background Service Plugin - Core logic</name>\n    <description>Framework code that allows the development and operation of an Android Background Service.</description>\n    <license>Apache 2.0</license>\n    <keywords>cordova,background</keywords>\n    \n    <js-module src=\"www/backgroundService.js\" name=\"BackgroundService\">\n    </js-module>\n\n    <platform name=\"android\">\n\n        <config-file target=\"res/xml/config.xml\" parent=\"/*\">\n            <feature name=\"BackgroundServicePlugin\">\n                 <param name=\"android-package\" value=\"com.red_folder.phonegap.plugin.backgroundservice.BackgroundServicePlugin\"/>\n            </feature>\n        </config-file>\n\n        <config-file target=\"AndroidManifest.xml\" parent=\"/manifest\">\n            <uses-permission android:name=\"android.permission.RECEIVE_BOOT_COMPLETED\" />\n        </config-file>\n\n        <config-file target=\"AndroidManifest.xml\" parent=\"/manifest/application\">\n  \n            <receiver android:name=\"com.red_folder.phonegap.plugin.backgroundservice.BootReceiver\">\n                <intent-filter>     \n                    <action android:name=\"android.intent.action.BOOT_COMPLETED\"></action>   \n                </intent-filter> \n            </receiver>\n            \n        </config-file>\n\n         <source-file src=\"src/android/BackgroundService.java\" target-dir=\"src/com/red_folder/phonegap/plugin/backgroundservice\" />\n        <source-file src=\"src/android/BackgroundServicePlugin.java\" target-dir=\"src/com/red_folder/phonegap/plugin/backgroundservice\" />\n        <source-file src=\"src/android/BackgroundServicePluginLogic.java\" target-dir=\"src/com/red_folder/phonegap/plugin/backgroundservice\" />\n        <source-file src=\"src/android/BootReceiver.java\" target-dir=\"src/com/red_folder/phonegap/plugin/backgroundservice\" />\n        <source-file src=\"src/android/PropertyHelper.java\" target-dir=\"src/com/red_folder/phonegap/plugin/backgroundservice\" />\n        <source-file src=\"src/android/ReflectionHelper.java\" target-dir=\"src/com/red_folder/phonegap/plugin/backgroundservice\" />\n        \n        <source-file src=\"aidl/android/BackgroundServiceApi.aidl\" target-dir=\"src/com/red_folder/phonegap/plugin/backgroundservice\" />\n        <source-file src=\"aidl/android/BackgroundServiceListener.aidl\" target-dir=\"src/com/red_folder/phonegap/plugin/backgroundservice\" />\n\n    </platform>\n</plugin>\n"
  },
  {
    "path": "src/android/BackgroundService.java",
    "content": "package com.red_folder.phonegap.plugin.backgroundservice;\n\nimport java.text.SimpleDateFormat;\nimport java.util.ArrayList;\nimport java.util.Date;\nimport java.util.List;\nimport java.util.Timer;\n\nimport java.util.TimerTask;\n\nimport org.json.JSONObject;\n\nimport android.app.Service;\nimport android.content.Intent;\nimport android.content.SharedPreferences;\nimport android.os.IBinder;\nimport android.os.RemoteException;\nimport android.preference.PreferenceManager;\nimport android.util.Log;\n\nimport com.red_folder.phonegap.plugin.backgroundservice.BackgroundServiceApi;\n\npublic abstract class BackgroundService extends Service {\n\t\n\t/*\n\t ************************************************************************************************\n\t * Static values \n\t ************************************************************************************************\n\t */\n\tprivate static final String TAG = BackgroundService.class.getSimpleName();\n\n\t/*\n\t ************************************************************************************************\n\t * Fields \n\t ************************************************************************************************\n\t */\n\tprivate Boolean mServiceInitialised = false;\n\tprivate Timer mTimer;\n\t\n\tprivate final Object mResultLock = new Object();\n\tprivate JSONObject mLatestResult = null;\n\n\tprivate List<BackgroundServiceListener> mListeners = new ArrayList<BackgroundServiceListener>();\n\t\n\tprivate TimerTask mUpdateTask;\n\t\n\tprivate Date mPausedUntil = null;\n\n\tpublic void setPauseDuration(long pauseDuration) {\n\t\tthis.mPausedUntil = new Date(new Date().getTime() + pauseDuration);\n\t\t\n\t\t// Call the onPause event\n\t\tonPause();\n\t}\n\t\n\tpublic Boolean getEnabled() {\n\t\tSharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);  \n\n\t\treturn sharedPrefs.getBoolean(this.getClass().getName() + \".Enabled\", false);\n\t}\n\n\tpublic void setEnabled(Boolean enabled) {\n\t\tSharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);  \n\n\t\tSharedPreferences.Editor editor = sharedPrefs.edit();\n        editor.putBoolean(this.getClass().getName() + \".Enabled\", enabled);\n        editor.commit(); // Very important\n\t}\n\t\n\tpublic int getMilliseconds() {\n\t\tSharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);  \n\n\t\t// Should default to a minute\n\t\treturn sharedPrefs.getInt(this.getClass().getName() + \".Milliseconds\", 60000 );\t\n\t}\n\n\tpublic void setMilliseconds(int milliseconds) {\n\t\tSharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);  \n\n\t\tSharedPreferences.Editor editor = sharedPrefs.edit();\n        editor.putInt(this.getClass().getName() + \".Milliseconds\", milliseconds);\n        editor.commit(); // Very important\n\t}\n\n\tprotected JSONObject getLatestResult() {\n\t\tsynchronized (mResultLock) {\n\t\t\treturn mLatestResult;\n\t\t}\n\t}\n\t\n\tprotected void setLatestResult(JSONObject value) {\n\t\tsynchronized (mResultLock) {\n\t\t\tthis.mLatestResult = value;\n\t\t}\n\t}\n\n\tpublic void restartTimer() {\n        \n        // Stop the timertask and restart for the new interval to take effect\n        if (this.mUpdateTask != null) {\n        \tthis.mUpdateTask.cancel();\n        \tthis.mUpdateTask = null;\n\n\t\t\tthis.mUpdateTask = getTimerTask(); \t\t\t\n\t\t\tthis.mTimer.schedule(this.mUpdateTask, getMilliseconds(), getMilliseconds());\n        }\n\t}\n\t\n\t/*\n\t ************************************************************************************************\n\t * Overriden Methods \n\t ************************************************************************************************\n\t */\n\n\t@Override  \n\tpublic IBinder onBind(Intent intent) {\n\t\tLog.i(TAG, \"onBind called\");\n\t\treturn apiEndpoint;\n\t}     \n\t\n\t@Override\n\tpublic int onStartCommand(Intent intent, int flags, int startId) {\n\t    super.onStartCommand(intent, flags, startId);\n\t    Log.d(TAG, \"onStartCommand run\");\n\n\t    initialiseService();\n\t    return START_STICKY;  \n\t}\n\n\t@Override  \n\tpublic void onDestroy() {     \n\t\tsuper.onDestroy();     \n\t\tLog.i(TAG, \"Service destroying\");\n\t\t\n\t\tcleanupService();\n\t}\n\t\n\t/*\n\t ************************************************************************************************\n\t * Protected methods \n\t ************************************************************************************************\n\t */\n\tprotected void runOnce() {\n\t\t// Runs the doWork once\n\t\t// Sets the last result & updates the listeners\n\t\tdoWorkWrapper();\n\t}\n\n\t/*\n\t ************************************************************************************************\n\t * Private methods \n\t ************************************************************************************************\n\t */\n\tprivate BackgroundServiceApi.Stub apiEndpoint = new BackgroundServiceApi.Stub() {\n\n\t\t/*\n\t\t ************************************************************************************************\n\t\t * Overriden Methods \n\t\t ************************************************************************************************\n\t\t */\n\t\t@Override\n\t\tpublic String getLatestResult() throws RemoteException {\n\t\t\tsynchronized (mResultLock) {\n\t\t\t\tif (mLatestResult == null)\n\t\t\t\t\treturn \"{}\";\n\t\t\t\telse\n\t\t\t\t\treturn mLatestResult.toString();\n\t\t\t}\n\t\t}\n\n\t\t@Override\n\t\tpublic void addListener(BackgroundServiceListener listener)\n\t\t\t\tthrows RemoteException {\n\n\t\t\tsynchronized (mListeners) {\n\t\t\t\tif (mListeners.add(listener))\n\t\t\t\t\tLog.d(TAG, \"Listener added\");\n\t\t\t\telse\n\t\t\t\t\tLog.d(TAG, \"Listener not added\");\n\t\t\t}\n\t\t}\n\n\t\t@Override\n\t\tpublic void removeListener(BackgroundServiceListener listener)\n\t\t\t\tthrows RemoteException {\n\n\t\t\tsynchronized (mListeners) {\n\t\t\t\tif (mListeners.size() > 0) {\n\t\t\t\t\tboolean removed = false;\n\t\t\t\t\tfor (int i = 0; i < mListeners.size() && !removed; i++)\n\t\t\t\t\t{\n\t\t\t\t\t\tif (listener.getUniqueID().equals(mListeners.get(i).getUniqueID())) {\n\t\t\t\t\t\t\tmListeners.remove(i);\n\t\t\t\t\t\t\tremoved = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tif (removed)\n\t\t\t\t\t\tLog.d(TAG, \"Listener removed\");\n\t\t\t\t\telse \n\t\t\t\t\t\tLog.d(TAG, \"Listener not found\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t@Override\n\t\tpublic void enableTimer(int milliseconds) throws RemoteException {\n\t\t\t// First stop it just to be on the safe side\n\t\t\tstopTimerTask();\n\t\t\t\n\t\t\t// Then enable and set the milliseconds\n\t\t\tsetEnabled(true);\n\t\t\tsetMilliseconds(milliseconds);\n\t\t\t\n\t\t\t// Finally setup the TimerTask\n\t\t\tsetupTimerTask();\n\t\t}\n\n\t\t@Override\n\t\tpublic void disableTimer() throws RemoteException {\n\t\t\t// Set to disabled\n\t\t\tsetEnabled(false);\n\t\t\t\n\t\t\t// Stop the timer task\n\t\t\tstopTimerTask();\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean isTimerEnabled() throws RemoteException {\n\t\t\treturn getEnabled();\n\t\t}\n\n\t\t@Override\n\t\tpublic String getConfiguration() throws RemoteException {\n\t\t\tJSONObject array = getConfig();\n\t\t\tif (array == null)\n\t\t\t\treturn \"\";\n\t\t\telse \n\t\t\t\treturn array.toString();\n\t\t}\n\n\t\t@Override\n\t\tpublic void setConfiguration(String configuration) throws RemoteException {\n\t\t\ttry {\n\t\t\t\tJSONObject array = null;\n\t\t\t\tif (configuration.length() > 0) {\n\t\t\t\t\tarray = new JSONObject(configuration);\n\t\t\t\t} else {\n\t\t\t\t\tarray = new JSONObject();\n\t\t\t\t}\t\n\t\t\t\tsetConfig(array);\n\t\t\t} catch (Exception ex) {\n\t\t\t\tthrow new RemoteException();\n\t\t\t}\n\t\t}\n\n\t\t@Override\n\t\tpublic int getTimerMilliseconds() throws RemoteException {\n\t\t\treturn getMilliseconds();\n\t\t}\n\n\t\t@Override\n\t\tpublic void run() throws RemoteException {\n\t\t\trunOnce();\n\t\t}\n\t};\n\n\tprivate void initialiseService() {\n\t\t\n\t\tif (!this.mServiceInitialised) {\n\t\t\tLog.i(TAG, \"Initialising the service\");\n\n\t\t\t// Initialise the LatestResult object\n\t\t\tJSONObject tmp = initialiseLatestResult();\n\n\t\t\tLog.i(TAG, \"Syncing result\");\n\t\t\tthis.setLatestResult(tmp);\n\t\t\n\t\t\tif (getEnabled())\n\t\t\t\tthis.setupTimerTask();\n\t\t\t\n\t\t\tthis.mServiceInitialised = true;\n\t\t}\n\n\t}\n\t\n\tprivate void cleanupService() {\n\t\tLog.i(TAG, \"Running cleanupService\");\n\t\t\n\t\tLog.i(TAG, \"Stopping timer task\");\n\t\tstopTimerTask();\n\n\t\tLog.i(TAG, \"Removing the timer\");\n\t\tif (this.mTimer != null) {\n\t\t\tLog.i(TAG, \"Timer is not null\");\n\t\t\ttry {\n\t\t\t\tthis.mTimer.cancel();     \n\t\t\t\tLog.i(TAG, \"Timer.cancel has been called\");\n\t\t\t\tthis.mTimer = null;\n\t\t\t} catch (Exception ex) {\n\t\t\t\tLog.i(TAG, \"Exception has occurred - \" + ex.getMessage());\n\t\t\t}\n\t\t}\n\n\t}\n\n\tprivate void setupTimerTask () {\n\t\t// Only create a timer if the timer is null\n\t\tif (this.mTimer == null) {\n\t\t\tthis.mTimer = new Timer(this.getClass().getName());\n\t\t}\n\t\t\n\t\t// Only create the updateTask if is null\n\t\tif (this.mUpdateTask == null) {\n\t\t\tthis.mUpdateTask = getTimerTask(); \t\t\t\n\t\t\tint milliseconds = getMilliseconds();\n\t\t\tthis.mTimer.schedule(this.mUpdateTask, 1000L, milliseconds);\n\t\t}\n\n\t\tonTimerEnabled();\n\t}\n\t\n\tprivate void stopTimerTask() {\n\t\t\n\t\tLog.i(TAG, \"stopTimerTask called\");\n\t\tif (this.mUpdateTask != null)\n\t\t{\n\t\t\tLog.i(TAG, \"updateTask is not null\");\n\t\t\tif (this.mUpdateTask.cancel() )\n\t\t\t{\n\t\t\t\tLog.i(TAG, \"updateTask.cancel returned true\");\n\t\t\t} else {\n\t\t\t\tLog.i(TAG, \"updateTask.cancel returned false\");\n\t\t\t}\n\t\t\tthis.mUpdateTask = null;\n\t\t}\n\t\t\n\t\tonTimerDisabled();\n\t}\n\t\n\tprivate TimerTask getTimerTask() {\n\t\treturn new TimerTask() {\n\n\t\t\t@Override    \n\t\t\tpublic void run() {       \n\t\t\t\tLog.i(TAG, \"Timer task starting work\");\n\n\t\t\t\tLog.d(TAG, \"Is the service paused?\");\n\t\t\t\tBoolean paused = false;\n\t\t\t\tif (mPausedUntil != null) {\n\t\t\t\t\tLog.d(TAG, \"Service is paused until \" + (new SimpleDateFormat(\"dd/MM/yyyy hh:mm:ss\")).format(mPausedUntil));\n\t\t\t\t\tDate current = new Date();\n\t\t\t\t\tLog.d(TAG, \"Current is \" + (new SimpleDateFormat(\"dd/MM/yyyy hh:mm:ss\")).format(current));\n\t\t\t\t\tif (mPausedUntil.after(current)) {\n\t\t\t\t\t\tLog.d(TAG, \"Service should be paused\");\n\t\t\t\t\t\tpaused = true;\t\t\t\t\t// Still paused\n\t\t\t\t\t} else {\n\t\t\t\t\t\tLog.d(TAG, \"Service should not be paused\");\n\t\t\t\t\t\tmPausedUntil = null;\t\t\t\t// Paused time has past so we can clear the pause\n\t\t\t\t\t\tonPauseComplete();\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (paused) {\n\t\t\t\t\tLog.d(TAG, \"Service is paused\");\n\t\t\t\t} else {\n\t\t\t\t\tLog.d(TAG, \"Service is not paused\");\n\t\t\t\t\t\n\t\t\t\t\t// Runs the doWork \n\t\t\t\t\t// Sets the last result & updates the listeners\n\t\t\t\t\tdoWorkWrapper();\n\t\t\t\t}\n\n\t\t\t\tLog.i(TAG, \"Timer task completing work\");\n\t\t\t}   \n\t\t};\n\n\t}\n\t\n\t// Seperated out to allow the doWork to be called from timer and adhoc (via run method)\n\tprivate void doWorkWrapper() {\n\t\tJSONObject tmp = null;\n\t\t\n\t\ttry {\n\t\t\ttmp = doWork();\n\t\t} catch (Exception ex) {\n\t\t\tLog.i(TAG, \"Exception occurred during doWork()\", ex);\n\t\t}\n\n\t\tLog.i(TAG, \"Syncing result\");\n\t\tsetLatestResult(tmp);\n\t\t\n\t\t// Now call the listeners\n\t\tLog.i(TAG, \"Sending to all listeners\");\n\t\tfor (int i = 0; i < mListeners.size(); i++)\n\t\t{\n\t\t\ttry {\n\t\t\t\tmListeners.get(i).handleUpdate();\n\t\t\t\tLog.i(TAG, \"Sent listener - \" + i);\n\t\t\t} catch (RemoteException e) {\n\t\t\t\tLog.i(TAG, \"Failed to send to listener - \" + i + \" - \" + e.getMessage());\n\t\t\t}\n\t\t}\n\t\t\n\t}\n\t\n\t/*\n\t ************************************************************************************************\n\t * Methods for subclasses to override \n\t ************************************************************************************************\n\t */\n\tprotected abstract JSONObject initialiseLatestResult(); \n\tprotected abstract JSONObject doWork();\n\tprotected abstract JSONObject getConfig();\n\tprotected abstract void setConfig(JSONObject config);\n\t\n\tprotected void onTimerEnabled() {\n\t}\n\n\tprotected void onTimerDisabled() {\n\t}\n\t\n\tprotected void onPause() {\n\t}\n\t\n\tprotected void onPauseComplete() {\n\t}\n}\n"
  },
  {
    "path": "src/android/BackgroundServicePlugin.java",
    "content": "package com.red_folder.phonegap.plugin.backgroundservice;\n\nimport org.apache.cordova.CallbackContext;\nimport org.apache.cordova.PluginResult;\nimport org.json.JSONArray;\n\nimport android.util.Log;\n\nimport org.apache.cordova.CordovaPlugin;\n\nimport com.red_folder.phonegap.plugin.backgroundservice.BackgroundServicePluginLogic.ExecuteResult;\nimport com.red_folder.phonegap.plugin.backgroundservice.BackgroundServicePluginLogic.ExecuteStatus;\n\npublic class BackgroundServicePlugin extends CordovaPlugin implements BackgroundServicePluginLogic.IUpdateListener {\n\n\t/*\n\t ************************************************************************************************\n\t * Static values \n\t ************************************************************************************************\n\t */\n\tprivate static final String TAG = BackgroundServicePlugin.class.getSimpleName();\n\n\t/*\n\t ************************************************************************************************\n\t * Fields \n\t ************************************************************************************************\n\t */\n\t// Part fix for https://github.com/Red-Folder/Cordova-Plugin-BackgroundService/issues/19\n\t//private final BackgroundServicePluginLogic mLogic = new BackgroundServicePluginLogic();\n\tprivate BackgroundServicePluginLogic mLogic = null;\n\n\t/*\n\t ************************************************************************************************\n\t * Overriden Methods \n\t ************************************************************************************************\n\t */\n\t// Part fix for https://github.com/Red-Folder/Cordova-Plugin-BackgroundService/issues/19\n\t//public boolean execute(String action, JSONArray data, CallbackContext callback) {\n\t@Override\n\tpublic boolean execute(final String action, final JSONArray data, final CallbackContext callback) {\n\t\n\t\n\t\tboolean result = false;\n\t\t\n\t\t\n\t\tif (this.mLogic == null)\n\t\t\tthis.mLogic = new BackgroundServicePluginLogic(this.cordova.getActivity());\n\t\t\n\t\t// Part fix for https://github.com/Red-Folder/Cordova-Plugin-BackgroundService/issues/19\n\t\t//if (this.mLogic.isInitialized())\n\t\t//\tthis.mLogic.initialize(this.cordova.getActivity());\n\t\t\n\t\ttry {\n\t\t\t\n\t\t\tif (this.mLogic.isActionValid(action)) {\n\t\t\t\t\n\t\t\t\t//Part fix for https://github.com/Red-Folder/Cordova-Plugin-BackgroundService/issues/19\n\t\t\t\t//final String finalAction = action;\n\t\t\t\t//final JSONArray finalData = data;\n\t\t\t\t//final CallbackContext finalCallback = callback;\n\n\t\t\t\tfinal BackgroundServicePluginLogic.IUpdateListener listener = this;\n\t\t\t\tfinal Object[] listenerExtras = new Object[] { callback };\n\t\t\t\n\t\t\t\tcordova.getThreadPool().execute(new Runnable() {\n\t\t\t\t\t@Override\n\t\t\t\t\tpublic void run() {\n\t\t\t\t\t\tExecuteResult logicResult = mLogic.execute(action, data, listener, listenerExtras);\n\n\t\t\t\t\t\tLog.d(TAG, \"logicResult = \" +  logicResult.toString());\n\t\t\t\t\t\t\n\t\t\t\t\t\tPluginResult pluginResult = transformResult(logicResult);\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\tLog.d(TAG, \"pluginResult = \" +  pluginResult.toString());\n\t\t\t\t\t\tLog.d(TAG, \"pluginResult.getMessage() = \" +  pluginResult.getMessage());\n\t\t\t\t\t\tif (pluginResult.getKeepCallback())\n\t\t\t\t\t\t\tLog.d(TAG, \"Keep Callback\");\n\t\t\t\t\t\telse\n\t\t\t\t\t\t\tLog.d(TAG, \"Dont keep Callback\");\n\t\t\t\t\t\t\n\t\t\t\t\t\tcallback.sendPluginResult(pluginResult);\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\tresult = true;\n\t\t\t} else {\n\t\t\t\tresult = false;\n\t\t\t}\n\t\t\t\n\t\t} catch (Exception ex) {\n\t\t\tLog.d(TAG, \"Exception - \" + ex.getMessage());\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t@Override\n\tpublic void onDestroy() {\n\t\tsuper.onDestroy();\n\n\t\tif (this.mLogic != null) {\n\t\t\tthis.mLogic.onDestroy();\n\t\t\tthis.mLogic = null;\n\t\t}\n\t}\n\n\t/*\n\t ************************************************************************************************\n\t * Public Methods \n\t ************************************************************************************************\n\t */\n\tpublic void handleUpdate(ExecuteResult logicResult, Object[] listenerExtras) {\n\t\tLog.d(TAG, \"Starting handleUpdate\");\n\t\tsendUpdateToListener(logicResult, listenerExtras);\n\t\tLog.d(TAG, \"Finished handleUpdate\");\n\t}\n\t\n\tpublic void closeListener(ExecuteResult logicResult, Object[] listenerExtras) {\n\t\tLog.d(TAG, \"Starting closeListener\");\n\t\tsendUpdateToListener(logicResult, listenerExtras);\n\t\tLog.d(TAG, \"Finished closeListener\");\n\t}\n\n\t/*\n\t ************************************************************************************************\n\t * Private Methods \n\t ************************************************************************************************\n\t */\n\tprivate void sendUpdateToListener(ExecuteResult logicResult, Object[] listenerExtras) {\n\t\ttry {\n\t\t\tif (listenerExtras != null && listenerExtras.length > 0) {\n\t\t\t\tLog.d(TAG, \"Sending update\");\n\t\t\t\tCallbackContext callback = (CallbackContext)listenerExtras[0];\n\t\t\n\t\t\t\tcallback.sendPluginResult(transformResult(logicResult));\n\t\t\t\tLog.d(TAG, \"Sent update\");\n\t\t\t}\n\t\t} catch (Exception ex) {\n\t\t\tLog.d(TAG, \"Sending update failed\", ex);\n\t\t}\n\t}\n\t\n\tprivate PluginResult transformResult(ExecuteResult logicResult) {\n\t\tPluginResult pluginResult = null;\n\t\t\n\t\tLog.d(TAG, \"Start of transformResult\");\n\t\tif (logicResult.getStatus() == ExecuteStatus.OK) {\n\t\t\tLog.d(TAG, \"Status is OK\");\n\t\t\t\n\t\t\tif (logicResult.getData() == null) {\n\t\t\t\tLog.d(TAG, \"We dont have data\");\n\t\t\t\tpluginResult = new PluginResult(PluginResult.Status.OK);\n\t\t\t} else {\n\t\t\t\tLog.d(TAG, \"We have data\");\n\t\t\t\tpluginResult = new PluginResult(PluginResult.Status.OK, logicResult.getData());\n\t\t\t}\n\t\t}\n\n\t\tif (logicResult.getStatus() == ExecuteStatus.ERROR) {\n\t\t\tLog.d(TAG, \"Status is ERROR\");\n\t\t\t\n\t\t\tif (logicResult.getData() == null) {\n\t\t\t\tLog.d(TAG, \"We dont have data\");\n\t\t\t\tpluginResult = new PluginResult(PluginResult.Status.ERROR, \"Unknown error\");\n\t\t\t} else {\n\t\t\t\tLog.d(TAG, \"We have data\");\n\t\t\t\tpluginResult = new PluginResult(PluginResult.Status.ERROR, logicResult.getData());\n\t\t\t}\n\t\t}\n\t\t\n\t\tif (logicResult.getStatus() == ExecuteStatus.INVALID_ACTION) {\n\t\t\tLog.d(TAG, \"Status is INVALID_ACTION\");\n\t\t\t\n\t\t\tif (logicResult.getData() == null) {\n\t\t\t\tLog.d(TAG, \"We have data\");\n\t\t\t\tpluginResult = new PluginResult(PluginResult.Status.INVALID_ACTION, \"Unknown error\");\n\t\t\t} else {\n\t\t\t\tLog.d(TAG, \"We dont have data\");\n\t\t\t\tpluginResult = new PluginResult(PluginResult.Status.INVALID_ACTION, logicResult.getData());\n\t\t\t}\n\t\t}\n\t\t\n\t\tif (!logicResult.isFinished()) {\n\t\t\tLog.d(TAG, \"Keep Callback set to true\");\n\t\t\tpluginResult.setKeepCallback(true);\n\t\t}\n\t\t\n\t\tLog.d(TAG, \"End of transformResult\");\n\t\treturn pluginResult;\n\t}\n}\n"
  },
  {
    "path": "src/android/BackgroundServicePluginLogic.java",
    "content": "package com.red_folder.phonegap.plugin.backgroundservice;\n\nimport java.util.Enumeration;\nimport java.util.Hashtable;\n\nimport org.json.JSONArray;\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\nimport android.app.ActivityManager;\nimport android.app.ActivityManager.RunningServiceInfo;\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.IBinder;\nimport android.os.RemoteException;\nimport android.util.Log;\nimport android.content.ServiceConnection;\n\npublic class BackgroundServicePluginLogic {\n\n\t/*\n\t ************************************************************************************************\n\t * Static values \n\t ************************************************************************************************\n\t */\n\tpublic static final String TAG = BackgroundServicePluginLogic.class.getSimpleName();\n\t\n\t/*\n\t ************************************************************************************************\n\t * Keys \n\t ************************************************************************************************\n\t */\n\tpublic static final String ACTION_START_SERVICE = \"startService\";\n\tpublic static final String ACTION_STOP_SERVICE = \"stopService\";\n\t\n\tpublic static final String ACTION_ENABLE_TIMER = \"enableTimer\";\n\tpublic static final String ACTION_DISABLE_TIMER = \"disableTimer\";\n\n\tpublic static final String ACTION_SET_CONFIGURATION = \"setConfiguration\";\n\t\n\tpublic static final String ACTION_REGISTER_FOR_BOOTSTART = \"registerForBootStart\";\n\tpublic static final String ACTION_DEREGISTER_FOR_BOOTSTART = \"deregisterForBootStart\";\n\t\n\tpublic static final String ACTION_GET_STATUS = \"getStatus\";\n\n\tpublic static final String ACTION_RUN_ONCE = \"runOnce\";\n\n\tpublic static final String ACTION_REGISTER_FOR_UPDATES = \"registerForUpdates\";\n\tpublic static final String ACTION_DEREGISTER_FOR_UPDATES = \"deregisterForUpdates\";\n\t\n\t\n\tpublic static final int ERROR_NONE_CODE = 0;\n\tpublic static final String ERROR_NONE_MSG = \"\";\n\t\n\tpublic static final int ERROR_PLUGIN_ACTION_NOT_SUPPORTED_CODE = -1;\n\tpublic static final String ERROR_PLUGIN_ACTION_NOT_SUPPORTED_MSG = \"Passed action not supported by Plugin\";\n\t\n\tpublic static final int ERROR_INIT_NOT_YET_CALLED_CODE = -2;\n\tpublic static final String ERROR_INIT_NOT_YET_CALLED_MSG = \"Please call init prior any other action\";\n\t\n\tpublic static final int ERROR_SERVICE_NOT_RUNNING_CODE = -3;\n\tpublic static final String ERROR_SERVICE_NOT_RUNNING_MSG = \"Sevice not currently running\";\n\t\n\tpublic static final int ERROR_UNABLE_TO_BIND_TO_BACKGROUND_SERVICE_CODE = -4;\n\tpublic static final String ERROR_UNABLE_TO_BIND_TO_BACKGROUND_SERVICE_MSG =\"Plugin unable to bind to background service\";\n\t\n\tpublic static final int ERROR_UNABLE_TO_RETRIEVE_LAST_RESULT_CODE = -5;\n\tpublic static final String ERROR_UNABLE_TO_RETRIEVE_LAST_RESULT_MSG = \"Unable to retrieve latest result (reason unknown)\";\n\t\n\tpublic static final int ERROR_LISTENER_ALREADY_REGISTERED_CODE = -6;\n\tpublic static final String ERROR_LISTENER_ALREADY_REGISTERED_MSG = \"Listener already registered\";\n\t\n\tpublic static final int ERROR_LISTENER_NOT_REGISTERED_CODE = -7;\n\tpublic static final String ERROR_LISTENER_NOT_REGISTERED_MSG = \"Listener not registered\";\n\n\tpublic static final int ERROR_UNABLE_TO_CLOSED_LISTENER_CODE = -8;\n\tpublic static final String ERROR_UNABLE_TO_CLOSED_LISTENER_MSG = \"Unable to close listener\";\n\t\n\tpublic static final int ERROR_ACTION_NOT_SUPPORTED__IN_PLUGIN_VERSION_CODE = -9;\n\tpublic static final String ERROR_ACTION_NOT_SUPPORTED__IN_PLUGIN_VERSION_MSG = \"Action is not supported in this version of the plugin\";\n\n\tpublic static final int ERROR_EXCEPTION_CODE = -99;\n\t\n\t/*\n\t ************************************************************************************************\n\t * Fields \n\t ************************************************************************************************\n\t */\n\tprivate Context mContext;\n\tprivate Hashtable<String, ServiceDetails> mServices = new Hashtable<String, ServiceDetails>();\n\n\t/*\n\t ************************************************************************************************\n\t * Constructors \n\t ************************************************************************************************\n\t */\n\t// Part fix for https://github.com/Red-Folder/Cordova-Plugin-BackgroundService/issues/19\n\t//public BackgroundServicePluginLogic() {\n\t//}\n\n\tpublic BackgroundServicePluginLogic(Context pContext) {\n\t\tthis.mContext = pContext;\n\t}\n\n\t/*\n\t ************************************************************************************************\n\t * Public Methods \n\t ************************************************************************************************\n\t */\n\t\n\t// Part fix for https://github.com/Red-Folder/Cordova-Plugin-BackgroundService/issues/19\n\t//public void initialize(Context pContext) {\n\t//\tthis.mContext = pContext;\n\t//}\n\t\n\t//public boolean isInitialized() {\n\t//\tif (this.mContext == null)\n\t//\t\treturn false;\n\t//\telse\n\t//\t\treturn true;\n\t//}\n\t\n\tpublic boolean isActionValid(String action) {\n\t\tboolean result = false;\n\n\t\tif(ACTION_START_SERVICE.equals(action)) result = true;\n\t\tif(ACTION_STOP_SERVICE.equals(action)) result = true;\n\t\t\n\t\tif(ACTION_ENABLE_TIMER.equals(action)) result = true;\n\t\tif(ACTION_DISABLE_TIMER.equals(action)) result = true;\n\n\t\tif(ACTION_SET_CONFIGURATION.equals(action)) result = true;\n\t\t\n\t\tif(ACTION_REGISTER_FOR_BOOTSTART.equals(action)) result = true;\n\t\tif(ACTION_DEREGISTER_FOR_BOOTSTART.equals(action)) result = true;\n\t\t\n\t\tif(ACTION_GET_STATUS.equals(action)) result = true;\n\n\t\tif(ACTION_RUN_ONCE.equals(action)) result = true;\n\t\t\n\t\tif(ACTION_REGISTER_FOR_UPDATES.equals(action)) result = true;\n\t\tif(ACTION_DEREGISTER_FOR_UPDATES.equals(action)) result = true;\n\n\t\treturn result;\n\t}\n\n\tpublic ExecuteResult execute(String action, JSONArray data) {\n\t\treturn execute(action, data, null, null);\n\t}\n\n\tpublic ExecuteResult execute(String action, JSONArray data, IUpdateListener listener, Object[] listenerExtras) {\n\t\tExecuteResult result = null;\n\t\t\n\t\tLog.d(TAG, \"Start of Execute\");\n\t\ttry {\n\t\t\tLog.d(TAG, \"Withing try block\");\n\t\t\tif ((data != null) &&\n\t\t\t\t\t(!data.isNull(0)) &&\n\t\t\t\t\t(data.get(0) instanceof String) &&\n\t\t\t\t\t(data.getString(0).length() > 0)) {\n\n\t\t\t\tString serviceName = data.getString(0);\n\t\t\t\t\n\t\t\t\tLog.d(TAG, \"Finding servicename \" + serviceName);\n\t\t\t\t\n\t\t\t\tServiceDetails service = null;\n\n\t\t\t\tLog.d(TAG, \"Services contain \" + this.mServices.size() + \" records\");\n\t\t\t\t\n\t\t\t\tif (this.mServices.containsKey(serviceName)) {\n\t\t\t\t\tLog.d(TAG, \"Found existing Service Details\");\n\t\t\t\t\tservice = this.mServices.get(serviceName);\n\t\t\t\t} else {\n\t\t\t\t\tLog.d(TAG, \"Creating new Service Details\");\n\t\t\t\t\tservice = new ServiceDetails(this.mContext, serviceName);\n\t\t\t\t\tthis.mServices.put(serviceName, service);\n\t\t\t\t}\n\n\t\t\t\tLog.d(TAG, \"Action = \" + action);\n\n\n\t\t\t\tif (!service.isInitialised())\n\t\t\t\t\tservice.initialise();\n\n\t\t\t\tif (ACTION_GET_STATUS.equals(action)) result = service.getStatus();\n\t\t\t\t\n\t\t\t\tif (ACTION_START_SERVICE.equals(action)) result = service.startService();\n\n\t\t\t\tif (ACTION_REGISTER_FOR_BOOTSTART.equals(action)) result = service.registerForBootStart();\n\t\t\t\tif (ACTION_DEREGISTER_FOR_BOOTSTART.equals(action)) result = service.deregisterForBootStart();\n\n\t\t\t\tif (ACTION_REGISTER_FOR_UPDATES.equals(action)) result = service.registerForUpdates(listener, listenerExtras);\n\t\t\t\tif (ACTION_DEREGISTER_FOR_UPDATES.equals(action)) result = service.deregisterForUpdates();\n\n\t\t\t\tif (result == null) {\n\t\t\t\t\tLog.d(TAG, \"Check if the service is running?\");\n\n\t\t\t\t\tif (service != null && service.isServiceRunning()) {\n\t\t\t\t\t\tLog.d(TAG, \"Service is running?\");\n\t\t\t\t\t\t\n\t\t\t\t\t\tif (ACTION_STOP_SERVICE.equals(action)) result = service.stopService();\n\n\t\t\t\t\t\tif (ACTION_ENABLE_TIMER.equals(action)) result = service.enableTimer(data);\n\t\t\t\t\t\tif (ACTION_DISABLE_TIMER.equals(action)) result = service.disableTimer();\n\n\t\t\t\t\t\tif (ACTION_SET_CONFIGURATION.equals(action)) result = service.setConfiguration(data);\n\n\t\t\t\t\t\tif (ACTION_RUN_ONCE.equals(action)) result = service.runOnce();\n\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresult = new ExecuteResult(ExecuteStatus.INVALID_ACTION);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (result == null)\n\t\t\t\t\tresult = new ExecuteResult(ExecuteStatus.INVALID_ACTION);\n\t\t\t} else {\n\t\t\t\tresult = new ExecuteResult(ExecuteStatus.ERROR);\n\t\t\t\tLog.d(TAG, \"ERROR - no servicename\");\n\t\t\t}\n\t\t} catch (Exception ex) {\n\t\t\tresult = new ExecuteResult(ExecuteStatus.ERROR);\n\t\t\tLog.d(TAG, \"Exception - \" + ex.getMessage());\n\t\t}\n\n\t\treturn result;\n\t}\n\n\tpublic void onDestroy() {\n\t\t\n\t\tLog.d(TAG, \"On Destroy Start\");\n\t\ttry {\n\t\t\tLog.d(TAG, \"Checking for services\");\n\t\t\tif (this.mServices != null && \n\t\t\t\tthis.mServices.size() > 0 ) {\n\n\t\t\t\tLog.d(TAG, \"Found services\");\n\n\t\t\t\tEnumeration<String> keys = this.mServices.keys();\n\t\t\t\t\n\t\t\t\twhile( keys.hasMoreElements() ) {\n\t\t\t\t\tString key = keys.nextElement();  \n\t\t\t\t\tServiceDetails service = this.mServices.get(key);\n\t\t\t\t\tLog.d(TAG, \"Calling service.close()\");\n\t\t\t\t\tservice.close();\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (Throwable t) {\n\t\t\t// catch any issues, typical for destroy routines\n\t\t\t// even if we failed to destroy something, we need to continue destroying\n\t\t\tLog.d(TAG, \"Error has occurred while trying to close services\", t);\n\t\t}\n\t\t\n\t\t\n\t\tthis.mServices = null;\n\t\tLog.d(TAG, \"On Destroy Finish\");\n\t\t\n\t}\n\n\n\t/*\n\t ************************************************************************************************\n\t * Internal Class \n\t ************************************************************************************************\n\t */\n\tprotected class ServiceDetails {\n\t\t/*\n\t\t ************************************************************************************************\n\t\t * Static values \n\t\t ************************************************************************************************\n\t\t */\n\t\tpublic final String LOCALTAG = BackgroundServicePluginLogic.ServiceDetails.class.getSimpleName();\n\t\t\n\t\t/*\n\t\t ************************************************************************************************\n\t\t * Fields \n\t\t ************************************************************************************************\n\t\t */\n\t\tprivate String mServiceName = \"\";\n\t\tprivate Context mContext;\n\t\t\n\t\tprivate BackgroundServiceApi mApi;\n\n\t\tprivate String mUniqueID = java.util.UUID.randomUUID().toString();\n\t\t\n\t\tprivate boolean mInitialised = false;\n\t\t\n\t\tprivate Intent mService = null;\n\t\t\n\t\tprivate Object mServiceConnectedLock = new Object();\n\t\tprivate Boolean mServiceConnected = null;\n\n\t\tprivate IUpdateListener mListener = null;\n\t\tprivate Object[] mListenerExtras = null;\n\t\t\t\t\n\t\t/*\n\t\t ************************************************************************************************\n\t\t * Constructors \n\t\t ************************************************************************************************\n\t\t */\n\t\tpublic ServiceDetails(Context context, String serviceName)\n\t\t{\n\t\t\tthis.mContext = context;\n\t\t\tthis.mServiceName = serviceName;\n\t\t}\n\t\t\n\t\t/*\n\t\t ************************************************************************************************\n\t\t * Public Methods \n\t\t ************************************************************************************************\n\t\t */\n\t\tpublic void initialise()\n\t\t{\n\t\t\tthis.mInitialised = true;\n\t\t\t\n\t\t\t// If the service is running, then automatically bind to it\n\t\t\tif (this.isServiceRunning()) {\n\t\t\t\tstartService();\n\t\t\t}\n\t\t}\n\t\t\n\t\tpublic boolean isInitialised()\n\t\t{\n\t\t\treturn mInitialised;\n\t\t}\n\n\t\tpublic ExecuteResult startService()\n\t\t{\n\t\t\tLog.d(LOCALTAG, \"Starting startService\");\n\t\t\tExecuteResult result = null;\n\t\t\t\n\t\t\ttry {\n\t\t\t\tLog.d(LOCALTAG, \"Attempting to bind to Service\");\n\t\t\t\tif (this.bindToService()) {\n\t\t\t\t\tLog.d(LOCALTAG, \"Bind worked\");\n\t\t\t\t\tresult = new ExecuteResult(ExecuteStatus.OK, createJSONResult(true, ERROR_NONE_CODE, ERROR_NONE_MSG));\n\t\t\t\t} else {\n\t\t\t\t\tLog.d(LOCALTAG, \"Bind Failed\");\n\t\t\t\t\tresult = new ExecuteResult(ExecuteStatus.ERROR, createJSONResult(false, ERROR_UNABLE_TO_BIND_TO_BACKGROUND_SERVICE_CODE, ERROR_UNABLE_TO_BIND_TO_BACKGROUND_SERVICE_MSG));\n\t\t\t\t}\n\t\t\t} catch (Exception ex) {\n\t\t\t\tLog.d(LOCALTAG, \"startService failed\", ex);\n\t\t\t\tresult = new ExecuteResult(ExecuteStatus.ERROR, createJSONResult(false, ERROR_EXCEPTION_CODE, ex.getMessage()));\n\t\t\t}\n\t\t\t\n\t\t\tLog.d(LOCALTAG, \"Finished startService\");\n\t\t\treturn result;\n\t\t}\n\t\t\n\t\tpublic ExecuteResult stopService()\n\t\t{\n\t\t\tExecuteResult result = null;\n\t\t\t\n\t\t\tLog.d(\"ServiceDetails\", \"stopService called\");\n\n\t\t\ttry {\n\t\t\t\t\n\t\t\t\tLog.d(\"ServiceDetails\", \"Unbinding Service\");\n\t\t\t\tthis.mContext.unbindService(serviceConnection);\n\t\t\t\t\n\t\t\t\tLog.d(\"ServiceDetails\", \"Stopping service\");\n\t\t\t\tif (this.mContext.stopService(this.mService))\n\t\t\t\t{\n\t\t\t\t\tLog.d(\"ServiceDetails\", \"Service stopped\");\n\t\t\t\t} else {\n\t\t\t\t\tLog.d(\"ServiceDetails\", \"Service not stopped\");\n\t\t\t\t}\n\t\t\t\tresult = new ExecuteResult(ExecuteStatus.OK, createJSONResult(true, ERROR_NONE_CODE, ERROR_NONE_MSG));\n\t\t\t} catch (Exception ex) {\n\t\t\t\tLog.d(LOCALTAG, \"stopService failed\", ex);\n\t\t\t\tresult = new ExecuteResult(ExecuteStatus.ERROR, createJSONResult(false, ERROR_EXCEPTION_CODE, ex.getMessage()));\n\t\t\t}\n\t\t\t\n\t\t\treturn result;\n\t\t}\n\t\t\n\t\tpublic ExecuteResult enableTimer(JSONArray data)\n\t\t{\n\t\t\tExecuteResult result = null;\n\n\t\t\tint milliseconds = data.optInt(1, 60000);\n\t\t\ttry {\n\t\t\t\tmApi.enableTimer(milliseconds);\n\t\t\t\tresult = new ExecuteResult(ExecuteStatus.OK, createJSONResult(true, ERROR_NONE_CODE, ERROR_NONE_MSG));\n\t\t\t} catch (RemoteException ex) {\n\t\t\t\tLog.d(LOCALTAG, \"enableTimer failed\", ex);\n\t\t\t\tresult = new ExecuteResult(ExecuteStatus.ERROR, createJSONResult(false, ERROR_EXCEPTION_CODE, ex.getMessage()));\n\t\t\t}\n\n\t\t\treturn result;\n\t\t}\n\n\t\tpublic ExecuteResult disableTimer()\n\t\t{\n\t\t\tExecuteResult result = null;\n\t\t\n\t\t\ttry {\n\t\t\t\tmApi.disableTimer();\n\t\t\t\tresult = new ExecuteResult(ExecuteStatus.OK, createJSONResult(true, ERROR_NONE_CODE, ERROR_NONE_MSG));\n\t\t\t} catch (RemoteException ex) {\n\t\t\t\tLog.d(LOCALTAG, \"disableTimer failed\", ex);\n\t\t\t\tresult = new ExecuteResult(ExecuteStatus.ERROR, createJSONResult(false, ERROR_EXCEPTION_CODE, ex.getMessage()));\n\t\t\t}\n\n\t\t\treturn result;\n\t\t}\n\n\t\tpublic ExecuteResult registerForBootStart()\n\t\t{\n\t\t\tExecuteResult result = null;\n\t\t\n\t\t\ttry {\n\t\t\t\tPropertyHelper.addBootService(this.mContext, this.mServiceName);\n\n\t\t\t\tresult = new ExecuteResult(ExecuteStatus.OK, createJSONResult(true, ERROR_NONE_CODE, ERROR_NONE_MSG));\n\t\t\t} catch (Exception ex) {\n\t\t\t\tLog.d(LOCALTAG, \"registerForBootStart failed\", ex);\n\t\t\t\tresult = new ExecuteResult(ExecuteStatus.ERROR, createJSONResult(false, ERROR_EXCEPTION_CODE, ex.getMessage()));\n\t\t\t}\n\n\t\t\treturn result;\n\t\t}\n\t\t\n\t\tpublic ExecuteResult deregisterForBootStart()\n\t\t{\n\t\t\tExecuteResult result = null;\n\t\t\n\t\t\ttry {\n\t\t\t\tPropertyHelper.removeBootService(this.mContext, this.mServiceName);\n\n\t\t\t\tresult = new ExecuteResult(ExecuteStatus.OK, createJSONResult(true, ERROR_NONE_CODE, ERROR_NONE_MSG));\n\t\t\t} catch (Exception ex) {\n\t\t\t\tLog.d(LOCALTAG, \"deregisterForBootStart failed\", ex);\n\t\t\t\tresult = new ExecuteResult(ExecuteStatus.ERROR, createJSONResult(false, ERROR_EXCEPTION_CODE, ex.getMessage()));\n\t\t\t}\n\n\t\t\treturn result;\n\t\t}\n\t\t\n\t\tpublic ExecuteResult setConfiguration(JSONArray data)\n\t\t{\n\t\t\tExecuteResult result = null;\n\t\t\t\n\t\t\ttry {\n\t\t\t\tif (this.isServiceRunning()) {\n\t\t\t\t\tObject obj;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tobj = data.get(1);\n\t\t\t\t\t\tmApi.setConfiguration(obj.toString());\n\t\t\t\t\t\tresult = new ExecuteResult(ExecuteStatus.OK, createJSONResult(true, ERROR_NONE_CODE, ERROR_NONE_MSG));\n\t\t\t\t\t} catch (JSONException e) {\n\t\t\t\t\t\tLog.d(LOCALTAG, \"Processing config JSON from background service failed\", e);\n\t\t\t\t\t\tresult = new ExecuteResult(ExecuteStatus.ERROR, createJSONResult(false, ERROR_EXCEPTION_CODE, e.getMessage()));\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tresult = new ExecuteResult(ExecuteStatus.INVALID_ACTION, createJSONResult(false, ERROR_SERVICE_NOT_RUNNING_CODE, ERROR_SERVICE_NOT_RUNNING_MSG));\n\t\t\t\t}\n\t\t\t} catch (RemoteException ex) {\n\t\t\t\tLog.d(LOCALTAG, \"setConfiguration failed\", ex);\n\t\t\t\tresult = new ExecuteResult(ExecuteStatus.ERROR, createJSONResult(false, ERROR_EXCEPTION_CODE, ex.getMessage()));\n\t\t\t}\n\t\t\t\n\t\t\treturn result;\n\t\t}\n\n\t\tpublic ExecuteResult getStatus()\n\t\t{\n\t\t\tExecuteResult result = null;\n\t\t\t\n\t\t\tresult = new ExecuteResult(ExecuteStatus.OK, createJSONResult(true, ERROR_NONE_CODE, ERROR_NONE_MSG));\n\t\t\t\n\t\t\treturn result;\n\t\t}\n\t\t\n\t\tpublic ExecuteResult runOnce()\n\t\t{\n\t\t\tExecuteResult result = null;\n\t\t\t\n\t\t\ttry {\n\t\t\t\tif (this.isServiceRunning()) {\n\t\t\t\t\tmApi.run();\n\t\t\t\t\tresult = new ExecuteResult(ExecuteStatus.OK, createJSONResult(true, ERROR_NONE_CODE, ERROR_NONE_MSG));\n\t\t\t\t} else {\n\t\t\t\t\tresult = new ExecuteResult(ExecuteStatus.INVALID_ACTION, createJSONResult(false, ERROR_SERVICE_NOT_RUNNING_CODE, ERROR_SERVICE_NOT_RUNNING_MSG));\n\t\t\t\t}\n\t\t\t} catch (RemoteException ex) {\n\t\t\t\tLog.d(LOCALTAG, \"runOnce failed\", ex);\n\t\t\t\tresult = new ExecuteResult(ExecuteStatus.ERROR, createJSONResult(false, ERROR_EXCEPTION_CODE, ex.getMessage()));\n\t\t\t}\n\t\t\t\n\t\t\treturn result;\n\t\t}\n\n\t\tpublic ExecuteResult registerForUpdates(IUpdateListener listener, Object[] listenerExtras)\n\t\t{\n\t\t\tExecuteResult result = null;\n\t\t\ttry {\n\t\t\t\t\n\t\t\t\t// Check for if the listener is null\n\t\t\t\t// If it is then it will be because the Plguin version doesn't support the method\n\t\t\t\tif (listener == null) {\n\t\t\t\t\tresult = new ExecuteResult(ExecuteStatus.INVALID_ACTION, createJSONResult(false, ERROR_ACTION_NOT_SUPPORTED__IN_PLUGIN_VERSION_CODE, ERROR_ACTION_NOT_SUPPORTED__IN_PLUGIN_VERSION_MSG));\n\t\t\t\t} else {\n\t\t\t\t\t\n\t\t\t\t\t// If a listener already exists, then we fist need to deregister the original\n\t\t\t\t\t// Ignore any failures (likely due to the listener not being available anymore)\n\t\t\t\t\tif (this.isRegisteredForUpdates()) \n\t\t\t\t\t\tthis.deregisterListener();\n\t\t\t\t\n\t\t\t\t\tthis.mListener = listener;\n\t\t\t\t\tthis.mListenerExtras = listenerExtras;\n\n\t\t\t\t\tresult = new ExecuteResult(ExecuteStatus.OK, createJSONResult(true, ERROR_NONE_CODE, ERROR_NONE_MSG), false);\n\t\t\t\t}\n\t\t\t} catch (Exception ex) {\n\t\t\t\tLog.d(LOCALTAG, \"regsiterForUpdates failed\", ex);\n\t\t\t\tresult = new ExecuteResult(ExecuteStatus.ERROR, createJSONResult(false, ERROR_EXCEPTION_CODE, ex.getMessage()));\n\t\t\t}\n\t\t\t\n\t\t\treturn result;\n\t\t}\n\t\t\n\t\tpublic ExecuteResult deregisterForUpdates()\n\t\t{\n\t\t\tExecuteResult result = null;\n\t\t\ttry {\n\t\t\t\tif (this.isRegisteredForUpdates())\n\t\t\t\t\tif (this.deregisterListener())\n\t\t\t\t\t\tresult = new ExecuteResult(ExecuteStatus.OK, createJSONResult(true, ERROR_NONE_CODE, ERROR_NONE_MSG));\n\t\t\t\t\telse\n\t\t\t\t\t\tresult = new ExecuteResult(ExecuteStatus.ERROR, createJSONResult(false, ERROR_UNABLE_TO_CLOSED_LISTENER_CODE, ERROR_UNABLE_TO_CLOSED_LISTENER_MSG));\n\t\t\t\telse\n\t\t\t\t\tresult = new ExecuteResult(ExecuteStatus.INVALID_ACTION, createJSONResult(false, ERROR_LISTENER_NOT_REGISTERED_CODE, ERROR_LISTENER_NOT_REGISTERED_MSG));\n\t\t\t\t\n\t\t\t} catch (Exception ex) {\n\t\t\t\tLog.d(LOCALTAG, \"deregsiterForUpdates failed\", ex);\n\t\t\t\tresult = new ExecuteResult(ExecuteStatus.ERROR, createJSONResult(false, ERROR_EXCEPTION_CODE, ex.getMessage()));\n\t\t\t}\n\t\t\t\n\t\t\treturn result;\n\t\t}\n\n\t\t/*\n\t\t * Background Service specific methods\n\t\t */\n\t\tpublic void close()\n\t\t{\n\t\t\tLog.d(\"ServiceDetails\", \"Close called\");\n\t\t\ttry {\n\t\t\t\t// Remove the lister to this publisher\n\t\t\t\tthis.deregisterListener();\n\t\t\t\t\n\t\t\t\tLog.d(\"ServiceDetails\", \"Removing ServiceListener\");\n\t\t\t\tmApi.removeListener(serviceListener);\n\t\t\t\tLog.d(\"ServiceDetails\", \"Removing ServiceConnection\");\n\t\t\t\tthis.mContext.unbindService(serviceConnection);\n\t\t\t} catch (Exception ex) {\n\t\t\t\t// catch any issues, typical for destroy routines\n\t\t\t\t// even if we failed to destroy something, we need to continue destroying\n\t\t\t\tLog.d(LOCALTAG, \"close failed\", ex);\n\t\t\t\tLog.d(LOCALTAG, \"Ignoring exception - will continue\");\n\t\t\t}\n\t\t\tLog.d(\"ServiceDetails\", \"Close finished\");\n\t\t}\n\n\t\tprivate boolean deregisterListener() {\n\t\t\tboolean result = false;\n\n\t\t\tif (this.isRegisteredForUpdates()) {\n\t\t\t\tLog.d(\"ServiceDetails\", \"Listener deregistering\");\n\t\t\t\ttry {\n\t\t\t\t\tLog.d(\"ServiceDetails\", \"Listener closing\");\n\t\t\t\t\tthis.mListener.closeListener(new ExecuteResult(ExecuteStatus.OK, createJSONResult(true, ERROR_NONE_CODE, ERROR_NONE_MSG)), this.mListenerExtras);\n\t\t\t\t\tLog.d(\"ServiceDetails\", \"Listener closed\");\n\t\t\t\t} catch (Exception ex) {\n\t\t\t\t\tLog.d(\"ServiceDetails\", \"Error occurred while closing the listener\", ex);\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tthis.mListener = null;\n\t\t\t\tthis.mListenerExtras = null;\n\t\t\t\tLog.d(\"ServiceDetails\", \"Listener deregistered\");\n\t\t\t\t\n\t\t\t\tresult = true;\n\t\t\t}\n\t\t\t\n\t\t\treturn result;\n\t\t}\n\n\t\t/*\n\t\t ************************************************************************************************\n\t\t * Private Methods \n\t\t ************************************************************************************************\n\t\t */\n\t\tprivate boolean bindToService() {\n\t\t\tboolean result = false;\n\t\t\t\n\t\t\tLog.d(LOCALTAG, \"Starting bindToService\");\n\t\t\t\n\t\t\ttry {\n\t\t\t\t// Fix to https://github.com/Red-Folder/bgs-core/issues/18\n\t\t\t\t// Gets the class from string\n\t\t\t\tClass<?> serviceClass = ReflectionHelper.LoadClass(this.mServiceName);\n\t\t\t\tthis.mService = new Intent(this.mContext, serviceClass);\n\n\t\t\t\tLog.d(LOCALTAG, \"Attempting to start service\");\n\t\t\t\tthis.mContext.startService(this.mService);\n\n\t\t\t\tLog.d(LOCALTAG, \"Attempting to bind to service\");\n\t\t\t\tif (this.mContext.bindService(this.mService, serviceConnection, 0)) {\n\t\t\t\t\tLog.d(LOCALTAG, \"Waiting for service connected lock\");\n\t\t\t\t\tsynchronized(mServiceConnectedLock) {\n\t\t\t\t\t\twhile (mServiceConnected==null) {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tmServiceConnectedLock.wait();\n\t\t\t\t\t\t\t} catch (InterruptedException e) {\n\t\t\t\t\t\t\t\tLog.d(LOCALTAG, \"Interrupt occurred while waiting for connection\", e);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tresult = this.mServiceConnected;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (Exception ex) {\n\t\t\t\tLog.d(LOCALTAG, \"bindToService failed\", ex);\n\t\t\t}\n\n\t\t\tLog.d(LOCALTAG, \"Finished bindToService\");\n\n\t\t\treturn result;\n\t\t}\n\t\t\n\t\tprivate ServiceConnection serviceConnection = new ServiceConnection() {\n\t\t\t@Override\n\t\t\tpublic void onServiceConnected(ComponentName name, IBinder service) {\n\t\t\t\t// that's how we get the client side of the IPC connection\n\t\t\t\tmApi = BackgroundServiceApi.Stub.asInterface(service);\n\t\t\t\ttry {\n\t\t\t\t\tmApi.addListener(serviceListener);\n\t\t\t\t} catch (RemoteException e) {\n\t\t\t\t\tLog.d(LOCALTAG, \"addListener failed\", e);\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tsynchronized(mServiceConnectedLock) {\n\t\t\t\t\tmServiceConnected = true;\n\n\t\t\t\t\tmServiceConnectedLock.notify();\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void onServiceDisconnected(ComponentName name) {\n\t\t\t\tsynchronized(mServiceConnectedLock) {\n\t\t\t\t\tmServiceConnected = false;\n\n\t\t\t\t\tmServiceConnectedLock.notify();\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\t\tprivate BackgroundServiceListener.Stub serviceListener = new BackgroundServiceListener.Stub() {\n\t\t\t@Override\n\t\t\tpublic void handleUpdate() throws RemoteException {\n\t\t\t\thandleLatestResult();\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic String getUniqueID() throws RemoteException {\n\t\t\t\treturn mUniqueID;\n\t\t\t}\n\t\t};\n\n\t\tprivate void handleLatestResult() {\n\t\t\tLog.d(\"ServiceDetails\", \"Latest results received\");\n\t\t\t\n\t\t\tif (this.isRegisteredForUpdates()) {\n\t\t\t\tLog.d(\"ServiceDetails\", \"Calling listener\");\n\t\t\t\t\n\t\t\t\tExecuteResult result = new ExecuteResult(ExecuteStatus.OK, createJSONResult(true, ERROR_NONE_CODE, ERROR_NONE_MSG), false);\n\t\t\t\ttry {\n\t\t\t\t\tthis.mListener.handleUpdate(result, this.mListenerExtras);\n\t\t\t\t\tLog.d(\"ServiceDetails\", \"Listener finished\");\n\t\t\t\t} catch (Exception ex) {\n\t\t\t\t\tLog.d(\"ServiceDetails\", \"Listener failed\", ex);\n\t\t\t\t\tLog.d(\"ServiceDetails\", \"Disabling listener\");\n\t\t\t\t\tthis.mListener = null;\n\t\t\t\t\tthis.mListenerExtras = null;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tLog.d(\"ServiceDetails\", \"No action performed\");\n\t\t\t}\n\t\t}\n\n\t\tprivate JSONObject createJSONResult(Boolean success, int errorCode, String errorMessage) {\n\t\t\tJSONObject result = new JSONObject();\n\n\t\t\t// Append the basic information\n\t\t\ttry {\n\t\t\t\tresult.put(\"Success\", success);\n\t\t\t\tresult.put(\"ErrorCode\", errorCode);\n\t\t\t\tresult.put(\"ErrorMessage\", errorMessage);\n\t\t\t} catch (JSONException e) {\n\t\t\t\tLog.d(LOCALTAG, \"Adding basic info to JSONObject failed\", e);\n\t\t\t}\n\n\t\t\tif (this.mServiceConnected != null && this.mServiceConnected && this.isServiceRunning()) {\n\t\t\t\ttry { result.put(\"ServiceRunning\", true); } catch (Exception ex) {Log.d(LOCALTAG, \"Adding ServiceRunning to JSONObject failed\", ex);};\n\t\t\t\ttry { result.put(\"TimerEnabled\", isTimerEnabled()); } catch (Exception ex) {Log.d(LOCALTAG, \"Adding TimerEnabled to JSONObject failed\", ex);};\n\t\t\t\ttry { result.put(\"Configuration\", getConfiguration()); } catch (Exception ex) {Log.d(LOCALTAG, \"Adding Configuration to JSONObject failed\", ex);};\n\t\t\t\ttry { result.put(\"LatestResult\", getLatestResult()); } catch (Exception ex) {Log.d(LOCALTAG, \"Adding LatestResult to JSONObject failed\", ex);};\n\t\t\t\ttry { result.put(\"TimerMilliseconds\", getTimerMilliseconds()); } catch (Exception ex) {Log.d(LOCALTAG, \"Adding TimerMilliseconds to JSONObject failed\", ex);};\n\t\t\t} else {\n\t\t\t\ttry { result.put(\"ServiceRunning\", false); } catch (Exception ex) {Log.d(LOCALTAG, \"Adding ServiceRunning to JSONObject failed\", ex);};\n\t\t\t\ttry { result.put(\"TimerEnabled\", JSONObject.NULL); } catch (Exception ex) {Log.d(LOCALTAG, \"Adding TimerEnabled to JSONObject failed\", ex);};\n\t\t\t\ttry { result.put(\"Configuration\", JSONObject.NULL); } catch (Exception ex) {Log.d(LOCALTAG, \"Adding Configuration to JSONObject failed\", ex);};\n\t\t\t\ttry { result.put(\"LatestResult\", JSONObject.NULL); } catch (Exception ex) {Log.d(LOCALTAG, \"Adding LatestResult to JSONObject failed\", ex);};\n\t\t\t\ttry { result.put(\"TimerMilliseconds\", JSONObject.NULL); } catch (Exception ex) {Log.d(LOCALTAG, \"Adding TimerMilliseconds to JSONObject failed\", ex);};\n\t\t\t}\n\n\t\t\ttry { result.put(\"RegisteredForBootStart\", isRegisteredForBootStart()); } catch (Exception ex) {Log.d(LOCALTAG, \"Adding RegisteredForBootStart to JSONObject failed\", ex);};\n\t\t\ttry { result.put(\"RegisteredForUpdates\", isRegisteredForUpdates()); } catch (Exception ex) {Log.d(LOCALTAG, \"Adding RegisteredForUpdates to JSONObject failed\", ex);};\n\t\t\t\t\n\t\t\treturn result;\n\t\t}\n\t\t\n\t\tprivate boolean isServiceRunning()\n\t\t{\n\t\t\tboolean result = false;\n\t\t\t\n\t\t\ttry {\n\t\t\t\t// Return Plugin with ServiceRunning true/ false\n\t\t\t\tActivityManager manager = (ActivityManager)this.mContext.getSystemService(Context.ACTIVITY_SERVICE); \n\t\t\t\tfor (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) { \n\t\t\t\t\tif (this.mServiceName.equals(service.service.getClassName())) { \n\t\t\t\t\t\tresult = true; \n\t\t\t\t\t} \n\t\t\t\t} \n\t\t\t} catch (Exception ex) {\n\t\t\t\tLog.d(LOCALTAG, \"isServiceRunning failed\", ex);\n\t\t\t}\n\n\t\t    return result;\n\t\t}\n\n\t\tprivate Boolean isTimerEnabled()\n\t\t{\n\t\t\tBoolean result = false;\n\t\t\t\n\t\t\ttry {\n\t\t\t\tresult = mApi.isTimerEnabled();\n\t\t\t} catch (Exception ex) {\n\t\t\t\tLog.d(LOCALTAG, \"isTimerEnabled failed\", ex);\n\t\t\t}\n\t\t\t\n\t\t\treturn result;\n\t\t}\n\n\t\tprivate Boolean isRegisteredForBootStart()\n\t\t{\n\t\t\tBoolean result = false;\n\t\t\n\t\t\ttry {\n\t\t\t\tresult = PropertyHelper.isBootService(this.mContext, this.mServiceName);\n\t\t\t} catch (Exception ex) {\n\t\t\t\tLog.d(LOCALTAG, \"isRegisteredForBootStart failed\", ex);\n\t\t\t}\n\n\t\t\treturn result;\n\t\t}\n\n\t\tprivate Boolean isRegisteredForUpdates()\n\t\t{\n\t\t\tif (this.mListener == null)\n\t\t\t\treturn false;\n\t\t\telse\n\t\t\t\treturn true;\n\t\t}\n\n\t\tprivate JSONObject getConfiguration()\n\t\t{\n\t\t\tJSONObject result = null;\n\t\t\t\n\t\t\ttry {\n\t\t\t\tString data = mApi.getConfiguration();\n\t\t\t\tresult = new JSONObject(data);\n\t\t\t} catch (Exception ex) {\n\t\t\t\tLog.d(LOCALTAG, \"getConfiguration failed\", ex);\n\t\t\t}\n\t\t\t\n\t\t\treturn result;\n\t\t}\n\n\t\tprivate JSONObject getLatestResult()\n\t\t{\n\t\t\tJSONObject result = null;\n\n\t\t\ttry {\n\t\t\t\tString data = mApi.getLatestResult();\n\t\t\t\tresult = new JSONObject(data);\n\t\t\t} catch (Exception ex) {\n\t\t\t\tLog.d(LOCALTAG, \"getLatestResult failed\", ex);\n\t\t\t}\n\n\t\t\treturn result;\n\t\t}\n\n\t\tprivate int getTimerMilliseconds()\n\t\t{\n\t\t\tint result = -1;\n\t\t\t\n\t\t\ttry {\n\t\t\t\tresult = mApi.getTimerMilliseconds();\n\t\t\t} catch (Exception ex) {\n\t\t\t\tLog.d(LOCALTAG, \"getTimerMilliseconds failed\", ex);\n\t\t\t}\n\t\t\t\n\t\t\treturn result;\n\t\t}\n\n\t}\n\n\tprotected class ExecuteResult {\n\n\t\t/*\n\t\t ************************************************************************************************\n\t\t * Fields \n\t\t ************************************************************************************************\n\t\t */\n\t\tprivate ExecuteStatus mStatus;\n\t\tprivate JSONObject mData;\n\t\tprivate boolean mFinished = true;\n\n\t\tpublic ExecuteStatus getStatus() {\n\t\t\treturn this.mStatus;\n\t\t}\n\t\t\n\t\tpublic void setStatus(ExecuteStatus pStatus) {\n\t\t\tthis.mStatus = pStatus;\n\t\t}\n\t\t\n\t\tpublic JSONObject getData() {\n\t\t\treturn this.mData;\n\t\t}\n\t\t\n\t\tpublic void setData(JSONObject pData) {\n\t\t\tthis.mData = pData;\n\t\t}\n\t\t\n\t\tpublic boolean isFinished() {\n\t\t\treturn this.mFinished;\n\t\t}\n\n\t\tpublic void setFinished(boolean pFinished) {\n\t\t\tthis.mFinished = pFinished;\n\t\t}\n\t\t\n\t\t/*\n\t\t ************************************************************************************************\n\t\t * Constructors \n\t\t ************************************************************************************************\n\t\t */\n\t\tpublic ExecuteResult(ExecuteStatus pStatus) {\n\t\t\tthis.mStatus = pStatus;\n\t\t}\n\t\t\n\t\tpublic ExecuteResult(ExecuteStatus pStatus, JSONObject pData) {\n\t\t\tthis.mStatus = pStatus;\n\t\t\tthis.mData = pData;\n\t\t}\n\n\t\tpublic ExecuteResult(ExecuteStatus pStatus, JSONObject pData, boolean pFinished) {\n\t\t\tthis.mStatus = pStatus;\n\t\t\tthis.mData = pData;\n\t\t\tthis.mFinished = pFinished;\n\t\t}\n\n\t}\n\n\tpublic interface IUpdateListener {\n\t\tpublic void handleUpdate(ExecuteResult logicResult, Object[] listenerExtras);\n\t\tpublic void closeListener(ExecuteResult logicResult, Object[] listenerExtras);\n\t}\n\t\n\t/*\n\t ************************************************************************************************\n\t * Enums \n\t ************************************************************************************************\n\t */\n\tprotected enum ExecuteStatus {\n\t\tOK,\n\t\tERROR,\n\t\tINVALID_ACTION\n\t}\n\n\n}\n"
  },
  {
    "path": "src/android/BootReceiver.java",
    "content": "package com.red_folder.phonegap.plugin.backgroundservice;\n\nimport android.content.BroadcastReceiver;\nimport android.content.Context;\nimport android.content.Intent;\n\npublic class BootReceiver extends BroadcastReceiver {  \n\t\n\t/*\n\t ************************************************************************************************\n\t * Overriden Methods \n\t ************************************************************************************************\n\t */\n\t@Override  \n\tpublic void onReceive(Context context, Intent intent) {\n\t\t\n\t\t// Get all the registered and loop through and start them\n\t\tString[] serviceList = PropertyHelper.getBootServices(context);\n\t\t\n\t\tif (serviceList != null) {\n\t\t\tfor (int i = 0; i < serviceList.length; i++)\n\t\t\t{\n\t\t\t\t// Fix to https://github.com/Red-Folder/bgs-core/issues/18\n\t\t\t\t// Gets the class from string\n\t\t\t\tClass<?> serviceClass = ReflectionHelper.LoadClass(serviceList[i]);\n\n\t\t\t\tIntent serviceIntent = new Intent(context, serviceClass);         \n\t\t\t\tcontext.startService(serviceIntent);\n\t\t\t}\n\t\t}\n\t} \n\t\n} \n"
  },
  {
    "path": "src/android/PropertyHelper.java",
    "content": "package com.red_folder.phonegap.plugin.backgroundservice;\n\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.preference.PreferenceManager;\n\npublic class PropertyHelper {\n\t\n\t/*\n\t ************************************************************************************************\n\t * Fields \n\t ************************************************************************************************\n\t */\n\tprivate final static String KEY_BOOTSERVICES = \"com.red_folder.phonegap.plugin.backgroundservice.BootServices\";\n\n\t/*\n\t ************************************************************************************************\n\t * Public Methods \n\t ************************************************************************************************\n\t */\n\tprotected static String[] getBootServices(Context context) {\n\t\tString serviceList = getBootServicesString(context);\n\t\t\t\t\n\t\tif (serviceList.length() == 0)\n\t\t\treturn null;\n\t\telse\n\t\t\treturn serviceList.split(\";\");\n\t}\n\n\tprotected static void addBootService(Context context, String serviceName) {\n\t\tString serviceList = getBootServicesString(context);\n\t\t\n\t\tif (serviceList.length() == 0)\n\t\t\tserviceList = serviceName;\n\t\telse\n\t\t\tif (serviceList.contains(serviceName)) {\n\t\t\t\t// Already in the list, so ignore the request\n\t\t\t} else {\n\t\t\t\tserviceList += \";\" + serviceName;\n\t\t\t}\n\t\t\n\t\tsaveBootServices(context, serviceList);\n\t}\n\n\tprotected static void removeBootService(Context context, String serviceName) {\n\t\tString serviceList = getBootServicesString(context);\n\t\t\n\t\tString newServiceList = \"\";\n\t\tif (serviceList.length() > 0)\n\t\t{\n\t\t\tif (serviceList.contains(serviceName)) {\n\t\t\t\tString[] services = serviceList.split(\";\");\n\t\t\t\tfor (int i = 0; i < services.length; i++) {\n\t\t\t\t\tif (!services[i].contains(serviceName))\n\t\t\t\t\t\tnewServiceList += \";\" + services[i];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t\n\t\tsaveBootServices(context, newServiceList);\n\t}\n\n\tprotected static boolean isBootService(Context context, String serviceName) {\n\t\tString serviceList = getBootServicesString(context);\n\t\tboolean result = false;\n\t\t\n\t\tif (serviceList.length() > 0)\n\t\t{\n\t\t\tif (serviceList.contains(serviceName)) {\n\t\t\t\tresult = true;\n\t\t\t}\n\t\t}\n\t\t\n\t\treturn result;\n\t}\n\n\t/*\n\t ************************************************************************************************\n\t * Private Methods \n\t ************************************************************************************************\n\t */\n\tprivate static void saveBootServices(Context context, String serviceList) {\n\t\tSharedPreferences.Editor editor = getEditor(context);\n        editor.putString(KEY_BOOTSERVICES, serviceList);\n        editor.commit(); // Very important\n\t}\n\t\n\tprivate static String getBootServicesString(Context context) {\n\t\tSharedPreferences sharedPrefs = getSharedPreferences(context);  \n\n\t\treturn sharedPrefs.getString(KEY_BOOTSERVICES, \"\");\n\t}\n\t\n\tprivate static SharedPreferences getSharedPreferences(Context context) {\n\t\treturn PreferenceManager.getDefaultSharedPreferences(context);\n\t}\n\t\n\tprivate static SharedPreferences.Editor getEditor(Context context) {\n\t\tSharedPreferences sharedPrefs = getSharedPreferences(context);\n\t\treturn sharedPrefs.edit();\n\t}\n}\n"
  },
  {
    "path": "src/android/ReflectionHelper.java",
    "content": "package com.red_folder.phonegap.plugin.backgroundservice;\r\n\r\nimport android.util.Log;\r\n\r\npublic class ReflectionHelper {\r\n\t\r\n\t/*\r\n\t ************************************************************************************************\r\n\t * Static values \r\n\t ************************************************************************************************\r\n\t */\r\n\tpublic static final String TAG = ReflectionHelper.class.getSimpleName();\r\n\t\r\n\tpublic static Class<?> LoadClass(String className) {\r\n\t\tClass<?> result = null;\r\n\t\r\n\t\tLog.d(TAG, \"Attempting to load call: \" + className);\r\n\t\tClassLoader classLoader = ReflectionHelper.class.getClassLoader();\r\n\r\n\t\ttry {\r\n\t\t\tresult = classLoader.loadClass(className);\r\n\t\t\tLog.d(TAG, \"Class loaded\");\r\n\t\t} catch (ClassNotFoundException ex) {\r\n\t\t\tLog.d(TAG, \"Class failed to load\");\r\n\t\t\tLog.d(TAG, ex.getMessage());\r\n\t\t}\r\n\t\t\r\n\t\treturn result;\r\n\t}\r\n}\r\n"
  },
  {
    "path": "www/backgroundService.js",
    "content": "/*\n * Copyright 2013 Red Folder Consultancy Ltd\n *   \n * Licensed under the Apache License, Version 2.0 (the \"License\");   \n * you may not use this file except in compliance with the License.   \n * You may obtain a copy of the License at       \n * \n * \thttp://www.apache.org/licenses/LICENSE-2.0   \n *\n * Unless required by applicable law or agreed to in writing, software   \n * distributed under the License is distributed on an \"AS IS\" BASIS,   \n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   \n * See the License for the specific language governing permissions and   \n * limitations under the License.\n */\n\n /*\n  * Declare a factory class which is used to create the background service \"wrapper\"\n  */\nfunction BackgroundServiceFactory() { }\n\nBackgroundServiceFactory.prototype.create = function (serviceName) {\n\tvar exec = require(\"cordova/exec\");\n\t\n\tvar BackgroundService = function (serviceName) {\n\t\tvar ServiceName = serviceName;\n\t\tthis.getServiceName = function() {\n\t\t\treturn ServiceName;\n\t\t};\n\t};\n\n\t/**\n\t  * All methods attempt to return the following data in both the success and failure callbacks\n\t  * Front end development should take into account any all or all of these values may be null\n\t  *\n\t  * Following returned in the JSONObject:\n\t  *\t\tBoolean Success - was the call a success\n\t  *\t\tint ErrorCode - Error code if an error occurred, else will be zero\n\t  *\t\tString ErrorMessage - Text representation of the error code\n\t  *\t\tBoolean ServiceRunning - True if the Service is running\n\t  *\t\tBoolean TimerEnabled - True if the Timer is enabled\n\t  *\t\tBoolean RegisteredForBootStart - True if the Service is registered for boot start\n\t  *\t\tJSONObject Configuration - A JSONObject of the configuration of the service (contents dependant on the service)\n\t  *\t\tJSONObject LastestResult - A JSONObject of the last result of the service (contents dependant on the service)\n\t  *\t\tint TimerMilliseconds - Milliseconds used by the background service if Timer enabled\n\t  *\t\tBoolean RegisteredForUpdates - True if the Service is registered to send updates to the front-end\n\t  */\n\n\t/**\n\t  * Starts the Service\n\t  * \n\t  * @param successCallback The callback which will be called if the method is successful\n\t  * @param failureCallback The callback which will be called if the method encounters an error\n\t  */\n\tBackgroundService.prototype.startService = function(successCallback, failureCallback) { \n\t\treturn exec(\tsuccessCallback,      \n\t\t\t\t\t\tfailureCallback,      \n\t\t\t\t\t\t'BackgroundServicePlugin',      \n\t\t\t\t\t\t'startService',      \n\t\t\t\t\t\t[this.getServiceName()]);\n\t};\n\n\t/**\n\t  * Stops the Service\n\t  *\n\t  * @param successCallback The callback which will be called if the method is successful\n\t  * @param failureCallback The callback which will be called if the method encounters an error\n\t  */\n\tBackgroundService.prototype.stopService = function(successCallback, failureCallback) { \n\t\treturn exec(\tsuccessCallback,      \n\t\t\t\t\t\tfailureCallback,      \n\t\t\t\t\t\t'BackgroundServicePlugin',      \n\t\t\t\t\t\t'stopService',      \n\t\t\t\t\t\t[this.getServiceName()]);\n\t};\n\n\t/**\n\t  * Enables the Service Timer\n\t  *\n\t  * @param milliseconds The milliseconds used for the timer\n\t  * @param successCallback The callback which will be called if the method is successful\n\t  * @param failureCallback The callback which will be called if the method encounters an error\n\t  */\n\tBackgroundService.prototype.enableTimer = function(milliseconds, successCallback, failureCallback) { \n\t\treturn exec(\tsuccessCallback,      \n\t\t\t\t\t\tfailureCallback,      \n\t\t\t\t\t\t'BackgroundServicePlugin',      \n\t\t\t\t\t\t'enableTimer',      \n\t\t\t\t\t\t[this.getServiceName(), milliseconds]);\n\t};\n\n\t/**\n\t  * Disabled the Service Timer\n\t  *\n\t  * @param successCallback The callback which will be called if the method is successful\n\t  * @param failureCallback The callback which will be called if the method encounters an error\n\t  */\n\tBackgroundService.prototype.disableTimer = function(successCallback, failureCallback) { \n\t\treturn exec(\tsuccessCallback,      \n\t\t\t\t\t\tfailureCallback,      \n\t\t\t\t\t\t'BackgroundServicePlugin',      \n\t\t\t\t\t\t'disableTimer',      \n\t\t\t\t\t\t[this.getServiceName()]);\n\t};\n\n\t/**\n\t  * Sets the configuration for the service\n\t  *\n\t  * @param configuration JSONObject to be sent to the service\n\t  * @param successCallback The callback which will be called if the method is successful\n\t  * @param failureCallback The callback which will be called if the method encounters an error\n\t  */\n\tBackgroundService.prototype.setConfiguration = function(configuration, successCallback, failureCallback) { \n\t\treturn exec(\tsuccessCallback,      \n\t\t\t\t\t\tfailureCallback,      \n\t\t\t\t\t\t'BackgroundServicePlugin',      \n\t\t\t\t\t\t'setConfiguration',      \n\t\t\t\t\t\t[this.getServiceName(), configuration]);\n\t};\n\n\t/**\n\t  * Registers the service for Boot Start\n\t  *\n\t  * @param successCallback The callback which will be called if the method is successful\n\t  * @param failureCallback The callback which will be called if the method encounters an error\n\t  */\n\tBackgroundService.prototype.registerForBootStart = function(successCallback, failureCallback) { \n\t\treturn exec(\tsuccessCallback,      \n\t\t\t\t\t\tfailureCallback,      \n\t\t\t\t\t\t'BackgroundServicePlugin',      \n\t\t\t\t\t\t'registerForBootStart',      \n\t\t\t\t\t\t[this.getServiceName()]);\n\t};\n\n\t/**\n\t  * Deregisters the service for Boot Start\n\t  *\n\t  * @param successCallback The callback which will be called if the method is successful\n\t  * @param failureCallback The callback which will be called if the method encounters an error\n\t  */\n\tBackgroundService.prototype.deregisterForBootStart = function(successCallback, failureCallback) { \n\t\treturn exec(\tsuccessCallback,      \n\t\t\t\t\t\tfailureCallback,      \n\t\t\t\t\t\t'BackgroundServicePlugin',      \n\t\t\t\t\t\t'deregisterForBootStart',      \n\t\t\t\t\t\t[this.getServiceName()]);\n\t};\n\n\t/**\n\t  * Get the current status of the service.\t\n\t  * \n\t  * @param successCallback The callback which will be called if the method is successful\n\t  * @param failureCallback The callback which will be called if the method encounters an error\n\t  */\n\tBackgroundService.prototype.isRegisteredForBootStart = function(successCallback, failureCallback) { \n\t\treturn exec(\tsuccessCallback,      \n\t\t\t\t\t\tfailureCallback,      \n\t\t\t\t\t\t'BackgroundServicePlugin',      \n\t\t\t\t\t\t'isRegisteredForBootStart',      \n\t\t\t\t\t\t[this.getServiceName()]);\n\t};\n\n\n\t/**\n\t  * Returns the status of the service\n\t  *\n\t  * @param successCallback The callback which will be called if the method is successful\n\t  * @param failureCallback The callback which will be called if the method encounters an error\n\t  */\n\tBackgroundService.prototype.getStatus = function(successCallback, failureCallback) { \n\t\treturn exec(\tsuccessCallback,      \n\t\t\t\t\t\tfailureCallback,      \n\t\t\t\t\t\t'BackgroundServicePlugin',      \n\t\t\t\t\t\t'getStatus',      \n\t\t\t\t\t\t[this.getServiceName()]);\n\t};\n\n\t/**\n\t  * Returns the doWork once\n\t  *\n\t  * @param successCallback The callback which will be called if the method is successful\n\t  * @param failureCallback The callback which will be called if the method encounters an error\n\t  */\n\tBackgroundService.prototype.runOnce = function(successCallback, failureCallback) { \n\t\treturn exec(\tsuccessCallback,      \n\t\t\t\t\t\tfailureCallback,      \n\t\t\t\t\t\t'BackgroundServicePlugin',      \n\t\t\t\t\t\t'runOnce',      \n\t\t\t\t\t\t[this.getServiceName()]);\n\t};\n\n\t/**\n\t  * Registers for doWork() updates\n\t  *\n\t  * @param successCallback The callback which will be called if the method is successful\n\t  * @param failureCallback The callback which will be called if the method encounters an error\n\t  */\n\tBackgroundService.prototype.registerForUpdates = function(successCallback, failureCallback) { \n\t\treturn exec(\tsuccessCallback,      \n\t\t\t\t\t\tfailureCallback,      \n\t\t\t\t\t\t'BackgroundServicePlugin',      \n\t\t\t\t\t\t'registerForUpdates',      \n\t\t\t\t\t\t[this.getServiceName()]);\n\t};\n\n\t/**\n\t  * Deregisters for doWork() updates\n\t  *\n\t  * @param successCallback The callback which will be called if the method is successful\n\t  * @param failureCallback The callback which will be called if the method encounters an error\n\t  */\n\tBackgroundService.prototype.deregisterForUpdates = function(successCallback, failureCallback) { \n\t\treturn exec(\tsuccessCallback,      \n\t\t\t\t\t\tfailureCallback,      \n\t\t\t\t\t\t'BackgroundServicePlugin',      \n\t\t\t\t\t\t'deregisterForUpdates',      \n\t\t\t\t\t\t[this.getServiceName()]);\n\t};\n\n\tvar backgroundService = new BackgroundService(serviceName);\n\treturn backgroundService;\n}; \n\nmodule.exports = new BackgroundServiceFactory();\n"
  }
]