Some fixes and enhancements for Android

This commit is contained in:
Sebastián Katzer 2019-02-03 15:58:40 +01:00
parent b5950b578d
commit 6b522e9832
3 changed files with 246 additions and 214 deletions

View File

@ -30,7 +30,6 @@ import android.content.Intent;
import android.os.Build; import android.os.Build;
import android.os.PowerManager; import android.os.PowerManager;
import android.view.View; import android.view.View;
import android.view.Window;
import org.apache.cordova.CallbackContext; import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaInterface; import org.apache.cordova.CordovaInterface;
@ -39,24 +38,29 @@ import org.apache.cordova.CordovaWebView;
import org.apache.cordova.PluginResult; import org.apache.cordova.PluginResult;
import org.apache.cordova.PluginResult.Status; import org.apache.cordova.PluginResult.Status;
import java.lang.ref.WeakReference;
import java.util.List; import java.util.List;
import static android.content.Context.ACTIVITY_SERVICE; import static android.content.Context.ACTIVITY_SERVICE;
import static android.content.Context.POWER_SERVICE; import static android.content.Context.POWER_SERVICE;
import static android.os.Build.VERSION.SDK_INT;
import static android.view.WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON; import static android.view.WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON;
import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD; import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
import static android.view.WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON; import static android.view.WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
/**
* Implements extended functions around the main purpose
* of infinite execution in the background.
*/
class BackgroundExt { class BackgroundExt {
// Weak reference to the cordova interface passed by the plugin // Reference to the cordova interface passed by the plugin
private final WeakReference<CordovaInterface> cordova; private final CordovaInterface cordova;
// Weak reference to the cordova web view passed by the plugin // Reference to the cordova web view passed by the plugin
private final WeakReference<CordovaWebView> webView; private final CordovaWebView webView;
// To keep the device awake
private PowerManager.WakeLock wakeLock; private PowerManager.WakeLock wakeLock;
/** /**
@ -64,34 +68,23 @@ class BackgroundExt {
* *
* @param plugin The cordova plugin. * @param plugin The cordova plugin.
*/ */
private BackgroundExt(CordovaPlugin plugin) { BackgroundExt(CordovaPlugin plugin)
this.cordova = new WeakReference<CordovaInterface>(plugin.cordova); {
this.webView = new WeakReference<CordovaWebView>(plugin.webView); this.cordova = plugin.cordova;
this.webView = plugin.webView;
} }
/** /**
* Executes the request asynchronous. * Executes the request within a thread.
* *
* @param plugin The cordova plugin.
* @param action The action to execute. * @param action The action to execute.
* @param callback The callback context used when * @param callback The callback context used when
* calling back into JavaScript. * calling back into JavaScript.
*/ */
@SuppressWarnings("UnusedParameters") void executeAsync (String action, CallbackContext callback)
static void execute (CordovaPlugin plugin, final String action, {
final CallbackContext callback) { cordova.getThreadPool().execute(() -> execute(action, callback));
final BackgroundExt ext = new BackgroundExt(plugin);
plugin.cordova.getThreadPool().execute(new Runnable() {
@Override
public void run() {
ext.execute(action, callback);
} }
});
}
// codebeat:disable[ABC]
/** /**
* Executes the request. * Executes the request.
@ -100,60 +93,59 @@ class BackgroundExt {
* @param callback The callback context used when * @param callback The callback context used when
* calling back into JavaScript. * calling back into JavaScript.
*/ */
private void execute (String action, CallbackContext callback) { private void execute (String action, CallbackContext callback)
{
if (action.equalsIgnoreCase("optimizations")) { switch (action)
{
case "optimizations":
disableWebViewOptimizations(); disableWebViewOptimizations();
} break;
case "background":
if (action.equalsIgnoreCase("background")) {
moveToBackground(); moveToBackground();
} break;
case "foreground":
if (action.equalsIgnoreCase("foreground")) {
moveToForeground(); moveToForeground();
} break;
case "tasklist":
if (action.equalsIgnoreCase("tasklist")) {
excludeFromTaskList(); excludeFromTaskList();
} break;
case "dimmed":
if (action.equalsIgnoreCase("dimmed")) {
isDimmed(callback); isDimmed(callback);
} break;
case "wakeup":
if (action.equalsIgnoreCase("wakeup")) {
wakeup(); wakeup();
} break;
case "unlock":
if (action.equalsIgnoreCase("unlock")) {
wakeup(); wakeup();
unlock(); unlock();
break;
} }
} }
// codebeat:enable[ABC]
/** /**
* Move app to background. * Moves the app to the background.
*/ */
private void moveToBackground() { private void moveToBackground()
{
Intent intent = new Intent(Intent.ACTION_MAIN); Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME); intent.addCategory(Intent.CATEGORY_HOME);
getApp().startActivity(intent); getApp().startActivity(intent);
} }
/** /**
* Move app to foreground. * Moves the app to the foreground.
*/ */
private void moveToForeground() { private void moveToForeground()
{
Activity app = getApp(); Activity app = getApp();
Intent intent = getLaunchIntent(); Intent intent = getLaunchIntent();
intent.addFlags( intent.addFlags(
Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT |
Intent.FLAG_ACTIVITY_SINGLE_TOP); Intent.FLAG_ACTIVITY_SINGLE_TOP |
Intent.FLAG_ACTIVITY_CLEAR_TOP);
app.startActivity(intent); app.startActivity(intent);
} }
@ -166,10 +158,8 @@ class BackgroundExt {
public void run() { public void run() {
try { try {
Thread.sleep(1000); Thread.sleep(1000);
getApp().runOnUiThread(new Runnable() { getApp().runOnUiThread(() -> {
@Override View view = webView.getEngine().getView();
public void run() {
View view = webView.get().getEngine().getView();
try { try {
Class.forName("org.crosswalk.engine.XWalkCordovaView") Class.forName("org.crosswalk.engine.XWalkCordovaView")
@ -178,7 +168,6 @@ class BackgroundExt {
} catch (Exception e){ } catch (Exception e){
view.dispatchWindowVisibilityChanged(View.VISIBLE); view.dispatchWindowVisibilityChanged(View.VISIBLE);
} }
}
}); });
} catch (InterruptedException e) { } catch (InterruptedException e) {
// do nothing // do nothing
@ -190,13 +179,14 @@ class BackgroundExt {
} }
/** /**
* Exclude the app from the recent tasks list. * Excludes the app from the recent tasks list.
*/ */
@TargetApi(Build.VERSION_CODES.LOLLIPOP) @TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void excludeFromTaskList() { private void excludeFromTaskList()
{
ActivityManager am = (ActivityManager) getService(ACTIVITY_SERVICE); ActivityManager am = (ActivityManager) getService(ACTIVITY_SERVICE);
if (am == null || Build.VERSION.SDK_INT < 21) if (am == null || SDK_INT < 21)
return; return;
List<AppTask> tasks = am.getAppTasks(); List<AppTask> tasks = am.getAppTasks();
@ -208,24 +198,29 @@ class BackgroundExt {
} }
/** /**
* Invoke the callback with information if the screen is on. * Invokes the callback with information if the screen is on.
* *
* @param callback The callback to invoke. * @param callback The callback to invoke.
*/ */
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
private void isDimmed(CallbackContext callback) { private void isDimmed (CallbackContext callback)
PluginResult result = new PluginResult(Status.OK, isDimmed()); {
callback.sendPluginResult(result); boolean status = isDimmed();
PluginResult res = new PluginResult(Status.OK, status);
callback.sendPluginResult(res);
} }
/** /**
* If the screen is active. * Returns if the screen is active.
*/ */
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
private boolean isDimmed() { private boolean isDimmed()
{
PowerManager pm = (PowerManager) getService(POWER_SERVICE); PowerManager pm = (PowerManager) getService(POWER_SERVICE);
if (Build.VERSION.SDK_INT < 20) { if (SDK_INT < 20)
{
return !pm.isScreenOn(); return !pm.isScreenOn();
} }
@ -235,7 +230,8 @@ class BackgroundExt {
/** /**
* Wakes up the device if the screen isn't still on. * Wakes up the device if the screen isn't still on.
*/ */
private void wakeup() { private void wakeup()
{
try { try {
acquireWakeLock(); acquireWakeLock();
} catch (Exception e) { } catch (Exception e) {
@ -246,27 +242,31 @@ class BackgroundExt {
/** /**
* Unlocks the device even with password protection. * Unlocks the device even with password protection.
*/ */
private void unlock() { private void unlock()
Intent intent = getLaunchIntent(); {
getApp().startActivity(intent); getApp().runOnUiThread(() -> {
addSreenAndKeyguardFlags();
getApp().startActivity(getLaunchIntent());
});
} }
/** /**
* Acquire a wake lock to wake up the device. * Acquires a wake lock to wake up the device.
*/ */
private void acquireWakeLock() { @SuppressWarnings("deprecation")
private void acquireWakeLock()
{
PowerManager pm = (PowerManager) getService(POWER_SERVICE); PowerManager pm = (PowerManager) getService(POWER_SERVICE);
releaseWakeLock(); releaseWakeLock();
if (!isDimmed()) { if (!isDimmed())
return; return;
}
int level = PowerManager.SCREEN_DIM_WAKE_LOCK | int level = PowerManager.SCREEN_DIM_WAKE_LOCK |
PowerManager.ACQUIRE_CAUSES_WAKEUP; PowerManager.ACQUIRE_CAUSES_WAKEUP;
wakeLock = pm.newWakeLock(level, "BackgroundModeExt"); wakeLock = pm.newWakeLock(level, "backgroundmode:wakelock");
wakeLock.setReferenceCounted(false); wakeLock.setReferenceCounted(false);
wakeLock.acquire(1000); wakeLock.acquire(1000);
} }
@ -274,7 +274,8 @@ class BackgroundExt {
/** /**
* Releases the previously acquire wake lock. * Releases the previously acquire wake lock.
*/ */
private void releaseWakeLock() { private void releaseWakeLock()
{
if (wakeLock != null && wakeLock.isHeld()) { if (wakeLock != null && wakeLock.isHeld()) {
wakeLock.release(); wakeLock.release();
wakeLock = null; wakeLock = null;
@ -282,36 +283,33 @@ class BackgroundExt {
} }
/** /**
* Add required flags to the window to unlock/wakeup the device. * Adds required flags to the window to unlock/wakeup the device.
*/ */
static void addWindowFlags(Activity app) { private void addSreenAndKeyguardFlags()
final Window window = app.getWindow(); {
getApp().getWindow().addFlags(FLAG_ALLOW_LOCK_WHILE_SCREEN_ON | FLAG_SHOW_WHEN_LOCKED | FLAG_TURN_SCREEN_ON | FLAG_DISMISS_KEYGUARD);
app.runOnUiThread(new Runnable() {
public void run() {
window.addFlags(
FLAG_ALLOW_LOCK_WHILE_SCREEN_ON |
FLAG_SHOW_WHEN_LOCKED |
FLAG_TURN_SCREEN_ON |
FLAG_DISMISS_KEYGUARD
);
}
});
} }
/** /**
* The activity referenced by cordova. * Removes required flags to the window to unlock/wakeup the device.
* */
* @return The main activity of the app. static void clearKeyguardFlags (Activity app)
{
app.runOnUiThread(() -> app.getWindow().clearFlags(FLAG_DISMISS_KEYGUARD));
}
/**
* Returns the activity referenced by cordova.
*/ */
Activity getApp() { Activity getApp() {
return cordova.get().getActivity(); return cordova.getActivity();
} }
/** /**
* The launch intent for the main activity. * Gets the launch intent for the main activity.
*/ */
private Intent getLaunchIntent() { private Intent getLaunchIntent()
{
Context app = getApp().getApplicationContext(); Context app = getApp().getApplicationContext();
String pkgName = app.getPackageName(); String pkgName = app.getPackageName();
@ -322,11 +320,9 @@ class BackgroundExt {
* Get the requested system service by name. * Get the requested system service by name.
* *
* @param name The name of the service. * @param name The name of the service.
*
* @return The service instance.
*/ */
private Object getService(String name) { private Object getService(String name)
{
return getApp().getSystemService(name); return getApp().getSystemService(name);
} }
} }

View File

@ -30,23 +30,20 @@ import android.os.IBinder;
import org.apache.cordova.CallbackContext; import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaPlugin; import org.apache.cordova.CordovaPlugin;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import de.appplant.cordova.plugin.background.ForegroundService.ForegroundBinder; import de.appplant.cordova.plugin.background.ForegroundService.ForegroundBinder;
import static android.content.Context.BIND_AUTO_CREATE; import static android.content.Context.BIND_AUTO_CREATE;
import static de.appplant.cordova.plugin.background.BackgroundExt.clearKeyguardFlags;
public class BackgroundMode extends CordovaPlugin { public class BackgroundMode extends CordovaPlugin {
// Event types for callbacks // Event types for callbacks
private enum Event { private enum Event { ACTIVATE, DEACTIVATE, FAILURE }
ACTIVATE, DEACTIVATE, FAILURE
}
// Plugin namespace // Plugin namespace
private static final String JS_NAMESPACE = private static final String JS_NAMESPACE = "cordova.plugins.backgroundMode";
"cordova.plugins.backgroundMode";
// Flag indicates if the app is in background or foreground // Flag indicates if the app is in background or foreground
private boolean inBackground = false; private boolean inBackground = false;
@ -64,26 +61,22 @@ public class BackgroundMode extends CordovaPlugin {
private ForegroundService service; private ForegroundService service;
// Used to (un)bind the service to with the activity // Used to (un)bind the service to with the activity
private final ServiceConnection connection = new ServiceConnection() { private final ServiceConnection connection = new ServiceConnection()
{
@Override @Override
public void onServiceConnected(ComponentName name, IBinder service) { public void onServiceConnected (ComponentName name, IBinder service)
{
ForegroundBinder binder = (ForegroundBinder) service; ForegroundBinder binder = (ForegroundBinder) service;
BackgroundMode.this.service = binder.getService(); BackgroundMode.this.service = binder.getService();
} }
@Override @Override
public void onServiceDisconnected(ComponentName name) { public void onServiceDisconnected (ComponentName name)
{
fireEvent(Event.FAILURE, "'service disconnected'"); fireEvent(Event.FAILURE, "'service disconnected'");
} }
}; };
@Override
protected void pluginInitialize() {
BackgroundExt.addWindowFlags(cordova.getActivity());
}
// codebeat:disable[ABC]
/** /**
* Executes the request. * Executes the request.
* *
@ -93,47 +86,68 @@ public class BackgroundMode extends CordovaPlugin {
* calling back into JavaScript. * calling back into JavaScript.
* *
* @return Returning false results in a "MethodNotFound" error. * @return Returning false results in a "MethodNotFound" error.
*
* @throws JSONException
*/ */
@Override @Override
public boolean execute (String action, JSONArray args, public boolean execute (String action, JSONArray args,
CallbackContext callback) throws JSONException { CallbackContext callback)
{
boolean validAction = true;
if (action.equalsIgnoreCase("configure")) { switch (action)
configure(args.getJSONObject(0), args.getBoolean(1)); {
callback.success(); case "configure":
return true; configure(args.optJSONObject(0), args.optBoolean(1));
} break;
case "enable":
if (action.equalsIgnoreCase("enable")) {
enableMode(); enableMode();
callback.success(); break;
return true; case "disable":
}
if (action.equalsIgnoreCase("disable")) {
disableMode(); disableMode();
break;
case "optimizations":
case "background":
case "foreground":
case "tasklist":
case "dimmed":
case "wakeup":
case "unlock":
new BackgroundExt(this).executeAsync(action, callback);
break;
default:
validAction = false;
}
if (validAction) {
callback.success(); callback.success();
return true; } else {
callback.error("Invalid action: " + action);
} }
BackgroundExt.execute(this, action, callback); return validAction;
return true;
} }
// codebeat:enable[ABC]
/** /**
* Called when the system is about to start resuming a previous activity. * Called when the system is about to start resuming a previous activity.
* *
* @param multitasking Flag indicating if multitasking is turned on for app. * @param multitasking Flag indicating if multitasking is turned on for app.
*/ */
@Override @Override
public void onPause(boolean multitasking) { public void onPause(boolean multitasking)
super.onPause(multitasking); {
try {
inBackground = true; inBackground = true;
startService(); startService();
} finally {
clearKeyguardFlags(cordova.getActivity());
}
}
/**
* Called when the activity is no longer visible to the user.
*/
@Override
public void onStop () {
clearKeyguardFlags(cordova.getActivity());
} }
/** /**
@ -142,8 +156,8 @@ public class BackgroundMode extends CordovaPlugin {
* @param multitasking Flag indicating if multitasking is turned on for app. * @param multitasking Flag indicating if multitasking is turned on for app.
*/ */
@Override @Override
public void onResume(boolean multitasking) { public void onResume (boolean multitasking)
super.onResume(multitasking); {
inBackground = false; inBackground = false;
stopService(); stopService();
} }
@ -152,16 +166,17 @@ public class BackgroundMode extends CordovaPlugin {
* Called when the activity will be destroyed. * Called when the activity will be destroyed.
*/ */
@Override @Override
public void onDestroy() { public void onDestroy()
{
stopService(); stopService();
super.onDestroy();
android.os.Process.killProcess(android.os.Process.myPid()); android.os.Process.killProcess(android.os.Process.myPid());
} }
/** /**
* Enable the background mode. * Enable the background mode.
*/ */
private void enableMode() { private void enableMode()
{
isDisabled = false; isDisabled = false;
if (inBackground) { if (inBackground) {
@ -172,7 +187,8 @@ public class BackgroundMode extends CordovaPlugin {
/** /**
* Disable the background mode. * Disable the background mode.
*/ */
private void disableMode() { private void disableMode()
{
stopService(); stopService();
isDisabled = true; isDisabled = true;
} }
@ -183,7 +199,8 @@ public class BackgroundMode extends CordovaPlugin {
* @param settings The settings * @param settings The settings
* @param update A truthy value means to update the running service. * @param update A truthy value means to update the running service.
*/ */
private void configure(JSONObject settings, boolean update) { private void configure(JSONObject settings, boolean update)
{
if (update) { if (update) {
updateNotification(settings); updateNotification(settings);
} else { } else {
@ -196,17 +213,15 @@ public class BackgroundMode extends CordovaPlugin {
* *
* @param settings The new default settings * @param settings The new default settings
*/ */
private void setDefaultSettings(JSONObject settings) { private void setDefaultSettings(JSONObject settings)
{
defaultSettings = settings; defaultSettings = settings;
} }
/** /**
* The settings for the new/updated notification. * Returns the settings for the new/updated notification.
*
* @return
* updateSettings if set or default settings
*/ */
protected static JSONObject getSettings() { static JSONObject getSettings () {
return defaultSettings; return defaultSettings;
} }
@ -215,7 +230,8 @@ public class BackgroundMode extends CordovaPlugin {
* *
* @param settings The config settings * @param settings The config settings
*/ */
private void updateNotification(JSONObject settings) { private void updateNotification(JSONObject settings)
{
if (isBind) { if (isBind) {
service.updateNotification(settings); service.updateNotification(settings);
} }
@ -225,7 +241,8 @@ public class BackgroundMode extends CordovaPlugin {
* Bind the activity to a background service and put them into foreground * Bind the activity to a background service and put them into foreground
* state. * state.
*/ */
private void startService() { private void startService()
{
Activity context = cordova.getActivity(); Activity context = cordova.getActivity();
if (isDisabled || isBind) if (isDisabled || isBind)
@ -248,12 +265,12 @@ public class BackgroundMode extends CordovaPlugin {
* Bind the activity to a background service and put them into foreground * Bind the activity to a background service and put them into foreground
* state. * state.
*/ */
private void stopService() { private void stopService()
{
Activity context = cordova.getActivity(); Activity context = cordova.getActivity();
Intent intent = new Intent(context, ForegroundService.class); Intent intent = new Intent(context, ForegroundService.class);
if (!isBind) if (!isBind) return;
return;
fireEvent(Event.DEACTIVATE, null); fireEvent(Event.DEACTIVATE, null);
context.unbindService(connection); context.unbindService(connection);
@ -268,7 +285,8 @@ public class BackgroundMode extends CordovaPlugin {
* @param event The name of the event * @param event The name of the event
* @param params Optional arguments for the event * @param params Optional arguments for the event
*/ */
private void fireEvent (Event event, String params) { private void fireEvent (Event event, String params)
{
String eventName = event.name().toLowerCase(); String eventName = event.name().toLowerCase();
Boolean active = event == Event.ACTIVATE; Boolean active = event == Event.ACTIVATE;
@ -283,12 +301,7 @@ public class BackgroundMode extends CordovaPlugin {
final String js = str; final String js = str;
cordova.getActivity().runOnUiThread(new Runnable() { cordova.getActivity().runOnUiThread(() -> webView.loadUrl("javascript:" + js));
@Override
public void run() {
webView.loadUrl("javascript:" + js);
}
});
} }
} }

View File

@ -1,5 +1,6 @@
/* /*
Copyright 2013-2017 appPlant GmbH Copyright 2013-2017 appPlant GmbH
Licensed to the Apache Software Foundation (ASF) under one Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file or more contributor license agreements. See the NOTICE file
distributed with this work for additional information distributed with this work for additional information
@ -7,7 +8,9 @@
to you under the Apache License, Version 2.0 (the to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance "License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@ -18,6 +21,7 @@
package de.appplant.cordova.plugin.background; package de.appplant.cordova.plugin.background;
import android.annotation.SuppressLint;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.app.Notification; import android.app.Notification;
import android.app.NotificationManager; import android.app.NotificationManager;
@ -58,26 +62,27 @@ public class ForegroundService extends Service {
private static final String NOTIFICATION_ICON = "icon"; private static final String NOTIFICATION_ICON = "icon";
// Binder given to clients // Binder given to clients
private final IBinder mBinder = new ForegroundBinder(); private final IBinder binder = new ForegroundBinder();
// Partial wake lock to prevent the app from going to sleep when locked // Partial wake lock to prevent the app from going to sleep when locked
private PowerManager.WakeLock wakeLock; private PowerManager.WakeLock wakeLock;
private final String CHANNEL_ID = "cordova-plugin-background-mode-id";
/** /**
* Allow clients to call on to the service. * Allow clients to call on to the service.
*/ */
@Override @Override
public IBinder onBind (Intent intent) { public IBinder onBind (Intent intent) {
return mBinder; return binder;
} }
/** /**
* Class used for the client Binder. Because we know this service always * Class used for the client Binder. Because we know this service always
* runs in the same process as its clients, we don't need to deal with IPC. * runs in the same process as its clients, we don't need to deal with IPC.
*/ */
public class ForegroundBinder extends Binder { class ForegroundBinder extends Binder
ForegroundService getService() { {
ForegroundService getService()
{
// Return this instance of ForegroundService // Return this instance of ForegroundService
// so clients can call public methods // so clients can call public methods
return ForegroundService.this; return ForegroundService.this;
@ -89,7 +94,8 @@ public class ForegroundService extends Service {
* by the OS. * by the OS.
*/ */
@Override @Override
public void onCreate () { public void onCreate()
{
super.onCreate(); super.onCreate();
keepAwake(); keepAwake();
} }
@ -98,16 +104,27 @@ public class ForegroundService extends Service {
* No need to run headless on destroy. * No need to run headless on destroy.
*/ */
@Override @Override
public void onDestroy() { public void onDestroy()
{
super.onDestroy(); super.onDestroy();
sleepWell(); sleepWell();
} }
/**
* Prevent Android from stopping the background service automatically.
*/
@Override
public int onStartCommand (Intent intent, int flags, int startId) {
return START_STICKY;
}
/** /**
* Put the service in a foreground state to prevent app from being killed * Put the service in a foreground state to prevent app from being killed
* by the OS. * by the OS.
*/ */
private void keepAwake() { @SuppressLint("WakelockTimeout")
private void keepAwake()
{
JSONObject settings = BackgroundMode.getSettings(); JSONObject settings = BackgroundMode.getSettings();
boolean isSilent = settings.optBoolean("silent", false); boolean isSilent = settings.optBoolean("silent", false);
@ -115,11 +132,10 @@ public class ForegroundService extends Service {
startForeground(NOTIFICATION_ID, makeNotification()); startForeground(NOTIFICATION_ID, makeNotification());
} }
PowerManager pm = (PowerManager) PowerManager pm = (PowerManager)getSystemService(POWER_SERVICE);
getSystemService(POWER_SERVICE);
wakeLock = pm.newWakeLock( wakeLock = pm.newWakeLock(
PARTIAL_WAKE_LOCK, "BackgroundMode"); PARTIAL_WAKE_LOCK, "backgroundmode:wakelock");
wakeLock.acquire(); wakeLock.acquire();
} }
@ -127,7 +143,8 @@ public class ForegroundService extends Service {
/** /**
* Stop background mode. * Stop background mode.
*/ */
private void sleepWell() { private void sleepWell()
{
stopForeground(true); stopForeground(true);
getNotificationManager().cancel(NOTIFICATION_ID); getNotificationManager().cancel(NOTIFICATION_ID);
@ -141,7 +158,8 @@ public class ForegroundService extends Service {
* Create a notification as the visible part to be able to put the service * Create a notification as the visible part to be able to put the service
* in a foreground state by using the default settings. * in a foreground state by using the default settings.
*/ */
private Notification makeNotification() { private Notification makeNotification()
{
return makeNotification(BackgroundMode.getSettings()); return makeNotification(BackgroundMode.getSettings());
} }
@ -151,8 +169,10 @@ public class ForegroundService extends Service {
* *
* @param settings The config settings * @param settings The config settings
*/ */
private Notification makeNotification(JSONObject settings) { private Notification makeNotification (JSONObject settings)
{
// use channelid for Oreo and higher // use channelid for Oreo and higher
String CHANNEL_ID = "cordova-plugin-background-mode-id";
if(Build.VERSION.SDK_INT >= 26){ if(Build.VERSION.SDK_INT >= 26){
// The user-visible name of the channel. // The user-visible name of the channel.
CharSequence name = "cordova-plugin-background-mode"; CharSequence name = "cordova-plugin-background-mode";
@ -161,7 +181,7 @@ public class ForegroundService extends Service {
int importance = NotificationManager.IMPORTANCE_LOW; int importance = NotificationManager.IMPORTANCE_LOW;
NotificationChannel mChannel = new NotificationChannel(this.CHANNEL_ID, name,importance); NotificationChannel mChannel = new NotificationChannel(CHANNEL_ID, name,importance);
// Configure the notification channel. // Configure the notification channel.
mChannel.setDescription(description); mChannel.setDescription(description);
@ -184,7 +204,7 @@ public class ForegroundService extends Service {
.setSmallIcon(getIconResId(settings)); .setSmallIcon(getIconResId(settings));
if(Build.VERSION.SDK_INT >= 26){ if(Build.VERSION.SDK_INT >= 26){
notification.setChannelId(this.CHANNEL_ID); notification.setChannelId(CHANNEL_ID);
} }
if (settings.optBoolean("hidden", true)) { if (settings.optBoolean("hidden", true)) {
@ -216,7 +236,8 @@ public class ForegroundService extends Service {
* *
* @param settings The config settings * @param settings The config settings
*/ */
protected void updateNotification (JSONObject settings) { protected void updateNotification (JSONObject settings)
{
boolean isSilent = settings.optBoolean("silent", false); boolean isSilent = settings.optBoolean("silent", false);
if (isSilent) { if (isSilent) {
@ -234,10 +255,10 @@ public class ForegroundService extends Service {
* *
* @param settings A JSON dict containing the icon name. * @param settings A JSON dict containing the icon name.
*/ */
private int getIconResId(JSONObject settings) { private int getIconResId (JSONObject settings)
{
String icon = settings.optString("icon", NOTIFICATION_ICON); String icon = settings.optString("icon", NOTIFICATION_ICON);
// cordova-android 6 uses mipmaps
int resId = getIconResId(icon, "mipmap"); int resId = getIconResId(icon, "mipmap");
if (resId == 0) { if (resId == 0) {
@ -255,7 +276,8 @@ public class ForegroundService extends Service {
* *
* @return The resource id or 0 if not found. * @return The resource id or 0 if not found.
*/ */
private int getIconResId(String icon, String type) { private int getIconResId (String icon, String type)
{
Resources res = getResources(); Resources res = getResources();
String pkgName = getPackageName(); String pkgName = getPackageName();
@ -275,8 +297,8 @@ public class ForegroundService extends Service {
* @param settings A JSON dict containing the color definition (red: FF0000) * @param settings A JSON dict containing the color definition (red: FF0000)
*/ */
@TargetApi(Build.VERSION_CODES.LOLLIPOP) @TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void setColor(Notification.Builder notification, private void setColor (Notification.Builder notification, JSONObject settings)
JSONObject settings) { {
String hex = settings.optString("color", null); String hex = settings.optString("color", null);
@ -292,9 +314,10 @@ public class ForegroundService extends Service {
} }
/** /**
* Shared manager for the notification service. * Returns the shared notification service manager.
*/ */
private NotificationManager getNotificationManager() { private NotificationManager getNotificationManager()
{
return (NotificationManager) getSystemService(NOTIFICATION_SERVICE); return (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
} }