Skip to content

Commit

Permalink
Merge branch 'master' into refactor-lollipop-defer
Browse files Browse the repository at this point in the history
  • Loading branch information
davidgyoung committed Jun 23, 2017
2 parents ab2c1d9 + c9e3c5e commit 5d87a03
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 4 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@

Enhancements:

- Add ability to circumvent prohibition against scans running for > 30 minutes on Android N.
(#529, David G. Young)
- Add support for running the beacon scanning service in a separate process and working with
application setups that have more than one process. (#479, David G. Young)

Bug Fixes:

- Fix Google Play submission errors by no longer using uses-permission-sdk-23 in
Manifest (#527, David G. Young)
- Fix failure to restart scanning in some cases after bluetooth has been off but then is turned
back on. (#519, David G. Young)
- Fix failure to stop scanning when unbinding from service or when the between scan period
Expand Down
2 changes: 1 addition & 1 deletion src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<uses-permission android:name="android.permission.BLUETOOTH" android:required="false"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:required="false"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission-sdk-23 android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>

<application>
<receiver android:name="org.altbeacon.beacon.startup.StartupBroadcastReceiver">
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/org/altbeacon/beacon/service/BeaconService.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,12 @@
import android.app.PendingIntent;
import android.app.Service;
import android.bluetooth.BluetoothDevice;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
import android.os.AsyncTask;
import android.os.Binder;
import android.os.Build;
Expand Down Expand Up @@ -240,6 +243,15 @@ 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");
mCycledScanner.setLongScanForcingEnabled(true);
}
} catch (PackageManager.NameNotFoundException e) {}

reloadParsers();

defaultDistanceCalculator = new ModelSpecificDistanceCalculator(this, BeaconManager.getDistanceModelUpdateUrl());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,20 @@

@TargetApi(18)
public abstract class CycledLeScanner {
public static final long ANDROID_N_MAX_SCAN_DURATION_MILLIS = 30 * 60 * 1000l; // 30 minutes
private static final String TAG = "CycledLeScanner";
private BluetoothAdapter mBluetoothAdapter;

private long mLastScanCycleStartTime = 0l;
private long mLastScanCycleEndTime = 0l;
protected long mNextScanCycleStartTime = 0l;
private long mScanCycleStopTime = 0l;

// This is the last time this class actually commanded the OS
// to start scanning.
private long mCurrentScanStartTime = 0l;
// True if the app has explicitly requested long running scans that
// may go beyond what is normally allowed on Android N.
private boolean mLongScanForcingEnabled = false;
private boolean mScanning;
protected boolean mScanningPaused;
private boolean mScanCyclerStarted = false;
Expand Down Expand Up @@ -136,6 +142,15 @@ public static CycledLeScanner createScanner(Context context, long scanPeriod, lo

}

/**
* Enables the scanner to go to extra lengths to keep scans going for longer than would
* otherwise be allowed. Useful only for Android N and higher.
* @param enabled
*/
public void setLongScanForcingEnabled(boolean enabled) {
mLongScanForcingEnabled = enabled;
}

/**
* Tells the cycler the scan rate and whether it is in operating in background mode.
* Background mode flag is used only with the Android 5.0 scanning implementations to switch
Expand Down Expand Up @@ -272,6 +287,7 @@ protected void scanLeDevice(final Boolean enable) {
}
try {
if (android.os.Build.VERSION.SDK_INT < 23 || checkLocationPermission()) {
mCurrentScanStartTime = SystemClock.elapsedRealtime();
startScan();
}
} catch (Exception e) {
Expand All @@ -290,7 +306,9 @@ protected void scanLeDevice(final Boolean enable) {
LogManager.e(e, TAG, "Exception starting Bluetooth scan. Perhaps Bluetooth is disabled or unavailable?");
}
} else {
LogManager.d(TAG, "We are already scanning");
LogManager.d(TAG, "We are already scanning and have been for "+(
SystemClock.elapsedRealtime() - mCurrentScanStartTime
)+" millis");
}
mScanCycleStopTime = (SystemClock.elapsedRealtime() + mScanPeriod);
scheduleScanCycleStop();
Expand All @@ -301,6 +319,7 @@ protected void scanLeDevice(final Boolean enable) {
mScanning = false;
mScanCyclerStarted = false;
stopScan();
mCurrentScanStartTime = 0l;
mLastScanCycleEndTime = SystemClock.elapsedRealtime();
// Clear any queued schedule tasks as we're done scanning
mScanHandler.removeCallbacksAndMessages(null);
Expand Down Expand Up @@ -353,7 +372,9 @@ private void finishScanCycle() {
// so it is best avoided. If we know the device has detected to distinct
// packets in the same cycle, we will not restart scanning and just keep it
// going.
if (!mDistinctPacketsDetectedPerScan || mBetweenScanPeriod != 0) {
if (!mDistinctPacketsDetectedPerScan ||
mBetweenScanPeriod != 0 ||
mustStopScanToPreventAndroidNScanTimeout()) {
long now = SystemClock.elapsedRealtime();
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N &&
mBetweenScanPeriod+mScanPeriod < ANDROID_N_MIN_SCAN_CYCLE_MILLIS &&
Expand Down Expand Up @@ -488,4 +509,35 @@ private boolean checkLocationPermission() {
private boolean checkPermission(final String permission) {
return mContext.checkPermission(permission, android.os.Process.myPid(), android.os.Process.myUid()) == PackageManager.PERMISSION_GRANTED;
}

/**
* On Android N and later, a scan that runs for more than 30 minutes will be automatically
* stopped by the OS and converted to an "opportunistic" scan, meaning that they will only yield
* detections if another app is scanning. This is inteneded to save battery. This can be
* prevented by stopping scanning and restarting. This method returns true if:
* * this is Android N or later
* * we are close to the 30 minute boundary since the last scan started
* * The app developer has explicitly enabled long-running scans
* @return true if we must stop scanning to prevent
*/
private boolean mustStopScanToPreventAndroidNScanTimeout() {
long timeOfNextScanCycleEnd = SystemClock.elapsedRealtime() + mBetweenScanPeriod +
mScanPeriod;
boolean timeoutAtRisk = android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N &&
mCurrentScanStartTime > 0 &&
(timeOfNextScanCycleEnd - mCurrentScanStartTime > ANDROID_N_MAX_SCAN_DURATION_MILLIS);

if (timeoutAtRisk) {
LogManager.d(TAG, "The next scan cycle would go over the Android N max duration.");
if (mLongScanForcingEnabled) {
LogManager.d(TAG, "Stopping scan to prevent Android N scan timeout.");
return true;
}
else {
LogManager.w(TAG, "Allowing a long running scan to be stopped by the OS. To " +
"prevent this, set longScanForcingEnabled in the AndroidBeaconLibrary.");
}
}
return false;
}
}

0 comments on commit 5d87a03

Please sign in to comment.