Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

impr(android): implement foregroundServiceType parameter #11197

Merged
merged 12 commits into from
Oct 2, 2019
2 changes: 1 addition & 1 deletion android/cli/lib/AndroidManifest.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)$/
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<BroadcastReceiverProxy> registeredBroadcastReceiverProxyList = new LinkedList<>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand All @@ -145,6 +147,7 @@ public void foregroundNotify(int notificationId, KrollProxy notificationProxy)
{
this.notificationId = notificationId;
this.notificationProxy = notificationProxy;
this.foregroundServiceType = foregroundServiceType;
}
updateForegroundState();
}
Expand Down Expand Up @@ -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)
Expand All @@ -282,6 +286,7 @@ public void run()
Method method = proxyClass.getMethod("buildNotification");
notificationObject = (Notification) method.invoke(object);
}
foregroundServiceType = ServiceProxy.this.foregroundServiceType;
}
}

Expand Down Expand Up @@ -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);
}
Expand Down
63 changes: 63 additions & 0 deletions apidoc/Titanium/Android/Android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: |
Expand Down
148 changes: 148 additions & 0 deletions apidoc/Titanium/Android/Service.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
<android>
<manifest>
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
</manifest>
<services>
<service url="locationService.js"/>
</services>
</android>

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]
Expand Down Expand Up @@ -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 <Titanium.Android.FOREGROUND_SERVICE_TYPE_*>.
type: Number
default: Titanium.Android.FOREGROUND_SERVICE_TYPE_NONE
optional: true

- name: start
summary: Starts the Service.
description: |
Expand Down