diff --git a/plugin.xml b/plugin.xml index 20d4978..f95008d 100644 --- a/plugin.xml +++ b/plugin.xml @@ -14,10 +14,12 @@ Sebastián Katzer + + @@ -49,9 +51,37 @@ - + + + + + + + + + + + + + + + + + + diff --git a/src/android/BackgroundMode.java b/src/android/BackgroundMode.java new file mode 100644 index 0000000..1e30390 --- /dev/null +++ b/src/android/BackgroundMode.java @@ -0,0 +1,180 @@ +/* + Copyright 2013-2014 appPlant UG + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +*/ + +package de.appplant.cordova.plugin.background; + +import android.app.Activity; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.IBinder; +import android.util.Log; + +import org.apache.cordova.CallbackContext; +import org.apache.cordova.CordovaPlugin; +import org.json.JSONArray; +import org.json.JSONException; + +public class BackgroundMode extends CordovaPlugin { + + // Flag indicates if the app is in background or foreground + private boolean inBackground = false; + + // Flag indicates if the plugin is enabled or disabled + private boolean isDisabled = false; + + // Used to (un)bind the service to with the activity + private ServiceConnection connection = new ServiceConnection() { + + @Override + public void onServiceConnected(ComponentName name, IBinder binder) { + // Nothing to do here + Log.d("BackgroundMode", "Service connected"); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + Log.w("BackgroundMode", "Service disrupted"); + //stopService(); + } + }; + + /** + * Executes the request. + * + * @param action The action to execute. + * @param args The exec() arguments. + * @param callback The callback context used when + * calling back into JavaScript. + * + * @return + * Returning false results in a "MethodNotFound" error. + * + * @throws JSONException + */ + @Override + public boolean execute (String action, JSONArray args, + CallbackContext callback) throws JSONException { + + if (action.equalsIgnoreCase("observeLifeCycle")) { + // Nothing to do here + return true; + } + + if (action.equalsIgnoreCase("enable")) { + enableMode(); + return true; + } + + if (action.equalsIgnoreCase("disable")) { + disableMode(); + return true; + } + + return false; + } + + /** + * Called when the system is about to start resuming a previous activity. + * + * @param multitasking + * Flag indicating if multitasking is turned on for app + */ + @Override + public void onPause(boolean multitasking) { + super.onPause(multitasking); + inBackground = true; + startService(); + } + + + /** + * Called when the activity will start interacting with the user. + * + * @param multitasking + * Flag indicating if multitasking is turned on for app + */ + @Override + public void onResume(boolean multitasking) { + super.onResume(multitasking); + inBackground = false; + stopService(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + stopService(); + } + + /** + * Enable the background mode. + */ + private void enableMode() { + isDisabled = false; + + if (inBackground) { + startService(); + } + } + + /** + * Disable the background mode. + */ + private void disableMode() { + stopService(); + isDisabled = true; + } + + /** + * Bind the activity to a background service and put them into foreground + * state. + */ + private void startService() { + Activity context = cordova.getActivity(); + + Intent intent = new Intent( + context, ForegroundService.class); + + if (isDisabled) + return; + + context.bindService( + intent, connection, Context.BIND_AUTO_CREATE); + + context.startService(intent); + } + + /** + * Bind the activity to a background service and put them into foreground + * state. + */ + private void stopService() { + Activity context = cordova.getActivity(); + + Intent intent = new Intent( + context, ForegroundService.class); + + context.unbindService(connection); + context.stopService(intent); + } +} diff --git a/src/android/ForegroundService.java b/src/android/ForegroundService.java new file mode 100644 index 0000000..84882eb --- /dev/null +++ b/src/android/ForegroundService.java @@ -0,0 +1,164 @@ +/* + Copyright 2013-2014 appPlant UG + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +*/ + +package de.appplant.cordova.plugin.background; + +import android.app.Notification; +import android.app.PendingIntent; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.os.Build; +import android.os.Handler; +import android.os.IBinder; +import android.util.Log; + +import java.util.Date; +import java.util.Timer; +import java.util.TimerTask; + +/** + * Puts the service in a foreground state, where the system considers it to be + * something the user is actively aware of and thus not a candidate for killing + * when low on memory. + */ +public class ForegroundService extends Service { + + // Fixed ID for the 'foreground' notification + private static final int NOTIFICATION_ID = -574543954; + + // Scheduler to exec periodic tasks + final Timer scheduler = new Timer(); + + // Used to keep the app alive + TimerTask keepAliveTask; + + /** + * Allow clients to call on to the service. + */ + @Override + public IBinder onBind (Intent intent) { + return null; + } + + /** + * Put the service in a foreground state to prevent app from being killed + * by the OS. + */ + @Override + public void onCreate () { + super.onCreate(); + keepAwake(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + sleepWell(); + } + + /** + * Put the service in a foreground state to prevent app from being killed + * by the OS. + */ + public void keepAwake() { + final Handler handler = new Handler(); + + startForeground(NOTIFICATION_ID, makeNotification()); + + keepAliveTask = new TimerTask() { + @Override + public void run() { + handler.post(new Runnable() { + public void run() { + // Nothing to do here + // Log.d("BackgroundMode", "" + new Date().getTime()); + } + }); + } + }; + + scheduler.schedule(keepAliveTask, 0, 1000); + } + + /** + * Stop background mode. + */ + private void sleepWell() { + stopForeground(true); + keepAliveTask.cancel(); + } + + /** + * Create a notification as the visible part to be able to put the service + * in a foreground state. + * + * @return + * A local ongoing notification which pending intent is bound to the + * main activity. + */ + @SuppressWarnings("deprecation") + private Notification makeNotification() { + Context context = getApplicationContext(); + String pkgName = context.getPackageName(); + Intent intent = context.getPackageManager() + .getLaunchIntentForPackage(pkgName); + + PendingIntent contentIntent = PendingIntent.getActivity( + context, NOTIFICATION_ID, intent, PendingIntent.FLAG_CANCEL_CURRENT); + + String title = "App is running in background"; + + Notification.Builder notification = new Notification.Builder(context) + .setContentTitle(title) + .setContentText(title) + .setTicker(title) + .setOngoing(true) + .setSmallIcon(getIconResId()) + .setContentIntent(contentIntent); + + if (Build.VERSION.SDK_INT < 16) { + // Build notification for HoneyComb to ICS + return notification.getNotification(); + } else { + // Notification for Jellybean and above + return notification.build(); + } + } + + /** + * Retrieves the resource ID of the app icon. + * + * @return + * The resource ID of the app icon + */ + private int getIconResId () { + Context context = getApplicationContext(); + Resources res = context.getResources(); + String pkgName = context.getPackageName(); + + int resId; + resId = res.getIdentifier("icon", "drawable", pkgName); + + return resId; + } +} diff --git a/www/background-mode.js b/www/background-mode.js index f4a6aea..dffc403 100644 --- a/www/background-mode.js +++ b/www/background-mode.js @@ -46,4 +46,6 @@ BackgroundMode.prototype = { var plugin = new BackgroundMode(); +document.addEventListener("backbutton", function () {}, false); + module.exports = plugin; \ No newline at end of file