├── appbeep.wav ├── appbeep.wma ├── package.json ├── src ├── ios │ ├── APPBackgroundMode.h │ ├── APPMethodMagic.h │ ├── APPMethodMagic.m │ └── APPBackgroundMode.m ├── browser │ └── BackgroundModeProxy.js └── android │ ├── BackgroundMode.java │ ├── ForegroundService.java │ └── BackgroundModeExt.java ├── .travis.yml ├── CHANGELOG.md ├── plugin.xml ├── README.md ├── LICENSE └── www └── background-mode.js /appbeep.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/katzer/cordova-plugin-background-mode/HEAD/appbeep.wav -------------------------------------------------------------------------------- /appbeep.wma: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/katzer/cordova-plugin-background-mode/HEAD/appbeep.wma -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cordova-plugin-background-mode", 3 | "version": "0.7.3", 4 | "description": "Prevent apps from going to sleep in background.", 5 | "cordova": { 6 | "id": "cordova-plugin-background-mode", 7 | "platforms": [ 8 | "ios", 9 | "android", 10 | "browser" 11 | ] 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/katzer/cordova-plugin-background-mode.git" 16 | }, 17 | "keywords": [ 18 | "appplant", 19 | "background", 20 | "ecosystem:cordova", 21 | "cordova-ios", 22 | "cordova-android", 23 | "cordova-browser" 24 | ], 25 | "engines": [ 26 | { 27 | "name": "cordova", 28 | "version": ">=3.0.0" 29 | }, 30 | { 31 | "name": "android-sdk", 32 | "version": ">=16" 33 | } 34 | ], 35 | "author": "Sebastián Katzer", 36 | "license": "Apache 2.0", 37 | "bugs": { 38 | "url": "https://github.com/katzer/cordova-plugin-background-mode/issues" 39 | }, 40 | "homepage": "https://github.com/katzer/cordova-plugin-background-mode#readme" 41 | } -------------------------------------------------------------------------------- /src/ios/APPBackgroundMode.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013-2017 appPlant GmbH 3 | 4 | Licensed to the Apache Software Foundation (ASF) under one 5 | or more contributor license agreements. See the NOTICE file 6 | distributed with this work for additional information 7 | regarding copyright ownership. The ASF licenses this file 8 | to you under the Apache License, Version 2.0 (the 9 | "License"); you may not use this file except in compliance 10 | with the License. You may obtain a copy of the License at 11 | 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | 14 | Unless required by applicable law or agreed to in writing, 15 | software distributed under the License is distributed on an 16 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | KIND, either express or implied. See the License for the 18 | specific language governing permissions and limitations 19 | under the License. 20 | */ 21 | 22 | #import 23 | #import 24 | 25 | @interface APPBackgroundMode : CDVPlugin { 26 | AVAudioPlayer* audioPlayer; 27 | BOOL enabled; 28 | } 29 | 30 | // Activate the background mode 31 | - (void) enable:(CDVInvokedUrlCommand*)command; 32 | // Deactivate the background mode 33 | - (void) disable:(CDVInvokedUrlCommand*)command; 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /src/browser/BackgroundModeProxy.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Sebastián Katzer 3 | 4 | Licensed to the Apache Software Foundation (ASF) under one 5 | or more contributor license agreements. See the NOTICE file 6 | distributed with this work for additional information 7 | regarding copyright ownership. The ASF licenses this file 8 | to you under the Apache License, Version 2.0 (the 9 | "License"); you may not use this file except in compliance 10 | with the License. You may obtain a copy of the License at 11 | 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | 14 | Unless required by applicable law or agreed to in writing, 15 | software distributed under the License is distributed on an 16 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | KIND, either express or implied. See the License for the 18 | specific language governing permissions and limitations 19 | under the License. 20 | */ 21 | 22 | /** 23 | * Activates the background mode. When activated the application 24 | * will be prevented from going to sleep while in background 25 | * for the next time. 26 | * 27 | * @param [ Function ] success The success callback to use. 28 | * @param [ Function ] error The error callback to use. 29 | * 30 | * @return [ Void ] 31 | */ 32 | exports.enable = function (success, error) { 33 | success(); 34 | }; 35 | 36 | /** 37 | * Deactivates the background mode. When deactivated the application 38 | * will not stay awake while in background. 39 | * 40 | * @param [ Function ] success The success callback to use. 41 | * @param [ Function ] error The error callback to use. 42 | * 43 | * @return [ Void ] 44 | */ 45 | exports.disable = function (success, error) { 46 | success(); 47 | }; 48 | 49 | cordova.commandProxy.add('BackgroundMode', exports); 50 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013-2017 by appPlant GmbH. All rights reserved. 2 | # 3 | # @APPPLANT_LICENSE_HEADER_START@ 4 | # 5 | # This file contains Original Code and/or Modifications of Original Code 6 | # as defined in and that are subject to the Apache License 7 | # Version 2.0 (the 'License'). You may not use this file except in 8 | # compliance with the License. Please obtain a copy of the License at 9 | # http://opensource.org/licenses/Apache-2.0/ and read it before using this 10 | # file. 11 | # 12 | # The Original Code and all software distributed under the License are 13 | # distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 14 | # EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 15 | # INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 17 | # Please see the License for the specific language governing rights and 18 | # limitations under the License. 19 | # 20 | # @APPPLANT_LICENSE_HEADER_END@ 21 | 22 | language: objective-c 23 | osx_image: xcode8.2 24 | 25 | branches: 26 | only: 27 | - master 28 | - /^feature\// 29 | - /^bug\// 30 | 31 | node_js: 32 | - 6 33 | 34 | notifications: 35 | email: false 36 | 37 | matrix: 38 | fast_finish: true 39 | 40 | before_install: 41 | - xcrun simctl delete 79C525D3-2383-4201-AC3A-81810F9F4E03 42 | 43 | install: 44 | - npm install 45 | - npm install -g cordova 46 | - brew install gradle 47 | - brew install android-sdk 48 | - ( sleep 5 && while [ 1 ]; do sleep 1; echo y; done ) | android update sdk -a -u -t "tools,platform-tools,build-tools-25.0.2,android-25,extra-android-m2repository" 49 | 50 | before_script: 51 | - cordova create myApp org.apache.cordova.myApp myApp 52 | - cd myApp 53 | - sed -i -r 's:\(:' config.xml 54 | - cordova platform add android@latest browser@latest ios@latest 55 | - cordova plugin add cordova-plugin-background-mode --searchpath $TRAVIS_BUILD_DIR 56 | - cordova platform ls 57 | - cordova plugin ls 58 | 59 | script: 60 | - cordova build android 61 | - cordova build browser 62 | - cordova build ios 63 | - cordova plugin add cordova-plugin-crosswalk-webview 64 | - cordova plugin add cordova-plugin-wkwebview-engine 65 | - cordova build android 66 | - cordova build ios 67 | -------------------------------------------------------------------------------- /src/ios/APPMethodMagic.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013-2017 appPlant GmbH 3 | 4 | Licensed to the Apache Software Foundation (ASF) under one 5 | or more contributor license agreements. See the NOTICE file 6 | distributed with this work for additional information 7 | regarding copyright ownership. The ASF licenses this file 8 | to you under the Apache License, Version 2.0 (the 9 | "License"); you may not use this file except in compliance 10 | with the License. You may obtain a copy of the License at 11 | 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | 14 | Unless required by applicable law or agreed to in writing, 15 | software distributed under the License is distributed on an 16 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | KIND, either express or implied. See the License for the 18 | specific language governing permissions and limitations 19 | under the License. 20 | */ 21 | 22 | /** 23 | * Code extracted from 24 | * - http://defagos.github.io/yet_another_article_about_method_swizzling/ 25 | * - https://gist.github.com/defagos/1312fec96b48540efa5c 26 | */ 27 | 28 | #import 29 | #import 30 | 31 | #define SwizzleSelector(clazz, selector, newImpl, oldImpl) \ 32 | (*oldImpl) = (__typeof((*oldImpl)))class_swizzleSelector((clazz), (selector), (IMP)(newImpl)) 33 | 34 | #define SwizzleClassSelector(clazz, selector, newImpl, oldImpl) \ 35 | (*oldImpl) = (__typeof((*oldImpl)))class_swizzleClassSelector((clazz), (selector), (IMP)(newImpl)) 36 | 37 | #define SwizzleSelectorWithBlock_Begin(clazz, selector) { \ 38 | SEL _cmd = selector; \ 39 | __block IMP _imp = class_swizzleSelectorWithBlock((clazz), (selector), 40 | #define SwizzleSelectorWithBlock_End );} 41 | 42 | #define SwizzleClassSelectorWithBlock_Begin(clazz, selector) { \ 43 | SEL _cmd = selector; \ 44 | __block IMP _imp = class_swizzleClassSelectorWithBlock((clazz), (selector), 45 | #define SwizzleClassSelectorWithBlock_End );} 46 | 47 | /** 48 | * Swizzle class method specified by class and selector 49 | * through the provided method implementation. 50 | * 51 | * @param [ Class ] clazz The class containing the method. 52 | * @param [ SEL ] selector The selector of the method. 53 | * @param [ IMP ] newImpl The new implementation of the method. 54 | * 55 | * @return [ IMP ] The previous implementation of the method. 56 | */ 57 | IMP class_swizzleClassSelector(Class clazz, SEL selector, IMP newImpl); 58 | 59 | /** 60 | * Swizzle class method specified by class and selector 61 | * through the provided code block. 62 | * 63 | * @param [ Class ] clazz The class containing the method. 64 | * @param [ SEL ] selector The selector of the method. 65 | * @param [ id ] newImplBlock The new implementation of the method. 66 | * 67 | * @return [ IMP ] The previous implementation of the method. 68 | */ 69 | IMP class_swizzleClassSelectorWithBlock(Class clazz, SEL selector, id newImplBlock); 70 | 71 | /** 72 | * Swizzle method specified by class and selector 73 | * through the provided code block. 74 | * 75 | * @param [ Class ] clazz The class containing the method. 76 | * @param [ SEL ] selector The selector of the method. 77 | * @param [ id ] newImplBlock The new implementation of the method. 78 | * 79 | * @return [ IMP ] The previous implementation of the method. 80 | */ 81 | IMP class_swizzleSelectorWithBlock(Class clazz, SEL selector, id newImplBlock); 82 | 83 | /** 84 | * Swizzle method specified by class and selector 85 | * through the provided method implementation. 86 | * 87 | * @param [ Class ] clazz The class containing the method. 88 | * @param [ SEL ] selector The selector of the method. 89 | * @param [ IMP ] newImpl The new implementation of the method. 90 | * 91 | * @return [ IMP ] The previous implementation of the method. 92 | */ 93 | IMP class_swizzleSelector(Class clazz, SEL selector, IMP newImpl); 94 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## ChangeLog 2 | 3 | #### Version 0.7.3 (07.08.2019) 4 | This is more a "just publish all changes after long time" release. 5 | - [___change___:] Removed code for Windows 10 Mobile 6 | - [feature:] Check if screen is off on Android 7 | - [feature:] Wake-up device on Android 8 | - [feature:] Unlock device on Android 9 | - [bugfix:] Plugin not working for Android 8 10 | - [bugfix:] Cannot install plugin on cordova > 9 11 | - [bugfix:] Function `onactivate` does no longer exist 12 | 13 | #### Version 0.7.2 (02.02.2017) 14 | - Fixed app freeze on iOS using wkwebview-engine 15 | - Websocket sample in SampleApp 16 | 17 | #### Version 0.7.1 (30.01.2017) 18 | - Bug fixes for iOS9 and Android 19 | - Allow app to be excluded from recent list on Android 20 | 21 | #### Version 0.7.0 (27.01.2017) 22 | - __Features__ 23 | - Support for tAmazon FireOS 24 | - Support for the browser platform 25 | - Ability to configure icon and color on Android 26 | - Allow app to move to foreground on Android 27 | - Allow app to move to background on Android 28 | - Allow app to override back button behaviour on Android 29 | - New events for when the mode has been enabled/disabled 30 | - __Improvements__ 31 | - Various enhancements and bug fixes for all platforms 32 | - Support for latest platform and OS versions 33 | - Multi line text on Android 34 | - Multiple listeners for same event 35 | - Compatibility with cordova-plugin-geolocation 36 | - Compatibility with cordova-plugin-crosswalk-webview 37 | - Compatibility with cordova-plugin-wkwebview-engine 38 | - New sample app 39 | - __Fixes__ 40 | - Silent mode issues on Android 41 | - Lock screen issues on Android 42 | - Callback not called on Android 43 | - Notification shows app info with cordova-android@6 44 | - Other apps audio interruption on iOS 45 | - __Changes__ 46 | - Deprecate event callbacks 47 | - Notification not visible by default on lock screen 48 | - Remove ticker property on Android 49 | - Remove unexpected back button handler 50 | - Remove support for wp8 platform 51 | 52 | #### Version 0.6.5 (29.02.2016) 53 | - Published on npm 54 | - Updated dependency ID for the device plug-in 55 | 56 | #### Version 0.6.4 (03.03.2015) 57 | - Resolve possibly dependency conflict 58 | 59 | #### Version 0.6.3 (01.01.2015) 60 | - [feature:] Silent mode for Android 61 | 62 | #### Version 0.6.2 (14.12.2014) 63 | - [bugfix:] Type error 64 | - [bugfix:] Wrong default values for `isEnabled` and `isActive`. 65 | 66 | #### Version 0.6.1 (14.12.2014) 67 | - [enhancement:] Set default settings through `setDefaults`. 68 | - [enhancement:] New method `isEnabled` to receive if mode is enabled. 69 | - [enhancement:] New method `isActive` to receive if mode is active. 70 | - [bugfix:] Events caused thread collision. 71 | 72 | 73 | #### Version 0.6.0 (14.12.2014) 74 | - [feature:] Android support 75 | - [feature:] Change Android notification through `configure`. 76 | - [feature:] `onactivate`, `ondeactivate` and `onfailure` callbacks. 77 | - [___change___:] Disabled by default 78 | - [enhancement:] Get default settings through `getDefaults`. 79 | - [enhancement:] iOS does not require user permissions, internet connection and geo location anymore. 80 | 81 | #### Version 0.5.0 (13.02.2014) 82 | - __retired__ 83 | 84 | #### Version 0.4.1 (13.02.2014) 85 | - Release under the Apache 2.0 license. 86 | - [enhancement:] Location tracking is only activated on WP8 if the location service is available. 87 | - [bigfix:] Nullpointer exception on WP8. 88 | 89 | #### Version 0.4.0 (10.10.2013) 90 | - Added WP8 support
91 | The plugin turns the app into an location tracking app *(for the time it runs in the background)*. 92 | 93 | #### Version 0.2.1 (09.10.2013) 94 | - Added js interface to manually enable/disable the background mode. 95 | 96 | #### Version 0.2.0 (08.10.2013) 97 | - Added iOS (>= 5) support
98 | The plugin turns the app into an location tracking app for the time it runs in the background. 99 | -------------------------------------------------------------------------------- /plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | BackgroundMode 9 | 10 | Prevent apps from going to sleep in background. 11 | 12 | https://github.com/katzer/cordova-plugin-background-mode.git 13 | 14 | appplant, background 15 | 16 | Apache 2.0 17 | 18 | Sebastián Katzer 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | audio 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 84 | 85 | 88 | 89 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /src/ios/APPMethodMagic.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013-2017 appPlant GmbH 3 | 4 | Licensed to the Apache Software Foundation (ASF) under one 5 | or more contributor license agreements. See the NOTICE file 6 | distributed with this work for additional information 7 | regarding copyright ownership. The ASF licenses this file 8 | to you under the Apache License, Version 2.0 (the 9 | "License"); you may not use this file except in compliance 10 | with the License. You may obtain a copy of the License at 11 | 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | 14 | Unless required by applicable law or agreed to in writing, 15 | software distributed under the License is distributed on an 16 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | KIND, either express or implied. See the License for the 18 | specific language governing permissions and limitations 19 | under the License. 20 | */ 21 | 22 | /** 23 | * Code extracted from 24 | * - http://defagos.github.io/yet_another_article_about_method_swizzling/ 25 | * - https://gist.github.com/defagos/1312fec96b48540efa5c 26 | */ 27 | 28 | #import "APPMethodMagic.h" 29 | #import 30 | #import 31 | 32 | /** 33 | * Swizzle class method specified by class and selector 34 | * through the provided method implementation. 35 | * 36 | * @param [ Class ] clazz The class containing the method. 37 | * @param [ SEL ] selector The selector of the method. 38 | * @param [ IMP ] newImpl The new implementation of the method. 39 | * 40 | * @return [ IMP ] The previous implementation of the method. 41 | */ 42 | IMP class_swizzleClassSelector(Class clazz, SEL selector, IMP newImpl) 43 | { 44 | return class_swizzleSelector(object_getClass(clazz), selector, newImpl); 45 | } 46 | 47 | /** 48 | * Swizzle class method specified by class and selector 49 | * through the provided code block. 50 | * 51 | * @param [ Class ] clazz The class containing the method. 52 | * @param [ SEL ] selector The selector of the method. 53 | * @param [ id ] newImplBlock The new implementation of the method. 54 | * 55 | * @return [ IMP ] The previous implementation of the method. 56 | */ 57 | IMP class_swizzleClassSelectorWithBlock(Class clazz, SEL selector, id newImplBlock) 58 | { 59 | IMP newImpl = imp_implementationWithBlock(newImplBlock); 60 | return class_swizzleClassSelector(clazz, selector, newImpl); 61 | } 62 | 63 | /** 64 | * Swizzle method specified by class and selector 65 | * through the provided code block. 66 | * 67 | * @param [ Class ] clazz The class containing the method. 68 | * @param [ SEL ] selector The selector of the method. 69 | * @param [ id ] newImplBlock The new implementation of the method. 70 | * 71 | * @return [ IMP ] The previous implementation of the method. 72 | */ 73 | IMP class_swizzleSelectorWithBlock(Class clazz, SEL selector, id newImplBlock) 74 | { 75 | IMP newImpl = imp_implementationWithBlock(newImplBlock); 76 | return class_swizzleSelector(clazz, selector, newImpl); 77 | } 78 | 79 | /** 80 | * Swizzle method specified by class and selector 81 | * through the provided method implementation. 82 | * 83 | * @param [ Class ] clazz The class containing the method. 84 | * @param [ SEL ] selector The selector of the method. 85 | * @param [ IMP ] newImpl The new implementation of the method. 86 | * 87 | * @return [ IMP ] The previous implementation of the method. 88 | */ 89 | IMP class_swizzleSelector(Class clazz, SEL selector, IMP newImpl) 90 | { 91 | Method method = class_getInstanceMethod(clazz, selector); 92 | const char *types = method_getTypeEncoding(method); 93 | 94 | class_addMethod(clazz, selector, imp_implementationWithBlock(^(__unsafe_unretained id self, va_list argp) { 95 | struct objc_super super = { 96 | .receiver = self, 97 | .super_class = class_getSuperclass(clazz) 98 | }; 99 | 100 | id (*objc_msgSendSuper_typed)(struct objc_super*, SEL, va_list) = (void*)&objc_msgSendSuper; 101 | return objc_msgSendSuper_typed(&super, selector, argp); 102 | }), types); 103 | 104 | return class_replaceMethod(clazz, selector, newImpl, types); 105 | } 106 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | SAMPLE APP :point_right: 4 |

5 | 6 | Cordova Background Plugin [![npm version](https://badge.fury.io/js/cordova-plugin-background-mode.svg)](http://badge.fury.io/js/cordova-plugin-background-mode) [![Build Status](https://travis-ci.org/katzer/cordova-plugin-background-mode.svg?branch=master)](https://travis-ci.org/katzer/cordova-plugin-background-mode) [![codebeat badge](https://codebeat.co/badges/49709283-b313-4ced-8630-f520baaec7b5)](https://codebeat.co/projects/github-com-katzer-cordova-plugin-background-mode) 7 | ========================= 8 | 9 | Plugin for the [Cordova][cordova] framework to perform infinite background execution. 10 | 11 | Most mobile operating systems are multitasking capable, but most apps dont need to run while in background and not present for the user. Therefore they pause the app in background mode and resume the app before switching to foreground mode. 12 | The system keeps all network connections open while in background, but does not deliver the data until the app resumes. 13 | 14 | #### Store Compliance 15 | Infinite background tasks are not official supported on most mobile operation systems and thus not compliant with public store vendors. A successful submssion isn't garanteed. 16 | 17 | Use the plugin by your own risk! 18 | 19 | 20 | ## Supported Platforms 21 | - __Android/Amazon FireOS__ 22 | - __Browser__ 23 | - __iOS__ 24 | - __Windows__ _(see #222)_ 25 | 26 | 27 | ## Installation 28 | The plugin can be installed via [Cordova-CLI][CLI] and is publicly available on [NPM][npm]. 29 | 30 | Execute from the projects root folder: 31 | 32 | $ cordova plugin add cordova-plugin-background-mode 33 | 34 | Or install a specific version: 35 | 36 | $ cordova plugin add de.appplant.cordova.plugin.background-mode@VERSION 37 | 38 | Or install the latest head version: 39 | 40 | $ cordova plugin add https://github.com/katzer/cordova-plugin-background-mode.git 41 | 42 | Or install from local source: 43 | 44 | $ cordova plugin add cordova-plugin-background-mode --searchpath 45 | 46 | 47 | ## Usage 48 | The plugin creates the object `cordova.plugins.backgroundMode` and is accessible after the *deviceready* event has been fired. 49 | 50 | ```js 51 | document.addEventListener('deviceready', function () { 52 | // cordova.plugins.backgroundMode is now available 53 | }, false); 54 | ``` 55 | 56 | ### Enable the background mode 57 | The plugin is not enabled by default. Once it has been enabled the mode becomes active if the app moves to background. 58 | 59 | ```js 60 | cordova.plugins.backgroundMode.enable(); 61 | // or 62 | cordova.plugins.backgroundMode.setEnabled(true); 63 | ``` 64 | 65 | To disable the background mode: 66 | ```js 67 | cordova.plugins.backgroundMode.disable(); 68 | // or 69 | cordova.plugins.backgroundMode.setEnabled(false); 70 | ``` 71 | 72 | ### Check if running in background 73 | Once the plugin has been enabled and the app has entered the background, the background mode becomes active. 74 | 75 | ```js 76 | cordova.plugins.backgroundMode.isActive(); // => boolean 77 | ``` 78 | 79 | A non-active mode means that the app is in foreground. 80 | 81 | ### Listen for events 82 | The plugin fires an event each time its status has been changed. These events are `enable`, `disable`, `activate`, `deactivate` and `failure`. 83 | 84 | ```js 85 | cordova.plugins.backgroundMode.on('EVENT', function); 86 | ``` 87 | 88 | To remove an event listeners: 89 | ```js 90 | cordova.plugins.backgroundMode.un('EVENT', function); 91 | ``` 92 | 93 | 94 | ## Android specifics 95 | 96 | ### Transit between application states 97 | Android allows to programmatically move from foreground to background or vice versa. 98 | 99 | ```js 100 | cordova.plugins.backgroundMode.moveToBackground(); 101 | // or 102 | cordova.plugins.backgroundMode.moveToForeground(); 103 | ``` 104 | 105 | ### Back button 106 | Override the back button on Android to go to background instead of closing the app. 107 | 108 | ```js 109 | cordova.plugins.backgroundMode.overrideBackButton(); 110 | ``` 111 | 112 | ### Recent task list 113 | Exclude the app from the recent task list works on Android 5.0+. 114 | 115 | ```js 116 | cordova.plugins.backgroundMode.excludeFromTaskList(); 117 | ``` 118 | 119 | ### Detect screen status 120 | The method works async instead of _isActive()_ or _isEnabled()_. 121 | 122 | ```js 123 | cordova.plugins.backgroundMode.isScreenOff(function(bool) { 124 | ... 125 | }); 126 | ``` 127 | 128 | ### Unlock and wake-up 129 | A wake-up turns on the screen while unlocking moves the app to foreground even the device is locked. 130 | 131 | ```js 132 | // Turn screen on 133 | cordova.plugins.backgroundMode.wakeUp(); 134 | // Turn screen on and show app even locked 135 | cordova.plugins.backgroundMode.unlock(); 136 | ``` 137 | 138 | ### Notification 139 | To indicate that the app is executing tasks in background and being paused would disrupt the user, the plug-in has to create a notification while in background - like a download progress bar. 140 | 141 | #### Override defaults 142 | The title, text and icon for that notification can be customized as below. Also, by default the app will come to foreground when tapping on the notification. That can be changed by setting resume to false. On Android 5.0+, the color option will set the background color of the notification circle. Also on Android 5.0+, setting hidden to false will make the notification visible on lockscreen. 143 | 144 | ```js 145 | cordova.plugins.backgroundMode.setDefaults({ 146 | title: String, 147 | text: String, 148 | icon: 'icon' // this will look for icon.png in platforms/android/res/drawable|mipmap 149 | color: String // hex format like 'F14F4D' 150 | resume: Boolean, 151 | hidden: Boolean, 152 | bigText: Boolean 153 | }) 154 | ``` 155 | 156 | To modify the currently displayed notification 157 | ```js 158 | cordova.plugins.backgroundMode.configure({ ... }); 159 | ``` 160 | 161 | __Note:__ All properties are optional - only override the things you need to. 162 | 163 | #### Run in background without notification 164 | In silent mode the plugin will not display a notification - which is not the default. Be aware that Android recommends adding a notification otherwise the OS may pause the app. 165 | 166 | ```js 167 | cordova.plugins.backgroundMode.setDefaults({ silent: true }); 168 | ``` 169 | 170 | 171 | ## Quirks 172 | 173 | Various APIs like playing media or tracking GPS position in background might not work while in background even the background mode is active. To fix such issues the plugin provides a method to disable most optimizations done by Android/CrossWalk. 174 | 175 | ```js 176 | cordova.plugins.backgroundMode.on('activate', function() { 177 | cordova.plugins.backgroundMode.disableWebViewOptimizations(); 178 | }); 179 | ``` 180 | 181 | __Note:__ Calling the method led to increased resource and power consumption. 182 | 183 | 184 | ## Contributing 185 | 186 | 1. Fork it 187 | 2. Create your feature branch (`git checkout -b my-new-feature`) 188 | 3. Commit your changes (`git commit -am 'Add some feature'`) 189 | 4. Push to the branch (`git push origin my-new-feature`) 190 | 5. Create new Pull Request 191 | 192 | 193 | ## License 194 | 195 | This software is released under the [Apache 2.0 License][apache2_license]. 196 | 197 | Made with :yum: from Leipzig 198 | 199 | ? 2017 [appPlant GmbH][appplant] & [meshfields][meshfields] 200 | 201 | 202 | [cordova]: https://cordova.apache.org 203 | [CLI]: http://cordova.apache.org/docs/en/edge/guide_cli_index.md.html#The%20Command-line%20Interface 204 | [NPM]: ??? 205 | [changelog]: CHANGELOG.md 206 | [apache2_license]: http://opensource.org/licenses/Apache-2.0 207 | [appplant]: http://appplant.de 208 | [meshfields]: http://meshfields.de -------------------------------------------------------------------------------- /src/ios/APPBackgroundMode.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013-2017 appPlant GmbH 3 | 4 | Licensed to the Apache Software Foundation (ASF) under one 5 | or more contributor license agreements. See the NOTICE file 6 | distributed with this work for additional information 7 | regarding copyright ownership. The ASF licenses this file 8 | to you under the Apache License, Version 2.0 (the 9 | "License"); you may not use this file except in compliance 10 | with the License. You may obtain a copy of the License at 11 | 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | 14 | Unless required by applicable law or agreed to in writing, 15 | software distributed under the License is distributed on an 16 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | KIND, either express or implied. See the License for the 18 | specific language governing permissions and limitations 19 | under the License. 20 | */ 21 | 22 | #import "APPMethodMagic.h" 23 | #import "APPBackgroundMode.h" 24 | #import 25 | 26 | @implementation APPBackgroundMode 27 | 28 | #pragma mark - 29 | #pragma mark Constants 30 | 31 | NSString* const kAPPBackgroundJsNamespace = @"cordova.plugins.backgroundMode"; 32 | NSString* const kAPPBackgroundEventActivate = @"activate"; 33 | NSString* const kAPPBackgroundEventDeactivate = @"deactivate"; 34 | 35 | 36 | #pragma mark - 37 | #pragma mark Life Cycle 38 | 39 | /** 40 | * Called by runtime once the Class has been loaded. 41 | * Exchange method implementations to hook into their execution. 42 | */ 43 | + (void) load 44 | { 45 | [self swizzleWKWebViewEngine]; 46 | } 47 | 48 | /** 49 | * Initialize the plugin. 50 | */ 51 | - (void) pluginInitialize 52 | { 53 | enabled = NO; 54 | [self configureAudioPlayer]; 55 | [self configureAudioSession]; 56 | [self observeLifeCycle]; 57 | } 58 | 59 | /** 60 | * Register the listener for pause and resume events. 61 | */ 62 | - (void) observeLifeCycle 63 | { 64 | NSNotificationCenter* listener = [NSNotificationCenter 65 | defaultCenter]; 66 | 67 | [listener addObserver:self 68 | selector:@selector(keepAwake) 69 | name:UIApplicationDidEnterBackgroundNotification 70 | object:nil]; 71 | 72 | [listener addObserver:self 73 | selector:@selector(stopKeepingAwake) 74 | name:UIApplicationWillEnterForegroundNotification 75 | object:nil]; 76 | 77 | [listener addObserver:self 78 | selector:@selector(handleAudioSessionInterruption:) 79 | name:AVAudioSessionInterruptionNotification 80 | object:nil]; 81 | } 82 | 83 | #pragma mark - 84 | #pragma mark Interface 85 | 86 | /** 87 | * Enable the mode to stay awake 88 | * when switching to background for the next time. 89 | */ 90 | - (void) enable:(CDVInvokedUrlCommand*)command 91 | { 92 | if (enabled) 93 | return; 94 | 95 | enabled = YES; 96 | [self execCallback:command]; 97 | } 98 | 99 | /** 100 | * Disable the background mode 101 | * and stop being active in background. 102 | */ 103 | - (void) disable:(CDVInvokedUrlCommand*)command 104 | { 105 | if (!enabled) 106 | return; 107 | 108 | enabled = NO; 109 | [self stopKeepingAwake]; 110 | [self execCallback:command]; 111 | } 112 | 113 | #pragma mark - 114 | #pragma mark Core 115 | 116 | /** 117 | * Keep the app awake. 118 | */ 119 | - (void) keepAwake 120 | { 121 | if (!enabled) 122 | return; 123 | 124 | [audioPlayer play]; 125 | [self fireEvent:kAPPBackgroundEventActivate]; 126 | } 127 | 128 | /** 129 | * Let the app going to sleep. 130 | */ 131 | - (void) stopKeepingAwake 132 | { 133 | if (TARGET_IPHONE_SIMULATOR) { 134 | NSLog(@"BackgroundMode: On simulator apps never pause in background!"); 135 | } 136 | 137 | if (audioPlayer.isPlaying) { 138 | [self fireEvent:kAPPBackgroundEventDeactivate]; 139 | } 140 | 141 | [audioPlayer pause]; 142 | } 143 | 144 | /** 145 | * Configure the audio player. 146 | */ 147 | - (void) configureAudioPlayer 148 | { 149 | NSString* path = [[NSBundle mainBundle] 150 | pathForResource:@"appbeep" ofType:@"wav"]; 151 | 152 | NSURL* url = [NSURL fileURLWithPath:path]; 153 | 154 | 155 | audioPlayer = [[AVAudioPlayer alloc] 156 | initWithContentsOfURL:url error:NULL]; 157 | 158 | audioPlayer.volume = 0; 159 | audioPlayer.numberOfLoops = -1; 160 | }; 161 | 162 | /** 163 | * Configure the audio session. 164 | */ 165 | - (void) configureAudioSession 166 | { 167 | AVAudioSession* session = [AVAudioSession 168 | sharedInstance]; 169 | 170 | // Don't activate the audio session yet 171 | [session setActive:NO error:NULL]; 172 | 173 | // Play music even in background and dont stop playing music 174 | // even another app starts playing sound 175 | [session setCategory:AVAudioSessionCategoryPlayback 176 | error:NULL]; 177 | 178 | // Active the audio session 179 | [session setActive:YES error:NULL]; 180 | }; 181 | 182 | #pragma mark - 183 | #pragma mark Helper 184 | 185 | /** 186 | * Simply invokes the callback without any parameter. 187 | */ 188 | - (void) execCallback:(CDVInvokedUrlCommand*)command 189 | { 190 | CDVPluginResult *result = [CDVPluginResult 191 | resultWithStatus:CDVCommandStatus_OK]; 192 | 193 | [self.commandDelegate sendPluginResult:result 194 | callbackId:command.callbackId]; 195 | } 196 | 197 | /** 198 | * Restart playing sound when interrupted by phone calls. 199 | */ 200 | - (void) handleAudioSessionInterruption:(NSNotification*)notification 201 | { 202 | [self fireEvent:kAPPBackgroundEventDeactivate]; 203 | [self keepAwake]; 204 | } 205 | 206 | /** 207 | * Find out if the app runs inside the webkit powered webview. 208 | */ 209 | + (BOOL) isRunningWebKit 210 | { 211 | return IsAtLeastiOSVersion(@"8.0") && NSClassFromString(@"CDVWKWebViewEngine"); 212 | } 213 | 214 | /** 215 | * Method to fire an event with some parameters in the browser. 216 | */ 217 | - (void) fireEvent:(NSString*)event 218 | { 219 | NSString* active = 220 | [event isEqualToString:kAPPBackgroundEventActivate] ? @"true" : @"false"; 221 | 222 | NSString* flag = [NSString stringWithFormat:@"%@._isActive=%@;", 223 | kAPPBackgroundJsNamespace, active]; 224 | 225 | NSString* depFn = [NSString stringWithFormat:@"%@.on('%@');", 226 | kAPPBackgroundJsNamespace, event]; 227 | 228 | NSString* fn = [NSString stringWithFormat:@"%@.fireEvent('%@');", 229 | kAPPBackgroundJsNamespace, event]; 230 | 231 | NSString* js = [NSString stringWithFormat:@"%@%@%@", flag, depFn, fn]; 232 | 233 | [self.commandDelegate evalJs:js]; 234 | } 235 | 236 | #pragma mark - 237 | #pragma mark Swizzling 238 | 239 | /** 240 | * Method to swizzle. 241 | */ 242 | + (NSString*) wkProperty 243 | { 244 | NSString* str = @"YWx3YXlzUnVuc0F0Rm9yZWdyb3VuZFByaW9yaXR5"; 245 | NSData* data = [[NSData alloc] initWithBase64EncodedString:str options:0]; 246 | 247 | return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 248 | } 249 | 250 | /** 251 | * Swizzle some implementations of CDVWKWebViewEngine. 252 | */ 253 | + (void) swizzleWKWebViewEngine 254 | { 255 | if (![self isRunningWebKit]) 256 | return; 257 | 258 | Class wkWebViewEngineCls = NSClassFromString(@"CDVWKWebViewEngine"); 259 | SEL selector = NSSelectorFromString(@"createConfigurationFromSettings:"); 260 | 261 | SwizzleSelectorWithBlock_Begin(wkWebViewEngineCls, selector) 262 | ^(CDVPlugin *self, NSDictionary *settings) { 263 | id obj = ((id (*)(id, SEL, NSDictionary*))_imp)(self, _cmd, settings); 264 | 265 | [obj setValue:[NSNumber numberWithBool:YES] 266 | forKey:[APPBackgroundMode wkProperty]]; 267 | 268 | [obj setValue:[NSNumber numberWithBool:NO] 269 | forKey:@"requiresUserActionForMediaPlayback"]; 270 | 271 | return obj; 272 | } 273 | SwizzleSelectorWithBlock_End; 274 | } 275 | 276 | @end 277 | -------------------------------------------------------------------------------- /src/android/BackgroundMode.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Sebastián Katzer 3 | 4 | Licensed to the Apache Software Foundation (ASF) under one 5 | or more contributor license agreements. See the NOTICE file 6 | distributed with this work for additional information 7 | regarding copyright ownership. The ASF licenses this file 8 | to you under the Apache License, Version 2.0 (the 9 | "License"); you may not use this file except in compliance 10 | with the License. You may obtain a copy of the License at 11 | 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | 14 | Unless required by applicable law or agreed to in writing, 15 | software distributed under the License is distributed on an 16 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | KIND, either express or implied. See the License for the 18 | specific language governing permissions and limitations 19 | under the License. 20 | */ 21 | 22 | package de.appplant.cordova.plugin.background; 23 | 24 | import android.app.Activity; 25 | import android.content.ComponentName; 26 | import android.content.Intent; 27 | import android.content.ServiceConnection; 28 | import android.os.IBinder; 29 | 30 | import org.apache.cordova.CallbackContext; 31 | import org.apache.cordova.CordovaPlugin; 32 | import org.json.JSONArray; 33 | import org.json.JSONObject; 34 | 35 | import de.appplant.cordova.plugin.background.ForegroundService.ForegroundBinder; 36 | 37 | import static android.content.Context.BIND_AUTO_CREATE; 38 | import static de.appplant.cordova.plugin.background.BackgroundModeExt.clearKeyguardFlags; 39 | 40 | public class BackgroundMode extends CordovaPlugin { 41 | 42 | // Event types for callbacks 43 | private enum Event { ACTIVATE, DEACTIVATE, FAILURE } 44 | 45 | // Plugin namespace 46 | private static final String JS_NAMESPACE = "cordova.plugins.backgroundMode"; 47 | 48 | // Flag indicates if the app is in background or foreground 49 | private boolean inBackground = false; 50 | 51 | // Flag indicates if the plugin is enabled or disabled 52 | private boolean isDisabled = true; 53 | 54 | // Flag indicates if the service is bind 55 | private boolean isBind = false; 56 | 57 | // Default settings for the notification 58 | private static JSONObject defaultSettings = new JSONObject(); 59 | 60 | // Service that keeps the app awake 61 | private ForegroundService service; 62 | 63 | // Used to (un)bind the service to with the activity 64 | private final ServiceConnection connection = new ServiceConnection() 65 | { 66 | @Override 67 | public void onServiceConnected (ComponentName name, IBinder service) 68 | { 69 | ForegroundBinder binder = (ForegroundBinder) service; 70 | BackgroundMode.this.service = binder.getService(); 71 | } 72 | 73 | @Override 74 | public void onServiceDisconnected (ComponentName name) 75 | { 76 | fireEvent(Event.FAILURE, "'service disconnected'"); 77 | } 78 | }; 79 | 80 | /** 81 | * Executes the request. 82 | * 83 | * @param action The action to execute. 84 | * @param args The exec() arguments. 85 | * @param callback The callback context used when 86 | * calling back into JavaScript. 87 | * 88 | * @return Returning false results in a "MethodNotFound" error. 89 | */ 90 | @Override 91 | public boolean execute (String action, JSONArray args, 92 | CallbackContext callback) 93 | { 94 | boolean validAction = true; 95 | 96 | switch (action) 97 | { 98 | case "configure": 99 | configure(args.optJSONObject(0), args.optBoolean(1)); 100 | break; 101 | case "enable": 102 | enableMode(); 103 | break; 104 | case "disable": 105 | disableMode(); 106 | break; 107 | default: 108 | validAction = false; 109 | } 110 | 111 | if (validAction) { 112 | callback.success(); 113 | } else { 114 | callback.error("Invalid action: " + action); 115 | } 116 | 117 | return validAction; 118 | } 119 | 120 | /** 121 | * Called when the system is about to start resuming a previous activity. 122 | * 123 | * @param multitasking Flag indicating if multitasking is turned on for app. 124 | */ 125 | @Override 126 | public void onPause(boolean multitasking) 127 | { 128 | try { 129 | inBackground = true; 130 | startService(); 131 | } finally { 132 | clearKeyguardFlags(cordova.getActivity()); 133 | } 134 | } 135 | 136 | /** 137 | * Called when the activity is no longer visible to the user. 138 | */ 139 | @Override 140 | public void onStop () { 141 | clearKeyguardFlags(cordova.getActivity()); 142 | } 143 | 144 | /** 145 | * Called when the activity will start interacting with the user. 146 | * 147 | * @param multitasking Flag indicating if multitasking is turned on for app. 148 | */ 149 | @Override 150 | public void onResume (boolean multitasking) 151 | { 152 | inBackground = false; 153 | stopService(); 154 | } 155 | 156 | /** 157 | * Called when the activity will be destroyed. 158 | */ 159 | @Override 160 | public void onDestroy() 161 | { 162 | stopService(); 163 | android.os.Process.killProcess(android.os.Process.myPid()); 164 | } 165 | 166 | /** 167 | * Enable the background mode. 168 | */ 169 | private void enableMode() 170 | { 171 | isDisabled = false; 172 | 173 | if (inBackground) { 174 | startService(); 175 | } 176 | } 177 | 178 | /** 179 | * Disable the background mode. 180 | */ 181 | private void disableMode() 182 | { 183 | stopService(); 184 | isDisabled = true; 185 | } 186 | 187 | /** 188 | * Update the default settings and configure the notification. 189 | * 190 | * @param settings The settings 191 | * @param update A truthy value means to update the running service. 192 | */ 193 | private void configure(JSONObject settings, boolean update) 194 | { 195 | if (update) { 196 | updateNotification(settings); 197 | } else { 198 | setDefaultSettings(settings); 199 | } 200 | } 201 | 202 | /** 203 | * Update the default settings for the notification. 204 | * 205 | * @param settings The new default settings 206 | */ 207 | private void setDefaultSettings(JSONObject settings) 208 | { 209 | defaultSettings = settings; 210 | } 211 | 212 | /** 213 | * Returns the settings for the new/updated notification. 214 | */ 215 | static JSONObject getSettings () { 216 | return defaultSettings; 217 | } 218 | 219 | /** 220 | * Update the notification. 221 | * 222 | * @param settings The config settings 223 | */ 224 | private void updateNotification(JSONObject settings) 225 | { 226 | if (isBind) { 227 | service.updateNotification(settings); 228 | } 229 | } 230 | 231 | /** 232 | * Bind the activity to a background service and put them into foreground 233 | * state. 234 | */ 235 | private void startService() 236 | { 237 | Activity context = cordova.getActivity(); 238 | 239 | if (isDisabled || isBind) 240 | return; 241 | 242 | Intent intent = new Intent(context, ForegroundService.class); 243 | 244 | try { 245 | context.bindService(intent, connection, BIND_AUTO_CREATE); 246 | fireEvent(Event.ACTIVATE, null); 247 | context.startService(intent); 248 | } catch (Exception e) { 249 | fireEvent(Event.FAILURE, String.format("'%s'", e.getMessage())); 250 | } 251 | 252 | isBind = true; 253 | } 254 | 255 | /** 256 | * Bind the activity to a background service and put them into foreground 257 | * state. 258 | */ 259 | private void stopService() 260 | { 261 | Activity context = cordova.getActivity(); 262 | Intent intent = new Intent(context, ForegroundService.class); 263 | 264 | if (!isBind) return; 265 | 266 | fireEvent(Event.DEACTIVATE, null); 267 | context.unbindService(connection); 268 | context.stopService(intent); 269 | 270 | isBind = false; 271 | } 272 | 273 | /** 274 | * Fire vent with some parameters inside the web view. 275 | * 276 | * @param event The name of the event 277 | * @param params Optional arguments for the event 278 | */ 279 | private void fireEvent (Event event, String params) 280 | { 281 | String eventName = event.name().toLowerCase(); 282 | Boolean active = event == Event.ACTIVATE; 283 | 284 | String str = String.format("%s._setActive(%b)", 285 | JS_NAMESPACE, active); 286 | 287 | str = String.format("%s;%s.on('%s', %s)", 288 | str, JS_NAMESPACE, eventName, params); 289 | 290 | str = String.format("%s;%s.fireEvent('%s',%s);", 291 | str, JS_NAMESPACE, eventName, params); 292 | 293 | final String js = str; 294 | 295 | cordova.getActivity().runOnUiThread(() -> webView.loadUrl("javascript:" + js)); 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /src/android/ForegroundService.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Sebastián Katzer 3 | 4 | Licensed to the Apache Software Foundation (ASF) under one 5 | or more contributor license agreements. See the NOTICE file 6 | distributed with this work for additional information 7 | regarding copyright ownership. The ASF licenses this file 8 | to you under the Apache License, Version 2.0 (the 9 | "License"); you may not use this file except in compliance 10 | with the License. You may obtain a copy of the License at 11 | 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | 14 | Unless required by applicable law or agreed to in writing, 15 | software distributed under the License is distributed on an 16 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | KIND, either express or implied. See the License for the 18 | specific language governing permissions and limitations 19 | under the License. 20 | */ 21 | 22 | package de.appplant.cordova.plugin.background; 23 | 24 | import android.annotation.SuppressLint; 25 | import android.annotation.TargetApi; 26 | import android.app.Notification; 27 | import android.app.NotificationManager; 28 | import android.app.PendingIntent; 29 | import android.app.Service; 30 | import android.content.Context; 31 | import android.content.Intent; 32 | import android.content.res.Resources; 33 | import android.os.Binder; 34 | import android.os.Build; 35 | import android.os.IBinder; 36 | import android.os.PowerManager; 37 | import android.app.NotificationChannel; 38 | 39 | import org.json.JSONObject; 40 | 41 | import static android.os.PowerManager.PARTIAL_WAKE_LOCK; 42 | 43 | /** 44 | * Puts the service in a foreground state, where the system considers it to be 45 | * something the user is actively aware of and thus not a candidate for killing 46 | * when low on memory. 47 | */ 48 | public class ForegroundService extends Service { 49 | 50 | // Fixed ID for the 'foreground' notification 51 | public static final int NOTIFICATION_ID = -574543954; 52 | 53 | // Default title of the background notification 54 | private static final String NOTIFICATION_TITLE = 55 | "App is running in background"; 56 | 57 | // Default text of the background notification 58 | private static final String NOTIFICATION_TEXT = 59 | "Doing heavy tasks."; 60 | 61 | // Default icon of the background notification 62 | private static final String NOTIFICATION_ICON = "icon"; 63 | 64 | // Binder given to clients 65 | private final IBinder binder = new ForegroundBinder(); 66 | 67 | // Partial wake lock to prevent the app from going to sleep when locked 68 | private PowerManager.WakeLock wakeLock; 69 | 70 | /** 71 | * Allow clients to call on to the service. 72 | */ 73 | @Override 74 | public IBinder onBind (Intent intent) { 75 | return binder; 76 | } 77 | 78 | /** 79 | * Class used for the client Binder. Because we know this service always 80 | * runs in the same process as its clients, we don't need to deal with IPC. 81 | */ 82 | class ForegroundBinder extends Binder 83 | { 84 | ForegroundService getService() 85 | { 86 | // Return this instance of ForegroundService 87 | // so clients can call public methods 88 | return ForegroundService.this; 89 | } 90 | } 91 | 92 | /** 93 | * Put the service in a foreground state to prevent app from being killed 94 | * by the OS. 95 | */ 96 | @Override 97 | public void onCreate() 98 | { 99 | super.onCreate(); 100 | keepAwake(); 101 | } 102 | 103 | /** 104 | * No need to run headless on destroy. 105 | */ 106 | @Override 107 | public void onDestroy() 108 | { 109 | super.onDestroy(); 110 | sleepWell(); 111 | } 112 | 113 | /** 114 | * Prevent Android from stopping the background service automatically. 115 | */ 116 | @Override 117 | public int onStartCommand (Intent intent, int flags, int startId) { 118 | return START_STICKY; 119 | } 120 | 121 | /** 122 | * Put the service in a foreground state to prevent app from being killed 123 | * by the OS. 124 | */ 125 | @SuppressLint("WakelockTimeout") 126 | private void keepAwake() 127 | { 128 | JSONObject settings = BackgroundMode.getSettings(); 129 | boolean isSilent = settings.optBoolean("silent", false); 130 | 131 | if (!isSilent) { 132 | startForeground(NOTIFICATION_ID, makeNotification()); 133 | } 134 | 135 | PowerManager pm = (PowerManager)getSystemService(POWER_SERVICE); 136 | 137 | wakeLock = pm.newWakeLock( 138 | PARTIAL_WAKE_LOCK, "backgroundmode:wakelock"); 139 | 140 | wakeLock.acquire(); 141 | } 142 | 143 | /** 144 | * Stop background mode. 145 | */ 146 | private void sleepWell() 147 | { 148 | stopForeground(true); 149 | getNotificationManager().cancel(NOTIFICATION_ID); 150 | 151 | if (wakeLock != null) { 152 | wakeLock.release(); 153 | wakeLock = null; 154 | } 155 | } 156 | 157 | /** 158 | * Create a notification as the visible part to be able to put the service 159 | * in a foreground state by using the default settings. 160 | */ 161 | private Notification makeNotification() 162 | { 163 | return makeNotification(BackgroundMode.getSettings()); 164 | } 165 | 166 | /** 167 | * Create a notification as the visible part to be able to put the service 168 | * in a foreground state. 169 | * 170 | * @param settings The config settings 171 | */ 172 | private Notification makeNotification (JSONObject settings) 173 | { 174 | // use channelid for Oreo and higher 175 | String CHANNEL_ID = "cordova-plugin-background-mode-id"; 176 | if(Build.VERSION.SDK_INT >= 26){ 177 | // The user-visible name of the channel. 178 | CharSequence name = "cordova-plugin-background-mode"; 179 | // The user-visible description of the channel. 180 | String description = "cordova-plugin-background-moden notification"; 181 | 182 | int importance = NotificationManager.IMPORTANCE_LOW; 183 | 184 | NotificationChannel mChannel = new NotificationChannel(CHANNEL_ID, name,importance); 185 | 186 | // Configure the notification channel. 187 | mChannel.setDescription(description); 188 | 189 | getNotificationManager().createNotificationChannel(mChannel); 190 | } 191 | String title = settings.optString("title", NOTIFICATION_TITLE); 192 | String text = settings.optString("text", NOTIFICATION_TEXT); 193 | boolean bigText = settings.optBoolean("bigText", false); 194 | 195 | Context context = getApplicationContext(); 196 | String pkgName = context.getPackageName(); 197 | Intent intent = context.getPackageManager() 198 | .getLaunchIntentForPackage(pkgName); 199 | 200 | Notification.Builder notification = new Notification.Builder(context) 201 | .setContentTitle(title) 202 | .setContentText(text) 203 | .setOngoing(true) 204 | .setSmallIcon(getIconResId(settings)); 205 | 206 | if(Build.VERSION.SDK_INT >= 26){ 207 | notification.setChannelId(CHANNEL_ID); 208 | } 209 | 210 | if (settings.optBoolean("hidden", true)) { 211 | notification.setPriority(Notification.PRIORITY_MIN); 212 | } 213 | 214 | if (bigText || text.contains("\n")) { 215 | notification.setStyle( 216 | new Notification.BigTextStyle().bigText(text)); 217 | } 218 | 219 | setColor(notification, settings); 220 | 221 | if (intent != null && settings.optBoolean("resume")) { 222 | int flags = PendingIntent.FLAG_UPDATE_CURRENT; 223 | 224 | if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { 225 | flags = flags | PendingIntent.FLAG_MUTABLE; 226 | } 227 | 228 | intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); 229 | PendingIntent contentIntent = PendingIntent.getActivity( 230 | context, NOTIFICATION_ID, intent, flags); 231 | 232 | 233 | notification.setContentIntent(contentIntent); 234 | } 235 | 236 | return notification.build(); 237 | } 238 | 239 | /** 240 | * Update the notification. 241 | * 242 | * @param settings The config settings 243 | */ 244 | protected void updateNotification (JSONObject settings) 245 | { 246 | boolean isSilent = settings.optBoolean("silent", false); 247 | 248 | if (isSilent) { 249 | stopForeground(true); 250 | return; 251 | } 252 | 253 | Notification notification = makeNotification(settings); 254 | getNotificationManager().notify(NOTIFICATION_ID, notification); 255 | 256 | } 257 | 258 | /** 259 | * Retrieves the resource ID of the app icon. 260 | * 261 | * @param settings A JSON dict containing the icon name. 262 | */ 263 | private int getIconResId (JSONObject settings) 264 | { 265 | String icon = settings.optString("icon", NOTIFICATION_ICON); 266 | 267 | int resId = getIconResId(icon, "mipmap"); 268 | 269 | if (resId == 0) { 270 | resId = getIconResId(icon, "drawable"); 271 | } 272 | 273 | return resId; 274 | } 275 | 276 | /** 277 | * Retrieve resource id of the specified icon. 278 | * 279 | * @param icon The name of the icon. 280 | * @param type The resource type where to look for. 281 | * 282 | * @return The resource id or 0 if not found. 283 | */ 284 | private int getIconResId (String icon, String type) 285 | { 286 | Resources res = getResources(); 287 | String pkgName = getPackageName(); 288 | 289 | int resId = res.getIdentifier(icon, type, pkgName); 290 | 291 | if (resId == 0) { 292 | resId = res.getIdentifier("icon", type, pkgName); 293 | } 294 | 295 | return resId; 296 | } 297 | 298 | /** 299 | * Set notification color if its supported by the SDK. 300 | * 301 | * @param notification A Notification.Builder instance 302 | * @param settings A JSON dict containing the color definition (red: FF0000) 303 | */ 304 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 305 | private void setColor (Notification.Builder notification, JSONObject settings) 306 | { 307 | 308 | String hex = settings.optString("color", null); 309 | 310 | if (Build.VERSION.SDK_INT < 21 || hex == null) 311 | return; 312 | 313 | try { 314 | int aRGB = Integer.parseInt(hex, 16) + 0xFF000000; 315 | notification.setColor(aRGB); 316 | } catch (Exception e) { 317 | e.printStackTrace(); 318 | } 319 | } 320 | 321 | /** 322 | * Returns the shared notification service manager. 323 | */ 324 | private NotificationManager getNotificationManager() 325 | { 326 | return (NotificationManager) getSystemService(NOTIFICATION_SERVICE); 327 | } 328 | } 329 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2013 appPlant GmbH 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /www/background-mode.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013-2017 appPlant GmbH 3 | 4 | Licensed to the Apache Software Foundation (ASF) under one 5 | or more contributor license agreements. See the NOTICE file 6 | distributed with this work for additional information 7 | regarding copyright ownership. The ASF licenses this file 8 | to you under the Apache License, Version 2.0 (the 9 | "License"); you may not use this file except in compliance 10 | with the License. You may obtain a copy of the License at 11 | 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | 14 | Unless required by applicable law or agreed to in writing, 15 | software distributed under the License is distributed on an 16 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | KIND, either express or implied. See the License for the 18 | specific language governing permissions and limitations 19 | under the License. 20 | */ 21 | 22 | var exec = require('cordova/exec'), 23 | channel = require('cordova/channel'); 24 | 25 | /** 26 | * Activates the background mode. When activated the application 27 | * will be prevented from going to sleep while in background 28 | * for the next time. 29 | * 30 | * @return [ Void ] 31 | */ 32 | exports.enable = function() 33 | { 34 | if (this.isEnabled()) 35 | return; 36 | 37 | var fn = function() { 38 | exports._isEnabled = true; 39 | exports.fireEvent('enable'); 40 | }; 41 | 42 | cordova.exec(fn, null, 'BackgroundMode', 'enable', []); 43 | }; 44 | 45 | /** 46 | * Deactivates the background mode. When deactivated the application 47 | * will not stay awake while in background. 48 | * 49 | * @return [ Void ] 50 | */ 51 | exports.disable = function() 52 | { 53 | if (!this.isEnabled()) 54 | return; 55 | 56 | var fn = function() { 57 | exports._isEnabled = false; 58 | exports.fireEvent('disable'); 59 | }; 60 | 61 | cordova.exec(fn, null, 'BackgroundMode', 'disable', []); 62 | }; 63 | 64 | /** 65 | * Enable or disable the background mode. 66 | * 67 | * @param [ Bool ] enable The status to set for. 68 | * 69 | * @return [ Void ] 70 | */ 71 | exports.setEnabled = function (enable) 72 | { 73 | if (enable) { 74 | this.enable(); 75 | } else { 76 | this.disable(); 77 | } 78 | }; 79 | 80 | /** 81 | * List of all available options with their default value. 82 | * 83 | * @return [ Object ] 84 | */ 85 | exports.getDefaults = function() 86 | { 87 | return this._defaults; 88 | }; 89 | 90 | /** 91 | * The actual applied settings. 92 | * 93 | * @return [ Object ] 94 | */ 95 | exports.getSettings = function() 96 | { 97 | return this._settings || {}; 98 | }; 99 | 100 | /** 101 | * Overwrite the default settings. 102 | * 103 | * @param [ Object ] overrides Dict of options to be overridden. 104 | * 105 | * @return [ Void ] 106 | */ 107 | exports.setDefaults = function (overrides) 108 | { 109 | var defaults = this.getDefaults(); 110 | 111 | for (var key in defaults) 112 | { 113 | if (overrides.hasOwnProperty(key)) 114 | { 115 | defaults[key] = overrides[key]; 116 | } 117 | } 118 | 119 | if (this._isAndroid) 120 | { 121 | cordova.exec(null, null, 'BackgroundMode', 'configure', [defaults, false]); 122 | } 123 | }; 124 | 125 | /** 126 | * Configures the notification settings for Android. 127 | * Will be merged with the defaults. 128 | * 129 | * @param [ Object ] options Dict of options to be overridden. 130 | * 131 | * @return [ Void ] 132 | */ 133 | exports.configure = function (options) 134 | { 135 | var settings = this.getSettings(), 136 | defaults = this.getDefaults(); 137 | 138 | if (!this._isAndroid) 139 | return; 140 | 141 | if (!this._isActive) 142 | { 143 | console.log('BackgroundMode is not active, skipped...'); 144 | return; 145 | } 146 | 147 | this._mergeObjects(options, settings); 148 | this._mergeObjects(options, defaults); 149 | this._settings = options; 150 | 151 | cordova.exec(null, null, 'BackgroundMode', 'configure', [options, true]); 152 | }; 153 | 154 | /** 155 | * Enable GPS-tracking in background (Android). 156 | * 157 | * @return [ Void ] 158 | */ 159 | exports.disableWebViewOptimizations = function() 160 | { 161 | if (this._isAndroid) 162 | { 163 | cordova.exec(null, null, 'BackgroundModeExt', 'webview', []); 164 | } 165 | }; 166 | 167 | /** 168 | * Disables battery optimazation mode for the app. 169 | * 170 | * @return [ Void ] 171 | */ 172 | exports.disableBatteryOptimizations = function() 173 | { 174 | if (this._isAndroid) 175 | { 176 | cordova.exec(null, null, 'BackgroundModeExt', 'battery', []); 177 | } 178 | }; 179 | 180 | /** 181 | * Opens the system settings dialog where the user can tweak or turn off any 182 | * custom app start settings added by the manufacturer if available. 183 | * 184 | * @param [ Object|Bool ] options Set to false if you dont want to display an 185 | * alert dialog first. 186 | * 187 | * @return [ Void ] 188 | */ 189 | exports.openAppStartSettings = function (options) 190 | { 191 | if (this._isAndroid) 192 | { 193 | cordova.exec(null, null, 'BackgroundModeExt', 'appstart', [options]); 194 | } 195 | }; 196 | 197 | /** 198 | * Move app to background (Android only). 199 | * 200 | * @return [ Void ] 201 | */ 202 | exports.moveToBackground = function() 203 | { 204 | if (this._isAndroid) 205 | { 206 | cordova.exec(null, null, 'BackgroundModeExt', 'background', []); 207 | } 208 | }; 209 | 210 | /** 211 | * Move app to foreground when in background (Android only). 212 | * 213 | * @return [ Void ] 214 | */ 215 | exports.moveToForeground = function() 216 | { 217 | if (this.isActive() && this._isAndroid) 218 | { 219 | cordova.exec(null, null, 'BackgroundModeExt', 'foreground', []); 220 | } 221 | }; 222 | 223 | /** 224 | * Exclude the app from the recent tasks list (Android only). 225 | * 226 | * @return [ Void ] 227 | */ 228 | exports.excludeFromTaskList = function() 229 | { 230 | if (this._isAndroid) 231 | { 232 | cordova.exec(null, null, 'BackgroundModeExt', 'tasklist', []); 233 | } 234 | }; 235 | 236 | /** 237 | * Override the back button on Android to go to background 238 | * instead of closing the app. 239 | * 240 | * @return [ Void ] 241 | */ 242 | exports.overrideBackButton = function() 243 | { 244 | document.addEventListener('backbutton', function() { 245 | exports.moveToBackground(); 246 | }, false); 247 | }; 248 | 249 | /** 250 | * If the screen is off. 251 | * 252 | * @param [ Function ] fn Callback function to invoke with boolean arg. 253 | * 254 | * @return [ Void ] 255 | */ 256 | exports.isScreenOff = function (fn) 257 | { 258 | if (this._isAndroid) 259 | { 260 | cordova.exec(fn, null, 'BackgroundModeExt', 'dimmed', []); 261 | } 262 | else 263 | { 264 | fn(undefined); 265 | } 266 | }; 267 | 268 | /** 269 | * Wake up the device. 270 | * 271 | * @return [ Void ] 272 | */ 273 | exports.wakeUp = function() 274 | { 275 | if (this._isAndroid) 276 | { 277 | cordova.exec(null, null, 'BackgroundModeExt', 'wakeup', []); 278 | } 279 | }; 280 | 281 | /** 282 | * Wake up and unlock the device. 283 | * 284 | * @return [ Void ] 285 | */ 286 | exports.unlock = function() 287 | { 288 | if (this._isAndroid) 289 | { 290 | cordova.exec(null, null, 'BackgroundModeExt', 'unlock', []); 291 | } 292 | }; 293 | 294 | /** 295 | * If the mode is enabled or disabled. 296 | * 297 | * @return [ Boolean ] 298 | */ 299 | exports.isEnabled = function() 300 | { 301 | return this._isEnabled !== false; 302 | }; 303 | 304 | /** 305 | * If the mode is active. 306 | * 307 | * @return [ Boolean ] 308 | */ 309 | exports.isActive = function() 310 | { 311 | return this._isActive !== false; 312 | }; 313 | 314 | exports._listener = {}; 315 | 316 | /** 317 | * Fire event with given arguments. 318 | * 319 | * @param [ String ] event The event's name. 320 | * @param [ Array ] The callback's arguments. 321 | * 322 | * @return [ Void ] 323 | */ 324 | exports.fireEvent = function (event) 325 | { 326 | var args = Array.apply(null, arguments).slice(1), 327 | listener = this._listener[event]; 328 | 329 | if (!listener) 330 | return; 331 | 332 | for (var i = 0; i < listener.length; i++) 333 | { 334 | var fn = listener[i][0], 335 | scope = listener[i][1]; 336 | 337 | fn.apply(scope, args); 338 | } 339 | }; 340 | 341 | /** 342 | * Register callback for given event. 343 | * 344 | * @param [ String ] event The event's name. 345 | * @param [ Function ] callback The function to be exec as callback. 346 | * @param [ Object ] scope The callback function's scope. 347 | * 348 | * @return [ Void ] 349 | */ 350 | exports.on = function (event, callback, scope) 351 | { 352 | if (typeof callback !== "function") 353 | return; 354 | 355 | if (!this._listener[event]) 356 | { 357 | this._listener[event] = []; 358 | } 359 | 360 | var item = [callback, scope || window]; 361 | 362 | this._listener[event].push(item); 363 | }; 364 | 365 | /** 366 | * Unregister callback for given event. 367 | * 368 | * @param [ String ] event The event's name. 369 | * @param [ Function ] callback The function to be exec as callback. 370 | * 371 | * @return [ Void ] 372 | */ 373 | exports.un = function (event, callback) 374 | { 375 | var listener = this._listener[event]; 376 | 377 | if (!listener) 378 | return; 379 | 380 | for (var i = 0; i < listener.length; i++) 381 | { 382 | var fn = listener[i][0]; 383 | 384 | if (fn == callback) 385 | { 386 | listener.splice(i, 1); 387 | break; 388 | } 389 | } 390 | }; 391 | 392 | /** 393 | * @private 394 | * 395 | * Flag indicates if the mode is enabled. 396 | */ 397 | exports._isEnabled = false; 398 | 399 | /** 400 | * @private 401 | * 402 | * Flag indicates if the mode is active. 403 | */ 404 | exports._isActive = false; 405 | 406 | /** 407 | * @private 408 | * 409 | * Default values of all available options. 410 | */ 411 | exports._defaults = 412 | { 413 | title: 'App is running in background', 414 | text: 'Doing heavy tasks.', 415 | bigText: false, 416 | resume: true, 417 | silent: false, 418 | hidden: true, 419 | color: undefined, 420 | icon: 'icon' 421 | }; 422 | 423 | /** 424 | * @private 425 | * 426 | * Merge settings with default values. 427 | * 428 | * @param [ Object ] options The custom options. 429 | * @param [ Object ] toMergeIn The options to merge in. 430 | * 431 | * @return [ Object ] Default values merged with custom values. 432 | */ 433 | exports._mergeObjects = function (options, toMergeIn) 434 | { 435 | for (var key in toMergeIn) 436 | { 437 | if (!options.hasOwnProperty(key)) 438 | { 439 | options[key] = toMergeIn[key]; 440 | continue; 441 | } 442 | } 443 | 444 | return options; 445 | }; 446 | 447 | /** 448 | * @private 449 | * 450 | * Setter for the isActive flag. Resets the 451 | * settings if the mode isnt active anymore. 452 | * 453 | * @param [ Boolean] value The new value for the flag. 454 | * 455 | * @return [ Void ] 456 | */ 457 | exports._setActive = function(value) 458 | { 459 | if (this._isActive == value) 460 | return; 461 | 462 | this._isActive = value; 463 | this._settings = value ? this._mergeObjects({}, this._defaults) : {}; 464 | }; 465 | 466 | /** 467 | * @private 468 | * 469 | * Initialize the plugin. 470 | * 471 | * Method should be called after the 'deviceready' event 472 | * but before the event listeners will be called. 473 | * 474 | * @return [ Void ] 475 | */ 476 | exports._pluginInitialize = function() 477 | { 478 | this._isAndroid = device.platform.match(/^android|amazon/i) !== null; 479 | this.setDefaults({}); 480 | 481 | if (device.platform == 'browser') 482 | { 483 | this.enable(); 484 | this._isEnabled = true; 485 | } 486 | 487 | this._isActive = this._isActive || device.platform == 'browser'; 488 | }; 489 | 490 | // Called before 'deviceready' listener will be called 491 | channel.onCordovaReady.subscribe(function() 492 | { 493 | channel.onCordovaInfoReady.subscribe(function() { 494 | exports._pluginInitialize(); 495 | }); 496 | }); 497 | 498 | // Called after 'deviceready' event 499 | channel.deviceready.subscribe(function() 500 | { 501 | if (exports.isEnabled()) 502 | { 503 | exports.fireEvent('enable'); 504 | } 505 | 506 | if (exports.isActive()) 507 | { 508 | exports.fireEvent('activate'); 509 | } 510 | }); 511 | -------------------------------------------------------------------------------- /src/android/BackgroundModeExt.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Sebastián Katzer 3 | 4 | Licensed to the Apache Software Foundation (ASF) under one 5 | or more contributor license agreements. See the NOTICE file 6 | distributed with this work for additional information 7 | regarding copyright ownership. The ASF licenses this file 8 | to you under the Apache License, Version 2.0 (the 9 | "License"); you may not use this file except in compliance 10 | with the License. You may obtain a copy of the License at 11 | 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | 14 | Unless required by applicable law or agreed to in writing, 15 | software distributed under the License is distributed on an 16 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | KIND, either express or implied. See the License for the 18 | specific language governing permissions and limitations 19 | under the License. 20 | */ 21 | 22 | package de.appplant.cordova.plugin.background; 23 | 24 | import android.annotation.SuppressLint; 25 | import android.annotation.TargetApi; 26 | import android.app.Activity; 27 | import android.app.ActivityManager; 28 | import android.app.ActivityManager.AppTask; 29 | import android.app.AlertDialog; 30 | import android.content.ComponentName; 31 | import android.content.Context; 32 | import android.content.Intent; 33 | import android.content.pm.PackageManager; 34 | import android.net.Uri; 35 | import android.os.Build; 36 | import android.os.PowerManager; 37 | import android.view.View; 38 | 39 | import org.apache.cordova.CallbackContext; 40 | import org.apache.cordova.CordovaPlugin; 41 | import org.apache.cordova.PluginResult; 42 | import org.apache.cordova.PluginResult.Status; 43 | import org.json.JSONArray; 44 | import org.json.JSONObject; 45 | 46 | import java.util.Arrays; 47 | import java.util.List; 48 | 49 | import static android.R.string.cancel; 50 | import static android.R.string.ok; 51 | import static android.R.style.Theme_DeviceDefault_Light_Dialog; 52 | import static android.content.Context.ACTIVITY_SERVICE; 53 | import static android.content.Context.POWER_SERVICE; 54 | import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY; 55 | import static android.os.Build.VERSION.SDK_INT; 56 | import static android.os.Build.VERSION_CODES.M; 57 | import static android.provider.Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS; 58 | import static android.view.WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON; 59 | import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD; 60 | import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; 61 | import static android.view.WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON; 62 | 63 | /** 64 | * Implements extended functions around the main purpose 65 | * of infinite execution in the background. 66 | */ 67 | public class BackgroundModeExt extends CordovaPlugin { 68 | 69 | // To keep the device awake 70 | private PowerManager.WakeLock wakeLock; 71 | 72 | /** 73 | * Executes the request. 74 | * 75 | * @param action The action to execute. 76 | * @param args The exec() arguments. 77 | * @param callback The callback context used when 78 | * calling back into JavaScript. 79 | * 80 | * @return Returning false results in a "MethodNotFound" error. 81 | */ 82 | @Override 83 | public boolean execute (String action, JSONArray args, 84 | CallbackContext callback) 85 | { 86 | boolean validAction = true; 87 | 88 | switch (action) 89 | { 90 | case "battery": 91 | disableBatteryOptimizations(); 92 | break; 93 | case "webview": 94 | disableWebViewOptimizations(); 95 | break; 96 | case "appstart": 97 | openAppStart(args.opt(0)); 98 | break; 99 | case "background": 100 | moveToBackground(); 101 | break; 102 | case "foreground": 103 | moveToForeground(); 104 | break; 105 | case "tasklist": 106 | excludeFromTaskList(); 107 | break; 108 | case "dimmed": 109 | isDimmed(callback); 110 | break; 111 | case "wakeup": 112 | wakeup(); 113 | break; 114 | case "unlock": 115 | wakeup(); 116 | unlock(); 117 | break; 118 | default: 119 | validAction = false; 120 | } 121 | 122 | if (validAction) { 123 | callback.success(); 124 | } else { 125 | callback.error("Invalid action: " + action); 126 | } 127 | 128 | return validAction; 129 | } 130 | 131 | /** 132 | * Moves the app to the background. 133 | */ 134 | private void moveToBackground() 135 | { 136 | Intent intent = new Intent(Intent.ACTION_MAIN); 137 | 138 | intent.addCategory(Intent.CATEGORY_HOME); 139 | 140 | getApp().startActivity(intent); 141 | } 142 | 143 | /** 144 | * Moves the app to the foreground. 145 | */ 146 | private void moveToForeground() 147 | { 148 | Activity app = getApp(); 149 | Intent intent = getLaunchIntent(); 150 | 151 | intent.addFlags( 152 | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | 153 | Intent.FLAG_ACTIVITY_SINGLE_TOP | 154 | Intent.FLAG_ACTIVITY_CLEAR_TOP); 155 | 156 | clearScreenAndKeyguardFlags(); 157 | app.startActivity(intent); 158 | } 159 | 160 | /** 161 | * Enable GPS position tracking while in background. 162 | */ 163 | private void disableWebViewOptimizations() { 164 | Thread thread = new Thread(){ 165 | public void run() { 166 | try { 167 | Thread.sleep(1000); 168 | getApp().runOnUiThread(() -> { 169 | View view = webView.getEngine().getView(); 170 | 171 | try { 172 | Class.forName("org.crosswalk.engine.XWalkCordovaView") 173 | .getMethod("onShow") 174 | .invoke(view); 175 | } catch (Exception e){ 176 | view.dispatchWindowVisibilityChanged(View.VISIBLE); 177 | } 178 | }); 179 | } catch (InterruptedException e) { 180 | // do nothing 181 | } 182 | } 183 | }; 184 | 185 | thread.start(); 186 | } 187 | 188 | /** 189 | * Disables battery optimizations for the app. 190 | * Requires permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS to function. 191 | */ 192 | @SuppressLint("BatteryLife") 193 | private void disableBatteryOptimizations() 194 | { 195 | Activity activity = cordova.getActivity(); 196 | Intent intent = new Intent(); 197 | String pkgName = activity.getPackageName(); 198 | PowerManager pm = (PowerManager)getService(POWER_SERVICE); 199 | 200 | if (SDK_INT < M) 201 | return; 202 | 203 | if (pm.isIgnoringBatteryOptimizations(pkgName)) 204 | return; 205 | 206 | intent.setAction(ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); 207 | intent.setData(Uri.parse("package:" + pkgName)); 208 | 209 | cordova.getActivity().startActivity(intent); 210 | } 211 | 212 | /** 213 | * Opens the system settings dialog where the user can tweak or turn off any 214 | * custom app start settings added by the manufacturer if available. 215 | * 216 | * @param arg Text and title for the dialog or false to skip the dialog. 217 | */ 218 | private void openAppStart (Object arg) 219 | { 220 | Activity activity = cordova.getActivity(); 221 | PackageManager pm = activity.getPackageManager(); 222 | 223 | for (Intent intent : getAppStartIntents()) 224 | { 225 | if (pm.resolveActivity(intent, MATCH_DEFAULT_ONLY) != null) 226 | { 227 | JSONObject spec = (arg instanceof JSONObject) ? (JSONObject) arg : null; 228 | 229 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 230 | 231 | if (arg instanceof Boolean && !((Boolean) arg)) 232 | { 233 | activity.startActivity(intent); 234 | break; 235 | } 236 | 237 | AlertDialog.Builder dialog = new AlertDialog.Builder(activity, Theme_DeviceDefault_Light_Dialog); 238 | 239 | dialog.setPositiveButton(ok, (o, d) -> activity.startActivity(intent)); 240 | dialog.setNegativeButton(cancel, (o, d) -> {}); 241 | dialog.setCancelable(true); 242 | 243 | if (spec != null && spec.has("title")) 244 | { 245 | dialog.setTitle(spec.optString("title")); 246 | } 247 | 248 | if (spec != null && spec.has("text")) 249 | { 250 | dialog.setMessage(spec.optString("text")); 251 | } 252 | else 253 | { 254 | dialog.setMessage("missing text"); 255 | } 256 | 257 | activity.runOnUiThread(dialog::show); 258 | 259 | break; 260 | } 261 | } 262 | } 263 | 264 | /** 265 | * Excludes the app from the recent tasks list. 266 | */ 267 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 268 | private void excludeFromTaskList() 269 | { 270 | ActivityManager am = (ActivityManager) getService(ACTIVITY_SERVICE); 271 | 272 | if (am == null || SDK_INT < 21) 273 | return; 274 | 275 | List tasks = am.getAppTasks(); 276 | 277 | if (tasks == null || tasks.isEmpty()) 278 | return; 279 | 280 | tasks.get(0).setExcludeFromRecents(true); 281 | } 282 | 283 | /** 284 | * Invokes the callback with information if the screen is on. 285 | * 286 | * @param callback The callback to invoke. 287 | */ 288 | @SuppressWarnings("deprecation") 289 | private void isDimmed (CallbackContext callback) 290 | { 291 | boolean status = isDimmed(); 292 | PluginResult res = new PluginResult(Status.OK, status); 293 | 294 | callback.sendPluginResult(res); 295 | } 296 | 297 | /** 298 | * Returns if the screen is active. 299 | */ 300 | @SuppressWarnings("deprecation") 301 | private boolean isDimmed() 302 | { 303 | PowerManager pm = (PowerManager) getService(POWER_SERVICE); 304 | 305 | if (SDK_INT < 20) 306 | { 307 | return !pm.isScreenOn(); 308 | } 309 | 310 | return !pm.isInteractive(); 311 | } 312 | 313 | /** 314 | * Wakes up the device if the screen isn't still on. 315 | */ 316 | private void wakeup() 317 | { 318 | try { 319 | acquireWakeLock(); 320 | } catch (Exception e) { 321 | releaseWakeLock(); 322 | } 323 | } 324 | 325 | /** 326 | * Unlocks the device even with password protection. 327 | */ 328 | private void unlock() 329 | { 330 | addSreenAndKeyguardFlags(); 331 | getApp().startActivity(getLaunchIntent()); 332 | } 333 | 334 | /** 335 | * Acquires a wake lock to wake up the device. 336 | */ 337 | @SuppressWarnings("deprecation") 338 | private void acquireWakeLock() 339 | { 340 | PowerManager pm = (PowerManager) getService(POWER_SERVICE); 341 | 342 | releaseWakeLock(); 343 | 344 | if (!isDimmed()) 345 | return; 346 | 347 | int level = PowerManager.SCREEN_DIM_WAKE_LOCK | 348 | PowerManager.ACQUIRE_CAUSES_WAKEUP; 349 | 350 | wakeLock = pm.newWakeLock(level, "backgroundmode:wakelock"); 351 | wakeLock.setReferenceCounted(false); 352 | wakeLock.acquire(1000); 353 | } 354 | 355 | /** 356 | * Releases the previously acquire wake lock. 357 | */ 358 | private void releaseWakeLock() 359 | { 360 | if (wakeLock != null && wakeLock.isHeld()) { 361 | wakeLock.release(); 362 | wakeLock = null; 363 | } 364 | } 365 | 366 | /** 367 | * Adds required flags to the window to unlock/wakeup the device. 368 | */ 369 | private void addSreenAndKeyguardFlags() 370 | { 371 | getApp().runOnUiThread(() -> getApp().getWindow().addFlags(FLAG_ALLOW_LOCK_WHILE_SCREEN_ON | FLAG_SHOW_WHEN_LOCKED | FLAG_TURN_SCREEN_ON | FLAG_DISMISS_KEYGUARD)); 372 | } 373 | 374 | /** 375 | * Clears required flags to the window to unlock/wakeup the device. 376 | */ 377 | private void clearScreenAndKeyguardFlags() 378 | { 379 | getApp().runOnUiThread(() -> getApp().getWindow().clearFlags(FLAG_ALLOW_LOCK_WHILE_SCREEN_ON | FLAG_SHOW_WHEN_LOCKED | FLAG_TURN_SCREEN_ON | FLAG_DISMISS_KEYGUARD)); 380 | } 381 | 382 | /** 383 | * Removes required flags to the window to unlock/wakeup the device. 384 | */ 385 | static void clearKeyguardFlags (Activity app) 386 | { 387 | app.runOnUiThread(() -> app.getWindow().clearFlags(FLAG_DISMISS_KEYGUARD)); 388 | } 389 | 390 | /** 391 | * Returns the activity referenced by cordova. 392 | */ 393 | Activity getApp() { 394 | return cordova.getActivity(); 395 | } 396 | 397 | /** 398 | * Gets the launch intent for the main activity. 399 | */ 400 | private Intent getLaunchIntent() 401 | { 402 | Context app = getApp().getApplicationContext(); 403 | String pkgName = app.getPackageName(); 404 | 405 | return app.getPackageManager().getLaunchIntentForPackage(pkgName); 406 | } 407 | 408 | /** 409 | * Get the requested system service by name. 410 | * 411 | * @param name The name of the service. 412 | */ 413 | private Object getService(String name) 414 | { 415 | return getApp().getSystemService(name); 416 | } 417 | 418 | /** 419 | * Returns list of all possible intents to present the app start settings. 420 | */ 421 | private List getAppStartIntents() 422 | { 423 | return Arrays.asList( 424 | new Intent().setComponent(new ComponentName("com.miui.securitycenter","com.miui.permcenter.autostart.AutoStartManagementActivity")), 425 | new Intent().setComponent(new ComponentName("com.letv.android.letvsafe", "com.letv.android.letvsafe.AutobootManageActivity")), 426 | new Intent().setComponent(new ComponentName("com.huawei.systemmanager", "com.huawei.systemmanager.appcontrol.activity.StartupAppControlActivity")), 427 | new Intent().setComponent(new ComponentName("com.huawei.systemmanager", "com.huawei.systemmanager.optimize.process.ProtectActivity")), 428 | new Intent().setComponent(new ComponentName("com.coloros.safecenter", "com.coloros.safecenter.permission.startup.StartupAppListActivity")), 429 | new Intent().setComponent(new ComponentName("com.coloros.safecenter", "com.coloros.safecenter.startupapp.StartupAppListActivity")), 430 | new Intent().setComponent(new ComponentName("com.oppo.safe", "com.oppo.safe.permission.startup.StartupAppListActivity")), 431 | new Intent().setComponent(new ComponentName("com.iqoo.secure", "com.iqoo.secure.ui.phoneoptimize.AddWhiteListActivity")), 432 | new Intent().setComponent(new ComponentName("com.iqoo.secure", "com.iqoo.secure.ui.phoneoptimize.BgStartUpManager")), 433 | new Intent().setComponent(new ComponentName("com.vivo.permissionmanager", "com.vivo.permissionmanager.activity.BgStartUpManagerActivity")), 434 | new Intent().setComponent(new ComponentName("com.asus.mobilemanager", "com.asus.mobilemanager.autostart.AutoStartActivity")), 435 | new Intent().setComponent(new ComponentName("com.asus.mobilemanager", "com.asus.mobilemanager.entry.FunctionActivity")).setData(android.net.Uri.parse("mobilemanager://function/entry/AutoStart")), 436 | new Intent().setAction("com.letv.android.permissionautoboot"), 437 | new Intent().setComponent(new ComponentName("com.samsung.android.sm_cn", "com.samsung.android.sm.ui.ram.AutoRunActivity")), 438 | new Intent().setComponent(ComponentName.unflattenFromString("com.iqoo.secure/.MainActivity")), 439 | new Intent().setComponent(ComponentName.unflattenFromString("com.meizu.safe/.permission.SmartBGActivity")), 440 | new Intent().setComponent(new ComponentName("com.yulong.android.coolsafe", ".ui.activity.autorun.AutoRunListActivity")), 441 | new Intent().setComponent(new ComponentName("cn.nubia.security2", "cn.nubia.security.appmanage.selfstart.ui.SelfStartActivity")), 442 | new Intent().setComponent(new ComponentName("com.zui.safecenter", "com.lenovo.safecenter.MainTab.LeSafeMainActivity")) 443 | ); 444 | } 445 | } 446 | --------------------------------------------------------------------------------