From 9ca5864439e2b1c2e4e3ff71db983938adb313b7 Mon Sep 17 00:00:00 2001 From: Gary Mathews Date: Wed, 2 Oct 2019 13:31:19 -0700 Subject: [PATCH] feat(android): implement foregroundServiceType parameter (#11197) * docs(android): include foregroundServiceType parameter and constants * docs(android): include foreground service example * fix(android): whitelist foregroundServiceType attribute Fixes TIMOB-26953 --- android/cli/lib/AndroidManifest.js | 2 +- .../titanium/android/AndroidModule.java | 20 +++ .../titanium/proxy/ServiceProxy.java | 13 +- apidoc/Titanium/Android/Android.yml | 63 ++++++++ apidoc/Titanium/Android/Service.yml | 148 ++++++++++++++++++ 5 files changed, 243 insertions(+), 3 deletions(-) diff --git a/android/cli/lib/AndroidManifest.js b/android/cli/lib/AndroidManifest.js index 39fe401d020..e070c1a8751 100644 --- a/android/cli/lib/AndroidManifest.js +++ b/android/cli/lib/AndroidManifest.js @@ -33,7 +33,7 @@ const appc = require('node-appc'), 'path-permission': /^(path|pathPrefix|pathPattern|permission|readPermissions|writePermissions)$/, provider: /^(authorities|directBootAware|enabled|exported|grantUriPermissions|icon|initOrder|label|multiprocess|name|permission|process|readPermission|syncable|writePermission)$/, receiver: /^(directBootAware|enabled|exported|icon|label|name|permission|process)$/, - service: /^(description|directBootAware|enabled|exported|icon|isolatedProcess|label|name|permission|process)$/, + service: /^(description|directBootAware|enabled|exported|icon|isolatedProcess|label|name|permission|process|foregroundServiceType)$/, 'uses-library': /^(name|required)$/, 'uses-sdk': /^(maxSdkVersion|minSdkVersion|targetSdkVersion)$/ }; diff --git a/android/modules/android/src/java/ti/modules/titanium/android/AndroidModule.java b/android/modules/android/src/java/ti/modules/titanium/android/AndroidModule.java index 0b4e471fcd8..f5c7a6c6ba0 100644 --- a/android/modules/android/src/java/ti/modules/titanium/android/AndroidModule.java +++ b/android/modules/android/src/java/ti/modules/titanium/android/AndroidModule.java @@ -11,6 +11,7 @@ import java.util.List; import android.content.pm.PackageManager; +import android.content.pm.ServiceInfo; import android.os.Build; import android.service.quicksettings.Tile; import org.appcelerator.kroll.KrollDict; @@ -522,6 +523,25 @@ public class AndroidModule extends KrollModule @Kroll.constant public static final int IMPORTANCE_UNSPECIFIED = NotificationManagerCompat.IMPORTANCE_UNSPECIFIED; + @Kroll.constant + public static final int FOREGROUND_SERVICE_TYPE_MANIFEST = ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST; + @Kroll.constant + public static final int FOREGROUND_SERVICE_TYPE_NONE = ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE; + @Kroll.constant + public static final int FOREGROUND_SERVICE_TYPE_DATA_SYNC = ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC; + @Kroll.constant + public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK = ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK; + @Kroll.constant + public static final int FOREGROUND_SERVICE_TYPE_PHONE_CALL = ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL; + @Kroll.constant + public static final int FOREGROUND_SERVICE_TYPE_LOCATION = ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION; + @Kroll.constant + public static final int FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE = + ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE; + @Kroll.constant + public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION = + ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION; + protected RProxy r; private LinkedList registeredBroadcastReceiverProxyList = new LinkedList<>(); diff --git a/android/titanium/src/java/org/appcelerator/titanium/proxy/ServiceProxy.java b/android/titanium/src/java/org/appcelerator/titanium/proxy/ServiceProxy.java index 1784897456d..8e24fbce128 100644 --- a/android/titanium/src/java/org/appcelerator/titanium/proxy/ServiceProxy.java +++ b/android/titanium/src/java/org/appcelerator/titanium/proxy/ServiceProxy.java @@ -42,6 +42,7 @@ public class ServiceProxy extends KrollProxy private IntentProxy intentProxy; private int notificationId; private KrollProxy notificationProxy; + private int foregroundServiceType; // Set only if the service is started via bindService as opposed to startService private ServiceConnection serviceConnection = null; @@ -130,7 +131,8 @@ public void stop() } @Kroll.method - public void foregroundNotify(int notificationId, KrollProxy notificationProxy) + public void foregroundNotify(int notificationId, KrollProxy notificationProxy, + @Kroll.argument(optional = true) int foregroundServiceType) { // Validate arguments. if (notificationId == 0) { @@ -145,6 +147,7 @@ public void foregroundNotify(int notificationId, KrollProxy notificationProxy) { this.notificationId = notificationId; this.notificationProxy = notificationProxy; + this.foregroundServiceType = foregroundServiceType; } updateForegroundState(); } @@ -268,6 +271,7 @@ public void run() // Fetch the proxy's notification and ID. int notificationId = 0; Notification notificationObject = null; + int foregroundServiceType = 0; try { // Fetch notification settings from proxy. synchronized (ServiceProxy.this) @@ -282,6 +286,7 @@ public void run() Method method = proxyClass.getMethod("buildNotification"); notificationObject = (Notification) method.invoke(object); } + foregroundServiceType = ServiceProxy.this.foregroundServiceType; } } @@ -357,7 +362,11 @@ public void run() // Note: A notification will be shown in the status bar while enabled. try { if (isForeground) { - service.startForeground(notificationId, notificationObject); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + service.startForeground(notificationId, notificationObject, foregroundServiceType); + } else { + service.startForeground(notificationId, notificationObject); + } } else { service.stopForeground(true); } diff --git a/apidoc/Titanium/Android/Android.yml b/apidoc/Titanium/Android/Android.yml index 4bd5c5ddcf1..ecad4478047 100644 --- a/apidoc/Titanium/Android/Android.yml +++ b/apidoc/Titanium/Android/Android.yml @@ -1816,6 +1816,69 @@ properties: type: Number permission: read-only + - name: FOREGROUND_SERVICE_TYPE_MANIFEST + summary: | + A special value indicates to use all types set in manifest file. + description: | + See [ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST in the Android API Reference](https://developer.android.com/reference/android/content/pm/ServiceInfo.html#FOREGROUND_SERVICE_TYPE_MANIFEST). + type: Number + permission: read-only + since: "8.3.0" + + - name: FOREGROUND_SERVICE_TYPE_NONE + summary: | + The default foreground service type if not been set in manifest file. + description: | + See [ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE in the Android API Reference](https://developer.android.com/reference/android/content/pm/ServiceInfo.html#FOREGROUND_SERVICE_TYPE_NONE). + type: Number + permission: read-only + since: "8.3.0" + + - name: FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK + summary: | + Constant corresponding to mediaPlayback in the R.attr.foregroundServiceType attribute. Music, video, news or other media playback. + description: | + See [ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK in the Android API Reference](https://developer.android.com/reference/android/content/pm/ServiceInfo.html#FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK). + type: Number + permission: read-only + since: "8.3.0" + + - name: FOREGROUND_SERVICE_TYPE_PHONE_CALL + summary: | + Constant corresponding to phoneCall in the R.attr.foregroundServiceType attribute. Ongoing phone call or video conference. + description: | + See [ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL in the Android API Reference](https://developer.android.com/reference/android/content/pm/ServiceInfo.html#FOREGROUND_SERVICE_TYPE_PHONE_CALL). + type: Number + permission: read-only + since: "8.3.0" + + - name: FOREGROUND_SERVICE_TYPE_LOCATION + summary: | + Constant corresponding to location in the R.attr.foregroundServiceType attribute. GPS, map, navigation location update. + description: | + See [ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION in the Android API Reference](https://developer.android.com/reference/android/content/pm/ServiceInfo.html#FOREGROUND_SERVICE_TYPE_LOCATION). + type: Number + permission: read-only + since: "8.3.0" + + - name: FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE + summary: | + Constant corresponding to connectedDevice in the R.attr.foregroundServiceType attribute. Auto, bluetooth, TV or other devices connection, monitoring and interaction. + description: | + See [ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE in the Android API Reference](https://developer.android.com/reference/android/content/pm/ServiceInfo.html#FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE). + type: Number + permission: read-only + since: "8.3.0" + + - name: FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION + summary: | + Constant corresponding to mediaProjection in the R.attr.foregroundServiceType attribute. Managing a media projection session, e.g for screen recording or taking screenshots. + description: | + See [ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION in the Android API Reference](https://developer.android.com/reference/android/content/pm/ServiceInfo.html#FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION). + type: Number + permission: read-only + since: "8.3.0" + - name: FLAG_AUTO_CANCEL summary: Cancel the notification when it is clicked by the user. description: | diff --git a/apidoc/Titanium/Android/Service.yml b/apidoc/Titanium/Android/Service.yml index 80907eb3029..2e73500bb6b 100644 --- a/apidoc/Titanium/Android/Service.yml +++ b/apidoc/Titanium/Android/Service.yml @@ -34,6 +34,148 @@ description: | * [Android Services guide](https://docs.appcelerator.com/platform/latest/#!/guide/Android_Services) * [Android Developer: Service](https://developer.android.com/reference/android/app/Service.html) + + ### Background Location Service Example + In order to obtain location events while the application is backgrounded a foreground service must be used. + Below is an example of a simple background location service. + + tiapp.xml: + + + + + + + + + + + locationService.js: + console.log(`Background location service started.`); + + // Create foreground service. + Ti.Android.currentService.foregroundNotify( + 123, + Ti.Android.createNotification({ + contentTitle: "Background Location Service", + contentText: "Obtaining location data...", + contentIntent: Ti.Android.createPendingIntent({ + intent: Ti.App.Android.launchIntent || Ti.Android.createIntent(), + }) + }), + Ti.Android.FOREGROUND_SERVICE_TYPE_LOCATION + ); + + app.js: + const win = Ti.UI.createWindow({ backgroundColor: 'gray', layout: 'vertical' }); + const backgroundServiceBtn = Ti.UI.createButton({ title: 'START BACKGROUND SERVICE' }); + const listView = Ti.UI.createListView(); + + let count = 1; + let service = null; + + // Set accuracy to high + Ti.Geolocation.accuracy = Ti.Geolocation.ACCURACY_HIGH; + + // Enable manual configuration via location providers. + Ti.Geolocation.Android.manualMode = true; + + // Define a location provider. + Ti.Geolocation.Android.addLocationProvider( + Ti.Geolocation.Android.createLocationProvider({ + name: Ti.Geolocation.Android.PROVIDER_GPS, + minUpdateDistance: 0, + minUpdateTime: 5000 + }) + ); + + function getLocation () { + + // Create location event listener. + Ti.Geolocation.addEventListener('location', e => { + + // Create new section for location data. + let section = Ti.UI.createListSection({ headerTitle: `#${count++} - ${new Date().toTimeString()}` }); + if (e.success) { + if (e.coords) { + e = e.coords; + } + + // Insert location data. + section.setItems([ + { properties: { title: 'LOCATION:\n' + e.latitude + ', ' + e.longitude, color: 'green' } }, + { properties: { title: 'ALTITUDE:\n' + e.altitude, color: 'green' } }, + { properties: { title: 'ACCURACY:\n' + e.accuracy, color: 'green' } } + ]); + + } else { + + // Oops! Something bad happened. + section.setItems([ + { properties: { title: 'ERROR:\n' + e.error, color: 'red' } } + ]); + } + + // Add section to listView + listView.appendSection(section); + }); + } + + function startBackgroundLocationService() { + if (service) { + console.log('Starting background location service...'); + service.start(); + backgroundServiceBtn.title = 'STOP BACKGROUND SERVICE'; + } + } + + function stopBackgroundLocationService() { + if (service) { + console.log('Stopping background location service...'); + service.stop(); + backgroundServiceBtn.title = 'START BACKGROUND SERVICE'; + } + } + + win.addEventListener('open', () => { + + // Request location permissions. + Ti.Geolocation.requestLocationPermissions(Ti.Geolocation.AUTHORIZATION_ALWAYS, e => { + if (e.success) { + getLocation(); + } else { + alert('Could not obtain location permissions.'); + } + }); + }); + + backgroundServiceBtn.addEventListener('click', () => { + if (!service) { + + // Create background location service. + const intent = Ti.Android.createServiceIntent({ url: 'locationService.js' }); + service = Ti.Android.createService(intent); + + // Android 10+ request background location permissions. + if (parseInt(Ti.Platform.version.split('.')[0]) >= 10) { + Ti.Android.requestPermissions([ 'android.permission.ACCESS_BACKGROUND_LOCATION' ], e => { + if (e.success) { + startBackgroundLocationService(); + } else { + alert('Could not obtain background location permissions.'); + } + }); + } else { + startBackgroundLocationService(); + } + + } else { + stopBackgroundLocationService(); + } + }); + + win.add([backgroundServiceBtn, listView]); + win.open(); extends: Titanium.Proxy platforms: [android] @@ -100,6 +242,12 @@ methods: summary: Notification to display in the status bar. Cannot be null. type: Titanium.Android.Notification + - name: foregroundServiceType + summary: Notification service type specified by . + type: Number + default: Titanium.Android.FOREGROUND_SERVICE_TYPE_NONE + optional: true + - name: start summary: Starts the Service. description: |