Skip to content

Commit

Permalink
Merge pull request #663 from AltBeacon/support-foreground-service
Browse files Browse the repository at this point in the history
Allow beacon service to run as foreground service
  • Loading branch information
davidgyoung authored Jul 4, 2018
2 parents 17f2463 + 30eaa69 commit 0f8e9fb
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 9 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
### Development

Enhancements:
- Optional foreground beacon scanning service for faster background detections on Android 8+
(#663, David G. Young)

Bug Fixes:
- Fixes inability to detect on some 5.x Samsung Devices without scan filters. (#693, David G. Young)
- Fix inverted logic for "disable ScanJob" warning (#700, Marcel Schnelle)
Expand Down
85 changes: 84 additions & 1 deletion src/main/java/org/altbeacon/beacon/BeaconManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
package org.altbeacon.beacon;

import android.annotation.TargetApi;
import android.app.Notification;
import android.bluetooth.BluetoothManager;
import android.content.ComponentName;
import android.content.Context;
Expand Down Expand Up @@ -163,6 +164,10 @@ public class BeaconManager {
private static boolean sAndroidLScanningDisabled = false;
private static boolean sManifestCheckingDisabled = false;

@Nullable
private Notification mForegroundServiceNotification = null;
private int mForegroundServiceNotificationId = -1;

/**
* Private lock object for singleton initialization protecting against denial-of-service attack.
*/
Expand Down Expand Up @@ -326,7 +331,7 @@ protected BeaconManager(@NonNull Context context) {
verifyServiceDeclaration();
}
this.beaconParsers.add(new AltBeaconParser());
mScheduledScanJobsEnabled = android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
setScheduledScanJobsEnabledDefault();
}

/***
Expand Down Expand Up @@ -1333,6 +1338,81 @@ public static boolean getManifestCheckingDisabled() {
return sManifestCheckingDisabled;
}


/**
* Configures the library to use a foreground service for bacon scanning. This allows nearly
* constant scanning on most Android versions to get around background limits, and displays an
* icon to the user to indicate that the app is doing something in the background, even on
* Android 8+. This will disable the user of the JobScheduler on Android 8 to do scans. Note
* that this method does not by itself enable constant scanning. The scan intervals will work
* as normal and must be configurd to specific values depending on how often you wish to scan.
*
* @see #setForegroundScanPeriod(long)
* @see #setForegroundBetweenScanPeriod(long)
*
* This method requires a notification to display a message to the user about why the app is
* scanning in the background. The notification must include an icon that will be displayed
* in the top bar whenever the scanning service is running.
*
* If the BeaconService is configured to run in a different process, this call will have no
* effect.
*
* @param notification - the notification that will be displayed when beacon scanning is active,
* along with the icon that shows up in the status bar.
*
* @throws IllegalStateException if called after consumers are already bound to the scanning
* service
*/
public void enableForegroundServiceScanning(Notification notification, int notificationId)
throws IllegalStateException {
if (isAnyConsumerBound()) {
throw new IllegalStateException("May not be called after consumers are already bound.");
}
if (notification == null) {
throw new NullPointerException("Notification cannot be null");
}
setEnableScheduledScanJobs(false);
mForegroundServiceNotification = notification;
mForegroundServiceNotificationId = notificationId;
}

/**
* Disables a foreground scanning service, if previously configured.
*
* @see #enableForegroundServiceScanning
*
* In order to call this method to disable a foreground service, you must unbind from the
* BeaconManager. You can then rebind after this call is made.
*
* @throws IllegalStateException if called after consumers are already bound to the scanning
* service
*/
public void disableForegroundServiceScanning() throws IllegalStateException {
if (isAnyConsumerBound()) {
throw new IllegalStateException("May not be called after consumers are already bound");
}
mForegroundServiceNotification = null;
setScheduledScanJobsEnabledDefault();
}

/**
* @see #enableForegroundServiceScanning
* @return The notification shown for the beacon scanning service, if so configured
*/
public Notification getForegroundServiceNotification() {
return mForegroundServiceNotification;
}


/**
* @see #enableForegroundServiceScanning
* @return The notification shown for the beacon scanning service, if so configured
*/
public int getForegroundServiceNotificationId() {
return mForegroundServiceNotificationId;
}


private boolean determineIfCalledFromSeparateScannerProcess() {
if (isScannerInDifferentProcess() && !isMainProcess()) {
LogManager.w(TAG, "Ranging/Monitoring may not be controlled from a separate "+
Expand All @@ -1351,4 +1431,7 @@ private static void warnIfScannerNotInSameProcess() {
}
}

private void setScheduledScanJobsEnabledDefault() {
mScheduledScanJobsEnabled = android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
}
}
46 changes: 38 additions & 8 deletions src/main/java/org/altbeacon/beacon/service/BeaconService.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@


import android.app.AlarmManager;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ComponentName;
Expand Down Expand Up @@ -216,14 +217,11 @@ public void onCreate() {
LogManager.i(TAG, "beaconService PID is "+processUtils.getPid()+" with process name "+processUtils.getProcessName());
}

try {
PackageItemInfo info = this.getPackageManager().getServiceInfo(new ComponentName(this, BeaconService.class), PackageManager.GET_META_DATA);
if (info != null && info.metaData != null && info.metaData.get("longScanForcingEnabled") != null &&
info.metaData.get("longScanForcingEnabled").toString().equals("true")) {
LogManager.i(TAG, "longScanForcingEnabled to keep scans going on Android N for > 30 minutes");
mScanHelper.getCycledScanner().setLongScanForcingEnabled(true);
}
} catch (PackageManager.NameNotFoundException e) {}
String longScanForcingEnabled = getManifestMetadataValue("longScanForcingEnabled");
if (longScanForcingEnabled != null && longScanForcingEnabled.equals("true")) {
LogManager.i(TAG, "longScanForcingEnabled to keep scans going on Android N for > 30 minutes");
mScanHelper.getCycledScanner().setLongScanForcingEnabled(true);
}

mScanHelper.reloadParsers();

Expand All @@ -240,6 +238,37 @@ public void onCreate() {
} catch (Exception e) {
LogManager.e(e, TAG, "Cannot get simulated Scan data. Make sure your org.altbeacon.beacon.SimulatedScanData class defines a field with the signature 'public static List<Beacon> beacons'");
}
this.startForegroundIfConfigured();
}

/*
* This starts the scanning service as a foreground service if it is so configured in the
* manifest
*/
private void startForegroundIfConfigured() {
BeaconManager beaconManager = BeaconManager.getInstanceForApplication(
this.getApplicationContext());
Notification notification = beaconManager
.getForegroundServiceNotification();
int notificationId = beaconManager
.getForegroundServiceNotificationId();
if (notification != null &&
Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
this.startForeground(notificationId, notification);
}
}

private String getManifestMetadataValue(String key) {
String value = null;
try {
PackageItemInfo info = this.getPackageManager().getServiceInfo(new ComponentName(this, BeaconService.class), PackageManager.GET_META_DATA);
if (info != null && info.metaData != null) {
return info.metaData.get(key).toString();
}
}
catch (PackageManager.NameNotFoundException e) {
}
return null;
}

@Override
Expand Down Expand Up @@ -277,6 +306,7 @@ public void onDestroy() {
LogManager.w(TAG, "Not supported prior to API 18.");
return;
}
stopForeground(true);
bluetoothCrashResolver.stop();
LogManager.i(TAG, "onDestroy called. stopping scanning");
handler.removeCallbacksAndMessages(null);
Expand Down

0 comments on commit 0f8e9fb

Please sign in to comment.