From c59223be575ebbc41ed09cd86d1106793cdeed5a Mon Sep 17 00:00:00 2001 From: Marco Salis Date: Tue, 13 Sep 2016 12:44:58 +0100 Subject: [PATCH 01/12] Start and stop BLE scans from a background thread to prevent blocking the UI (issue #136) --- .../beacon/service/BeaconService.java | 1 + .../service/scanner/CycledLeScanner.java | 15 +++- .../CycledLeScannerForJellyBeanMr2.java | 56 ++++++++++---- .../scanner/CycledLeScannerForLollipop.java | 76 +++++++++++-------- 4 files changed, 104 insertions(+), 44 deletions(-) diff --git a/src/main/java/org/altbeacon/beacon/service/BeaconService.java b/src/main/java/org/altbeacon/beacon/service/BeaconService.java index 4bc6b4274..dfe2baa77 100644 --- a/src/main/java/org/altbeacon/beacon/service/BeaconService.java +++ b/src/main/java/org/altbeacon/beacon/service/BeaconService.java @@ -273,6 +273,7 @@ public void onDestroy() { LogManager.i(TAG, "onDestroy called. stopping scanning"); handler.removeCallbacksAndMessages(null); mCycledScanner.stop(); + mCycledScanner.destroy(); monitoringStatus.stopStatusPreservation(); } diff --git a/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScanner.java b/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScanner.java index d0f4e7505..33165cdb0 100644 --- a/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScanner.java +++ b/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScanner.java @@ -12,6 +12,8 @@ import android.content.pm.PackageManager; import android.os.Build; import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; import android.os.SystemClock; import org.altbeacon.beacon.BeaconManager; @@ -40,7 +42,10 @@ public abstract class CycledLeScanner { private long mScanPeriod; protected long mBetweenScanPeriod; - protected final Handler mHandler = new Handler(); + + protected final Handler mHandler = new Handler(Looper.getMainLooper()); + protected final Handler mScanHandler; + private final HandlerThread mScanThread; protected final BluetoothCrashResolver mBluetoothCrashResolver; protected final CycledLeScanCallback mCycledLeScanCallback; @@ -57,6 +62,10 @@ protected CycledLeScanner(Context context, long scanPeriod, long betweenScanPeri mCycledLeScanCallback = cycledLeScanCallback; mBluetoothCrashResolver = crashResolver; mBackgroundFlag = backgroundFlag; + + mScanThread = new HandlerThread("CycledLeScannerThread"); + mScanThread.start(); + mScanHandler = new Handler(mScanThread.getLooper()); } public static CycledLeScanner createScanner(Context context, long scanPeriod, long betweenScanPeriod, boolean backgroundFlag, CycledLeScanCallback cycledLeScanCallback, BluetoothCrashResolver crashResolver) { @@ -156,6 +165,10 @@ public void stop() { } } + public void destroy() { + mScanThread.quit(); + } + protected abstract void stopScan(); protected abstract boolean deferScanIfNeeded(); diff --git a/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerForJellyBeanMr2.java b/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerForJellyBeanMr2.java index 7183b0107..b38f7281c 100644 --- a/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerForJellyBeanMr2.java +++ b/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerForJellyBeanMr2.java @@ -18,17 +18,9 @@ public CycledLeScannerForJellyBeanMr2(Context context, long scanPeriod, long bet super(context, scanPeriod, betweenScanPeriod, backgroundFlag, cycledLeScanCallback, crashResolver); } - @SuppressWarnings("deprecation") @Override protected void stopScan() { - try { - BluetoothAdapter bluetoothAdapter = getBluetoothAdapter(); - if (bluetoothAdapter != null) { - bluetoothAdapter.stopLeScan(getLeScanCallback()); - } - } catch (Exception e) { - LogManager.e(e, TAG, "Internal Android exception scanning for beacons"); - } + postStopLeScan(); } @Override @@ -54,19 +46,57 @@ public void run() { return false; } - @SuppressWarnings("deprecation") @Override protected void startScan() { - getBluetoothAdapter().startLeScan(getLeScanCallback()); + postStartLeScan(); } - @SuppressWarnings("deprecation") @Override protected void finishScan() { - getBluetoothAdapter().stopLeScan(getLeScanCallback()); + postStopLeScan(); mScanningPaused = true; } + private void postStartLeScan() { + final BluetoothAdapter bluetoothAdapter = getBluetoothAdapter(); + if (bluetoothAdapter == null) { + return; + } + final BluetoothAdapter.LeScanCallback leScanCallback = getLeScanCallback(); + mScanHandler.removeCallbacksAndMessages(null); + mScanHandler.post(new Runnable() { + @Override + public void run() { + try { + //noinspection deprecation + bluetoothAdapter.startLeScan(leScanCallback); + } catch (Exception e) { + LogManager.e(e, TAG, "Internal Android exception in startLeScan()"); + } + } + }); + } + + private void postStopLeScan() { + final BluetoothAdapter bluetoothAdapter = getBluetoothAdapter(); + if (bluetoothAdapter == null) { + return; + } + final BluetoothAdapter.LeScanCallback leScanCallback = getLeScanCallback(); + mScanHandler.removeCallbacksAndMessages(null); + mScanHandler.post(new Runnable() { + @Override + public void run() { + try { + //noinspection deprecation + bluetoothAdapter.stopLeScan(leScanCallback); + } catch (Exception e) { + LogManager.e(e, TAG, "Internal Android exception in stopLeScan()"); + } + } + }); + } + private BluetoothAdapter.LeScanCallback getLeScanCallback() { if (leScanCallback == null) { leScanCallback = diff --git a/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerForLollipop.java b/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerForLollipop.java index 7e37b9c08..e5ac14d65 100644 --- a/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerForLollipop.java +++ b/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerForLollipop.java @@ -8,7 +8,6 @@ import android.bluetooth.le.ScanResult; import android.bluetooth.le.ScanSettings; import android.content.Context; -import android.os.Build; import android.os.ParcelUuid; import android.os.SystemClock; @@ -39,20 +38,7 @@ public CycledLeScannerForLollipop(Context context, long scanPeriod, long between @Override protected void stopScan() { - try { - if (getScanner() != null) { - try { - getScanner().stopScan((android.bluetooth.le.ScanCallback) getNewLeScanCallback()); - } - catch (NullPointerException npe) { - // Necessary because of https://code.google.com/p/android/issues/detail?id=160503 - LogManager.e(TAG, "Cannot stop scan. Unexpected NPE.", npe); - } - } - } - catch (IllegalStateException e) { - LogManager.w(TAG, "Cannot stop scan. Bluetooth may be turned off."); - } + postStopLeScan(); } /* @@ -181,21 +167,7 @@ protected void startScan() { settings = (new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)).build(); } - try { - if (getScanner() != null) { - ScanCallback callback = getNewLeScanCallback(); - try { - getScanner().startScan(filters, settings, callback); - } - catch (NullPointerException npe) { - // Necessary because of https://code.google.com/p/android/issues/detail?id=160503 - LogManager.w(TAG, "Cannot start scan. Unexpected NPE.", npe); - } - } - } - catch (IllegalStateException e) { - LogManager.w(TAG, "Cannot start scan. Bluetooth may be turned off."); - } + postStartLeScan(filters, settings); } @Override @@ -205,6 +177,50 @@ protected void finishScan() { mScanningPaused = true; } + private void postStartLeScan(final List filters, final ScanSettings settings) { + final BluetoothLeScanner scanner = getScanner(); + if (scanner == null) { + return; + } + final ScanCallback scanCallback = getNewLeScanCallback(); + mScanHandler.removeCallbacksAndMessages(null); + mScanHandler.post(new Runnable() { + @Override + public void run() { + try { + scanner.startScan(filters, settings, scanCallback); + } catch (IllegalStateException e) { + LogManager.w(TAG, "Cannot start scan. Bluetooth may be turned off."); + } catch (NullPointerException npe) { + // Necessary because of https://code.google.com/p/android/issues/detail?id=160503 + LogManager.e(TAG, "Cannot start scan. Unexpected NPE.", npe); + } + } + }); + } + + private void postStopLeScan() { + final BluetoothLeScanner scanner = getScanner(); + if (scanner == null) { + return; + } + final ScanCallback scanCallback = getNewLeScanCallback(); + mScanHandler.removeCallbacksAndMessages(null); + mScanHandler.post(new Runnable() { + @Override + public void run() { + try { + scanner.stopScan(scanCallback); + } catch (IllegalStateException e) { + LogManager.w(TAG, "Cannot stop scan. Bluetooth may be turned off."); + } catch (NullPointerException npe) { + // Necessary because of https://code.google.com/p/android/issues/detail?id=160503 + LogManager.e(TAG, "Cannot stop scan. Unexpected NPE.", npe); + } + } + }); + } + private BluetoothLeScanner getScanner() { if (mScanner == null) { LogManager.d(TAG, "Making new Android L scanner"); From a39d2984db40eed1e625152621d19178294d92de Mon Sep 17 00:00:00 2001 From: "David G. Young" Date: Fri, 30 Sep 2016 15:42:05 -0400 Subject: [PATCH 02/12] Avoid Qark flagging vulnerabilities by creating explicit intents --- src/main/java/org/altbeacon/beacon/service/BeaconService.java | 3 +-- .../org/altbeacon/beacon/service/scanner/CycledLeScanner.java | 4 +--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/altbeacon/beacon/service/BeaconService.java b/src/main/java/org/altbeacon/beacon/service/BeaconService.java index dfe2baa77..29ba031da 100644 --- a/src/main/java/org/altbeacon/beacon/service/BeaconService.java +++ b/src/main/java/org/altbeacon/beacon/service/BeaconService.java @@ -291,8 +291,7 @@ public void onTaskRemoved(Intent rootIntent) { } private PendingIntent getRestartIntent() { - Intent restartIntent = new Intent(); - restartIntent.setClassName(getApplicationContext(), StartupBroadcastReceiver.class.getName()); + Intent restartIntent = new Intent(getApplicationContext(), StartupBroadcastReceiver.class); return getBroadcast(getApplicationContext(), 1, restartIntent, FLAG_ONE_SHOT); } diff --git a/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScanner.java b/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScanner.java index 33165cdb0..57fc004e2 100644 --- a/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScanner.java +++ b/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScanner.java @@ -339,9 +339,7 @@ protected void setWakeUpAlarm() { protected PendingIntent getWakeUpOperation() { if (mWakeUpOperation == null) { - Intent wakeupIntent = new Intent(); - //intent.setFlags(Intent.FLAG_UPDATE_CURRENT); - wakeupIntent.setClassName(mContext, StartupBroadcastReceiver.class.getName()); + Intent wakeupIntent = new Intent(mContext, StartupBroadcastReceiver.class); wakeupIntent.putExtra("wakeup", true); mWakeUpOperation = PendingIntent.getBroadcast(mContext, 0, wakeupIntent, PendingIntent.FLAG_UPDATE_CURRENT); } From c788074e7404317963c83c915a9729038fc40d7b Mon Sep 17 00:00:00 2001 From: "David G. Young" Date: Sun, 2 Oct 2016 17:43:41 -0400 Subject: [PATCH 03/12] Don't implicitly require bluetooth in manifest --- src/main/AndroidManifest.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index 2c31d988b..2000a4d87 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -2,8 +2,8 @@ package="org.altbeacon.beacon" > - - + + From d7301811e0d0961781eac38a5a456bdf3887f3ea Mon Sep 17 00:00:00 2001 From: olivier stevens Date: Tue, 8 Nov 2016 10:17:41 +0100 Subject: [PATCH 04/12] #Split: CycledLeScanner and LeScanner --- build.gradle | 27 +-- .../org/altbeacon/beacon/BeaconManager.java | 20 +++ .../beacon/service/BeaconService.java | 6 +- .../service/scanner/CycledLeScanner.java | 157 +++++++++--------- .../beacon/service/scanner/LeScanner.java | 127 ++++++++++++++ ...Mr2.java => LeScannerForJellyBeanMr2.java} | 65 ++------ ...ollipop.java => LeScannerForLollipop.java} | 85 ++++------ 7 files changed, 299 insertions(+), 188 deletions(-) create mode 100644 src/main/java/org/altbeacon/beacon/service/scanner/LeScanner.java rename src/main/java/org/altbeacon/beacon/service/scanner/{CycledLeScannerForJellyBeanMr2.java => LeScannerForJellyBeanMr2.java} (51%) rename src/main/java/org/altbeacon/beacon/service/scanner/{CycledLeScannerForLollipop.java => LeScannerForLollipop.java} (85%) diff --git a/build.gradle b/build.gradle index 5a675958b..428061933 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ ext { * Gets the version name from the latest Git tag */ def getVersionName = { - def stdout = new ByteArrayOutputStream() + /*def stdout = new ByteArrayOutputStream() try { exec { commandLine 'git', 'describe', '--tags' @@ -19,7 +19,8 @@ def getVersionName = { catch (e) { println("Can't get version from git: " + e); return "adhoc" - } + }*/ + return "2.9.1-CS6" } buildscript { @@ -31,13 +32,19 @@ buildscript { classpath 'com.android.tools.build:gradle:1.5.0' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.0' classpath 'org.jfrog.buildinfo:build-info-extractor-gradle:3.0.3' + classpath "net.researchgate:gradle-release:2.0.2" + classpath "com.github.dcendents:android-maven-plugin:1.2" } } apply plugin: 'com.android.library' - +apply plugin: 'net.researchgate.release' +apply plugin: 'com.github.dcendents.android-maven' apply from: 'gradle/eclipse.gradle' +archivesBaseName = 'android-beacon-library' +group = 'org.altbeacon' + allprojects { version = "${getVersionName()}${isSnapshot == true ? "-SNAPSHOT" : ""}" group = "org.altbeacon" @@ -66,11 +73,11 @@ android { targetCompatibility JavaVersion.VERSION_1_7 } - sourceSets { - androidTest { - setRoot('src/test') - } - } + /* sourceSets { + androidTest { + setRoot('src/test') + } + }*/ lintOptions { abortOnError false @@ -143,9 +150,9 @@ task distribution(dependsOn: [bundleEclipse, build, clean, renameAarForRelease]) println "Building with version=$version" } -task release(dependsOn: 'distribution') << { +/*task release(dependsOn: 'distribution') << { println('Doing release build') -} +}*/ android.libraryVariants.all { variant -> diff --git a/src/main/java/org/altbeacon/beacon/BeaconManager.java b/src/main/java/org/altbeacon/beacon/BeaconManager.java index b21c3a64c..b91421725 100644 --- a/src/main/java/org/altbeacon/beacon/BeaconManager.java +++ b/src/main/java/org/altbeacon/beacon/BeaconManager.java @@ -44,6 +44,7 @@ import org.altbeacon.beacon.service.RegionMonitoringState; import org.altbeacon.beacon.service.RunningAverageRssiFilter; import org.altbeacon.beacon.service.StartRMData; +import org.altbeacon.beacon.service.scanner.CycledLeScanner; import org.altbeacon.beacon.service.scanner.NonBeaconLeScanCallback; import org.altbeacon.beacon.simulator.BeaconSimulator; @@ -118,6 +119,7 @@ public class BeaconManager { private final ArrayList monitoredRegions = new ArrayList(); private final ArrayList rangedRegions = new ArrayList(); private final List beaconParsers = new CopyOnWriteArrayList<>(); + private CycledLeScanner cycledLeScanner; private NonBeaconLeScanCallback mNonBeaconLeScanCallback; private boolean mBackgroundMode = false; private boolean mBackgroundModeUninitialized = true; @@ -254,6 +256,8 @@ protected BeaconManager(Context context) { verifyServiceDeclaration(); } this.beaconParsers.add(new AltBeaconParser()); + cycledLeScanner = new CycledLeScanner(BeaconManager.DEFAULT_FOREGROUND_SCAN_PERIOD, + BeaconManager.DEFAULT_FOREGROUND_BETWEEN_SCAN_PERIOD, false); } /** @@ -996,4 +1000,20 @@ public static void setAndroidLScanningDisabled(boolean disabled) { public static void setsManifestCheckingDisabled(boolean disabled) { sManifestCheckingDisabled = disabled; } + + /** + * + * @return the CycledLeScanner to use + */ + public CycledLeScanner getCycledLeScanner(){ + return cycledLeScanner; + } + + /** + * Permits to update the way the library scans for beacon + * @param cycledLeScanner : the CycledLeScanner to use to scan for beacons + */ + public void setCycledLeScanner(CycledLeScanner cycledLeScanner){ + this.cycledLeScanner = cycledLeScanner; + } } diff --git a/src/main/java/org/altbeacon/beacon/service/BeaconService.java b/src/main/java/org/altbeacon/beacon/service/BeaconService.java index 29ba031da..18840f99f 100644 --- a/src/main/java/org/altbeacon/beacon/service/BeaconService.java +++ b/src/main/java/org/altbeacon/beacon/service/BeaconService.java @@ -197,11 +197,9 @@ public void onCreate() { // Create a private executor so we don't compete with threads used by AsyncTask // This uses fewer threads than the default executor so it won't hog CPU mExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() + 1); - - mCycledScanner = CycledLeScanner.createScanner(this, BeaconManager.DEFAULT_FOREGROUND_SCAN_PERIOD, - BeaconManager.DEFAULT_FOREGROUND_BETWEEN_SCAN_PERIOD, mBackgroundFlag, mCycledLeScanCallback, bluetoothCrashResolver); - beaconManager = BeaconManager.getInstanceForApplication(getApplicationContext()); + mCycledScanner = beaconManager.getCycledLeScanner(); + mCycledScanner.initScanner(this, mCycledLeScanCallback, bluetoothCrashResolver); //flatMap all beacon parsers boolean matchBeaconsByServiceUUID = true; diff --git a/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScanner.java b/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScanner.java index 57fc004e2..ee107889a 100644 --- a/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScanner.java +++ b/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScanner.java @@ -24,9 +24,8 @@ import java.util.Date; @TargetApi(18) -public abstract class CycledLeScanner { +public class CycledLeScanner { private static final String TAG = "CycledLeScanner"; - private BluetoothAdapter mBluetoothAdapter; private long mLastScanCycleStartTime = 0l; private long mLastScanCycleEndTime = 0l; @@ -38,41 +37,32 @@ public abstract class CycledLeScanner { protected boolean mScanningPaused; private boolean mScanCyclerStarted = false; private boolean mScanningEnabled = false; - protected final Context mContext; + protected Context mContext; private long mScanPeriod; protected long mBetweenScanPeriod; protected final Handler mHandler = new Handler(Looper.getMainLooper()); - protected final Handler mScanHandler; - private final HandlerThread mScanThread; - protected final BluetoothCrashResolver mBluetoothCrashResolver; - protected final CycledLeScanCallback mCycledLeScanCallback; protected boolean mBackgroundFlag = false; protected boolean mRestartNeeded = false; + protected LeScanner leScanner; private static final long ANDROID_N_MIN_SCAN_CYCLE_MILLIS = 6000l; - protected CycledLeScanner(Context context, long scanPeriod, long betweenScanPeriod, boolean backgroundFlag, CycledLeScanCallback cycledLeScanCallback, BluetoothCrashResolver crashResolver) { + public CycledLeScanner(long scanPeriod, long betweenScanPeriod, boolean backgroundFlag) { mScanPeriod = scanPeriod; mBetweenScanPeriod = betweenScanPeriod; - mContext = context; - mCycledLeScanCallback = cycledLeScanCallback; - mBluetoothCrashResolver = crashResolver; mBackgroundFlag = backgroundFlag; - - mScanThread = new HandlerThread("CycledLeScannerThread"); - mScanThread.start(); - mScanHandler = new Handler(mScanThread.getLooper()); } - public static CycledLeScanner createScanner(Context context, long scanPeriod, long betweenScanPeriod, boolean backgroundFlag, CycledLeScanCallback cycledLeScanCallback, BluetoothCrashResolver crashResolver) { + public boolean initScanner(Context context, CycledLeScanCallback cycledLeScanCallback, BluetoothCrashResolver crashResolver) { + mContext = context; boolean useAndroidLScanner; if (android.os.Build.VERSION.SDK_INT < 18) { LogManager.w(TAG, "Not supported prior to API 18."); - return null; + return false; } if (android.os.Build.VERSION.SDK_INT < 21) { @@ -89,17 +79,18 @@ public static CycledLeScanner createScanner(Context context, long scanPeriod, lo } if (useAndroidLScanner) { - return new CycledLeScannerForLollipop(context, scanPeriod, betweenScanPeriod, backgroundFlag, cycledLeScanCallback, crashResolver); + leScanner = new LeScannerForLollipop(context, cycledLeScanCallback, crashResolver); } else { - return new CycledLeScannerForJellyBeanMr2(context, scanPeriod, betweenScanPeriod, backgroundFlag, cycledLeScanCallback, crashResolver); + leScanner = new LeScannerForJellyBeanMr2(context, cycledLeScanCallback, crashResolver); } - + return true; } /** * 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 * between LOW_POWER_MODE vs. LOW_LATENCY_MODE + * * @param backgroundFlag */ public void setScanPeriods(long scanPeriod, long betweenScanPeriod, boolean backgroundFlag) { @@ -109,6 +100,11 @@ public void setScanPeriods(long scanPeriod, long betweenScanPeriod, boolean back mRestartNeeded = true; } mBackgroundFlag = backgroundFlag; + if (backgroundFlag) { + leScanner.onBackground(); + } else { + leScanner.onForeground(); + } mScanPeriod = scanPeriod; mBetweenScanPeriod = betweenScanPeriod; if (mBackgroundFlag) { @@ -159,26 +155,21 @@ public void stop() { if (mScanCyclerStarted) { scanLeDevice(false); } - if (mBluetoothAdapter != null) { + if (leScanner.getBluetoothAdapter() != null) { stopScan(); mLastScanCycleEndTime = SystemClock.elapsedRealtime(); } } public void destroy() { - mScanThread.quit(); + leScanner.onDestroy(); } - protected abstract void stopScan(); - - protected abstract boolean deferScanIfNeeded(); - - protected abstract void startScan(); @SuppressLint("NewApi") protected void scanLeDevice(final Boolean enable) { mScanCyclerStarted = true; - if (getBluetoothAdapter() == null) { + if (leScanner.getBluetoothAdapter() == null) { LogManager.e(TAG, "No Bluetooth adapter. beaconService cannot scan."); } if (enable) { @@ -190,9 +181,11 @@ protected void scanLeDevice(final Boolean enable) { mScanning = true; mScanningPaused = false; try { - if (getBluetoothAdapter() != null) { - if (getBluetoothAdapter().isEnabled()) { - if (mBluetoothCrashResolver != null && mBluetoothCrashResolver.isRecoveryInProgress()) { + BluetoothAdapter bluetoothAdapter = leScanner.getBluetoothAdapter(); + if (bluetoothAdapter != null) { + if (bluetoothAdapter.isEnabled()) { + BluetoothCrashResolver bluetoothCrashResolver = leScanner.getBluetoothCrashResolver(); + if (bluetoothCrashResolver != null && bluetoothCrashResolver.isRecoveryInProgress()) { LogManager.w(TAG, "Skipping scan because crash recovery is in progress."); } else { if (mScanningEnabled) { @@ -237,6 +230,39 @@ protected void scanLeDevice(final Boolean enable) { } } + + protected void stopScan() { + leScanner.stopScan(); + } + + protected boolean deferScanIfNeeded() { + long millisecondsUntilStart = mNextScanCycleStartTime - SystemClock.elapsedRealtime(); + boolean deferScanIsNeeded = millisecondsUntilStart > 0; + LogManager.d(TAG, "defer scan is needed %b", deferScanIsNeeded); + if (leScanner.onDeferScanIfNeeded(deferScanIsNeeded)) { + LogManager.d(TAG, "plan a wakeup alarm"); + setWakeUpAlarm(); + } + if (deferScanIsNeeded) { + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + scanLeDevice(true); + } + }, millisecondsUntilStart > 1000 ? 1000 : millisecondsUntilStart); + } + return deferScanIsNeeded; + } + + protected void startScan() { + leScanner.startScan(); + } + + protected void finishScan() { + leScanner.finishScan(); + mScanningPaused = true; + } + protected void scheduleScanCycleStop() { // Stops scanning after a pre-defined scan period. long millisecondsUntilStop = mScanCycleStopTime - SystemClock.elapsedRealtime(); @@ -257,40 +283,36 @@ public void run() { } } - protected abstract void finishScan(); private void finishScanCycle() { LogManager.d(TAG, "Done with scan cycle"); - mCycledLeScanCallback.onCycleEnd(); + leScanner.getCycledLeScanCallback().onCycleEnd(); if (mScanning) { - if (getBluetoothAdapter() != null) { - if (getBluetoothAdapter().isEnabled()) { - long now = System.currentTimeMillis(); - if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && - mBetweenScanPeriod+mScanPeriod < ANDROID_N_MIN_SCAN_CYCLE_MILLIS && - now-mLastScanStopTime < ANDROID_N_MIN_SCAN_CYCLE_MILLIS) { - // As of Android N, only 5 scans may be started in a 30 second period (6 - // seconds per cycle) otherwise they are blocked. So we check here to see - // if the scan period is 6 seconds or less, and if we last stopped scanning - // fewer than 6 seconds ag and if so, we simply do not stop scanning - LogManager.d(TAG, "Not stopping scan because this is Android N and we" + - " keep scanning for a minimum of 6 seconds at a time. "+ - "We will stop in "+(ANDROID_N_MIN_SCAN_CYCLE_MILLIS-(now-mLastScanStopTime))+" millisconds."); - } - else { - try { - LogManager.d(TAG, "stopping bluetooth le scan"); - finishScan(); - mLastScanStopTime = now; - } catch (Exception e) { - LogManager.w(e, TAG, "Internal Android exception scanning for beacons"); - } - } - - mLastScanCycleEndTime = SystemClock.elapsedRealtime(); + BluetoothAdapter bluetoothAdapter = leScanner.getBluetoothAdapter(); + if (bluetoothAdapter != null && bluetoothAdapter.isEnabled()) { + long now = System.currentTimeMillis(); + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && + mBetweenScanPeriod + mScanPeriod < ANDROID_N_MIN_SCAN_CYCLE_MILLIS && + now - mLastScanStopTime < ANDROID_N_MIN_SCAN_CYCLE_MILLIS) { + // As of Android N, only 5 scans may be started in a 30 second period (6 + // seconds per cycle) otherwise they are blocked. So we check here to see + // if the scan period is 6 seconds or less, and if we last stopped scanning + // fewer than 6 seconds ag and if so, we simply do not stop scanning + LogManager.d(TAG, "Not stopping scan because this is Android N and we" + + " keep scanning for a minimum of 6 seconds at a time. " + + "We will stop in " + (ANDROID_N_MIN_SCAN_CYCLE_MILLIS - (now - mLastScanStopTime)) + " millisconds."); } else { - LogManager.d(TAG, "Bluetooth is disabled. Cannot scan for beacons."); + try { + LogManager.d(TAG, "stopping bluetooth le scan"); + finishScan(); + mLastScanStopTime = now; + } catch (Exception e) { + LogManager.w(e, TAG, "Internal Android exception scanning for beacons"); + } } + mLastScanCycleEndTime = SystemClock.elapsedRealtime(); + } else { + LogManager.d(TAG, "Bluetooth is disabled. Cannot scan for beacons."); } mNextScanCycleStartTime = getNextScanStartTime(); if (mScanningEnabled) { @@ -304,19 +326,6 @@ private void finishScanCycle() { } } - protected BluetoothAdapter getBluetoothAdapter() { - if (mBluetoothAdapter == null) { - // Initializes Bluetooth adapter. - final BluetoothManager bluetoothManager = - (BluetoothManager) mContext.getApplicationContext().getSystemService(Context.BLUETOOTH_SERVICE); - mBluetoothAdapter = bluetoothManager.getAdapter(); - if (mBluetoothAdapter == null) { - LogManager.w(TAG, "Failed to construct a BluetoothAdapter"); - } - } - return mBluetoothAdapter; - } - private PendingIntent mWakeUpOperation = null; @@ -371,11 +380,11 @@ private long getNextScanStartTime() { return SystemClock.elapsedRealtime(); } long fullScanCycle = mScanPeriod + mBetweenScanPeriod; - long normalizedBetweenScanPeriod = mBetweenScanPeriod-(SystemClock.elapsedRealtime() % fullScanCycle); + long normalizedBetweenScanPeriod = mBetweenScanPeriod - (SystemClock.elapsedRealtime() % fullScanCycle); LogManager.d(TAG, "Normalizing between scan period from %s to %s", mBetweenScanPeriod, normalizedBetweenScanPeriod); - return SystemClock.elapsedRealtime()+normalizedBetweenScanPeriod; + return SystemClock.elapsedRealtime() + normalizedBetweenScanPeriod; } private boolean checkLocationPermission() { diff --git a/src/main/java/org/altbeacon/beacon/service/scanner/LeScanner.java b/src/main/java/org/altbeacon/beacon/service/scanner/LeScanner.java new file mode 100644 index 000000000..ad86c87a3 --- /dev/null +++ b/src/main/java/org/altbeacon/beacon/service/scanner/LeScanner.java @@ -0,0 +1,127 @@ +package org.altbeacon.beacon.service.scanner; + +import android.annotation.TargetApi; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothManager; +import android.content.Context; +import android.os.Build; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.SystemClock; + +import org.altbeacon.beacon.logging.LogManager; +import org.altbeacon.bluetooth.BluetoothCrashResolver; + +/** + * Created by Connecthings on 07/11/16. + */ +public abstract class LeScanner { + + private static String TAG = "LeScanner"; + + private Context mContext; + private CycledLeScanCallback mCycledLeScanCallback; + private BluetoothCrashResolver mBluetoothCrashResolver; + private BluetoothAdapter mBluetoothAdapter; + private boolean mBackgroundFlag; + private long mLastDetectionTime = 0l; + private final Handler mScanHandler; + private final HandlerThread mScanThread; + + public LeScanner(Context context,CycledLeScanCallback cycledLeScanCallback, BluetoothCrashResolver bluetoothCrashResolver) { + this.mContext = context; + this.mCycledLeScanCallback = cycledLeScanCallback; + this.mBluetoothCrashResolver = bluetoothCrashResolver; + mScanThread = new HandlerThread("CycledLeScannerThread"); + mScanThread.start(); + mScanHandler = new Handler(mScanThread.getLooper()); + } + + void onBackground(){ + this.mBackgroundFlag = true; + } + + void onForeground(){ + this.mBackgroundFlag = false; + } + + void onDestroy(){ + mScanThread.quit(); + } + + protected Handler getScanHandler(){ + return mScanHandler; + } + + protected void postStopLeScan() { + Runnable stopRunnable = generateStopScanRunnable(); + if(stopRunnable == null){ + return; + } + mScanHandler.removeCallbacksAndMessages(null); + mScanHandler.post(stopRunnable); + } + + protected void postStartLeScan() { + Runnable startRunnable = generateStartScanRunnable(); + if(startRunnable == null){ + return; + } + mScanHandler.removeCallbacksAndMessages(null); + mScanHandler.post(startRunnable); + } + + protected void stopScan() { + postStopLeScan(); + } + + + protected void startScan() { + postStartLeScan(); + } + + protected void finishScan() { + postStopLeScan(); + } + + abstract boolean onDeferScanIfNeeded(boolean deferScanIsNeeded); + + abstract Runnable generateStopScanRunnable(); + + abstract Runnable generateStartScanRunnable(); + + protected boolean getBackgroundFlag(){ + return mBackgroundFlag; + } + + protected CycledLeScanCallback getCycledLeScanCallback() { + return mCycledLeScanCallback; + } + + + protected BluetoothCrashResolver getBluetoothCrashResolver() { + return mBluetoothCrashResolver; + } + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) + protected BluetoothAdapter getBluetoothAdapter() { + if (mBluetoothAdapter == null) { + // Initializes Bluetooth adapter. + final BluetoothManager bluetoothManager = + (BluetoothManager) mContext.getApplicationContext().getSystemService(Context.BLUETOOTH_SERVICE); + mBluetoothAdapter = bluetoothManager.getAdapter(); + if (mBluetoothAdapter == null) { + LogManager.w(TAG, "Failed to construct a BluetoothAdapter"); + } + } + return mBluetoothAdapter; + } + + public long getLastDetectionTime() { + return mLastDetectionTime; + } + public void recordDetection() { + mLastDetectionTime = SystemClock.elapsedRealtime(); + } + +} diff --git a/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerForJellyBeanMr2.java b/src/main/java/org/altbeacon/beacon/service/scanner/LeScannerForJellyBeanMr2.java similarity index 51% rename from src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerForJellyBeanMr2.java rename to src/main/java/org/altbeacon/beacon/service/scanner/LeScannerForJellyBeanMr2.java index b38f7281c..0b8726ac1 100644 --- a/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerForJellyBeanMr2.java +++ b/src/main/java/org/altbeacon/beacon/service/scanner/LeScannerForJellyBeanMr2.java @@ -10,61 +10,29 @@ import org.altbeacon.bluetooth.BluetoothCrashResolver; @TargetApi(18) -public class CycledLeScannerForJellyBeanMr2 extends CycledLeScanner { +public class LeScannerForJellyBeanMr2 extends LeScanner { private static final String TAG = "CycledLeScannerForJellyBeanMr2"; private BluetoothAdapter.LeScanCallback leScanCallback; - public CycledLeScannerForJellyBeanMr2(Context context, long scanPeriod, long betweenScanPeriod, boolean backgroundFlag, CycledLeScanCallback cycledLeScanCallback, BluetoothCrashResolver crashResolver) { - super(context, scanPeriod, betweenScanPeriod, backgroundFlag, cycledLeScanCallback, crashResolver); + public LeScannerForJellyBeanMr2(Context context, CycledLeScanCallback cycledLeScanCallback, BluetoothCrashResolver crashResolver) { + super(context, cycledLeScanCallback, crashResolver); } - @Override - protected void stopScan() { - postStopLeScan(); - } - @Override - protected boolean deferScanIfNeeded() { - long millisecondsUntilStart = mNextScanCycleStartTime - SystemClock.elapsedRealtime(); - if (millisecondsUntilStart > 0) { - LogManager.d(TAG, "Waiting to start next Bluetooth scan for another %s milliseconds", - millisecondsUntilStart); - // Don't actually wait until the next scan time -- only wait up to 1 second. This - // allows us to start scanning sooner if a consumer enters the foreground and expects - // results more quickly. - if (mBackgroundFlag) { - setWakeUpAlarm(); - } - mHandler.postDelayed(new Runnable() { - @Override - public void run() { - scanLeDevice(true); - } - }, millisecondsUntilStart > 1000 ? 1000 : millisecondsUntilStart); - return true; + protected boolean onDeferScanIfNeeded(boolean deferScanIsNeeded) { + if (deferScanIsNeeded) { + return getBackgroundFlag(); } return false; } - @Override - protected void startScan() { - postStartLeScan(); - } - - @Override - protected void finishScan() { - postStopLeScan(); - mScanningPaused = true; - } - - private void postStartLeScan() { + Runnable generateStartScanRunnable() { final BluetoothAdapter bluetoothAdapter = getBluetoothAdapter(); if (bluetoothAdapter == null) { - return; + return null; } final BluetoothAdapter.LeScanCallback leScanCallback = getLeScanCallback(); - mScanHandler.removeCallbacksAndMessages(null); - mScanHandler.post(new Runnable() { + return new Runnable() { @Override public void run() { try { @@ -74,17 +42,16 @@ public void run() { LogManager.e(e, TAG, "Internal Android exception in startLeScan()"); } } - }); + }; } - private void postStopLeScan() { + Runnable generateStopScanRunnable() { final BluetoothAdapter bluetoothAdapter = getBluetoothAdapter(); if (bluetoothAdapter == null) { - return; + return null; } final BluetoothAdapter.LeScanCallback leScanCallback = getLeScanCallback(); - mScanHandler.removeCallbacksAndMessages(null); - mScanHandler.post(new Runnable() { + return new Runnable() { @Override public void run() { try { @@ -94,7 +61,7 @@ public void run() { LogManager.e(e, TAG, "Internal Android exception in stopLeScan()"); } } - }); + }; } private BluetoothAdapter.LeScanCallback getLeScanCallback() { @@ -106,8 +73,8 @@ private BluetoothAdapter.LeScanCallback getLeScanCallback() { public void onLeScan(final BluetoothDevice device, final int rssi, final byte[] scanRecord) { LogManager.d(TAG, "got record"); - mCycledLeScanCallback.onLeScan(device, rssi, scanRecord); - mBluetoothCrashResolver.notifyScannedDevice(device, getLeScanCallback()); + getLeScanCallback().onLeScan(device, rssi, scanRecord); + getBluetoothCrashResolver().notifyScannedDevice(device, getLeScanCallback()); } }; } diff --git a/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerForLollipop.java b/src/main/java/org/altbeacon/beacon/service/scanner/LeScannerForLollipop.java similarity index 85% rename from src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerForLollipop.java rename to src/main/java/org/altbeacon/beacon/service/scanner/LeScannerForLollipop.java index e5ac14d65..b912215f3 100644 --- a/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerForLollipop.java +++ b/src/main/java/org/altbeacon/beacon/service/scanner/LeScannerForLollipop.java @@ -20,7 +20,8 @@ import java.util.List; @TargetApi(21) -public class CycledLeScannerForLollipop extends CycledLeScanner { +public class LeScannerForLollipop extends LeScanner { + private static final String TAG = "CycledLeScannerForLollipop"; private static final long BACKGROUND_L_SCAN_DETECTION_PERIOD_MILLIS = 10000l; private BluetoothLeScanner mScanner; @@ -31,15 +32,12 @@ public class CycledLeScannerForLollipop extends CycledLeScanner { private boolean mMainScanCycleActive = false; private final BeaconManager mBeaconManager; - public CycledLeScannerForLollipop(Context context, long scanPeriod, long betweenScanPeriod, boolean backgroundFlag, CycledLeScanCallback cycledLeScanCallback, BluetoothCrashResolver crashResolver) { - super(context, scanPeriod, betweenScanPeriod, backgroundFlag, cycledLeScanCallback, crashResolver); - mBeaconManager = BeaconManager.getInstanceForApplication(mContext); + public LeScannerForLollipop(Context context, CycledLeScanCallback cycledLeScanCallback, BluetoothCrashResolver crashResolver) { + super(context, cycledLeScanCallback, crashResolver); + mBeaconManager = BeaconManager.getInstanceForApplication(context); } - @Override - protected void stopScan() { - postStopLeScan(); - } + /* Android 5 background scan algorithm (largely handled in this method) @@ -73,10 +71,10 @@ then no beacons will be detected until the next scan cycle starts (5 minutes max will get a callback within a few seconds on Android L vs. up to 5 minutes on older operating system versions. */ - protected boolean deferScanIfNeeded() { + protected boolean onDeferScanIfNeeded(boolean deferScanIsNeeded) { // This method is called to see if it is time to start a scan - long millisecondsUntilStart = mNextScanCycleStartTime - SystemClock.elapsedRealtime(); - if (millisecondsUntilStart > 0) { + boolean setWakeUpAlarm = false; + if (deferScanIsNeeded) { mMainScanCycleActive = false; if (true) { long secsSinceLastDetection = SystemClock.elapsedRealtime() - @@ -119,29 +117,18 @@ protected boolean deferScanIfNeeded() { else { // report the results up the chain LogManager.d(TAG, "Delivering Android L background scanning results"); - mCycledLeScanCallback.onCycleEnd(); + getCycledLeScanCallback().onCycleEnd(); } } } } - LogManager.d(TAG, "Waiting to start full Bluetooth scan for another %s milliseconds", - millisecondsUntilStart); + // Don't actually wait until the next scan time -- only wait up to 1 second. This // allows us to start scanning sooner if a consumer enters the foreground and expects // results more quickly. - if (mScanDeferredBefore == false && mBackgroundFlag) { - setWakeUpAlarm(); - } - mHandler.postDelayed(new Runnable() { - @Override - public void run() { - scanLeDevice(true); - } - }, millisecondsUntilStart > 1000 ? 1000 : millisecondsUntilStart); + setWakeUpAlarm = !mScanDeferredBefore && getBackgroundFlag(); mScanDeferredBefore = true; - return true; - } - else { + } else { if (mBackgroundLScanStartTime > 0l) { stopScan(); mBackgroundLScanStartTime = 0; @@ -149,15 +136,23 @@ public void run() { mScanDeferredBefore = false; mMainScanCycleActive = true; } - return false; + return setWakeUpAlarm; } + + @Override - protected void startScan() { + protected void finishScan() { + LogManager.d(TAG, "Stopping scan"); + stopScan(); + } + + + Runnable generateStartScanRunnable(){ List filters = new ArrayList(); ScanSettings settings; - if (mBackgroundFlag && !mMainScanCycleActive) { + if (getBackgroundFlag() && !mMainScanCycleActive) { LogManager.d(TAG, "starting filtered scan in SCAN_MODE_LOW_POWER"); settings = (new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)).build(); filters = new ScanFilterUtils().createScanFiltersForBeaconParsers( @@ -167,24 +162,13 @@ protected void startScan() { settings = (new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)).build(); } - postStartLeScan(filters, settings); + return generateStartScanRunnable(filters, settings); } - @Override - protected void finishScan() { - LogManager.d(TAG, "Stopping scan"); - stopScan(); - mScanningPaused = true; - } - - private void postStartLeScan(final List filters, final ScanSettings settings) { + private Runnable generateStartScanRunnable(final List filters, final ScanSettings settings) { final BluetoothLeScanner scanner = getScanner(); - if (scanner == null) { - return; - } final ScanCallback scanCallback = getNewLeScanCallback(); - mScanHandler.removeCallbacksAndMessages(null); - mScanHandler.post(new Runnable() { + return new Runnable() { @Override public void run() { try { @@ -196,17 +180,16 @@ public void run() { LogManager.e(TAG, "Cannot start scan. Unexpected NPE.", npe); } } - }); + }; } - private void postStopLeScan() { + Runnable generateStopScanRunnable() { final BluetoothLeScanner scanner = getScanner(); if (scanner == null) { - return; + return null; } final ScanCallback scanCallback = getNewLeScanCallback(); - mScanHandler.removeCallbacksAndMessages(null); - mScanHandler.post(new Runnable() { + return new Runnable() { @Override public void run() { try { @@ -218,7 +201,7 @@ public void run() { LogManager.e(TAG, "Cannot stop scan. Unexpected NPE.", npe); } } - }); + }; } private BluetoothLeScanner getScanner() { @@ -250,7 +233,7 @@ public void onScanResult(int callbackType, ScanResult scanResult) { } } } - mCycledLeScanCallback.onLeScan(scanResult.getDevice(), + getCycledLeScanCallback().onLeScan(scanResult.getDevice(), scanResult.getRssi(), scanResult.getScanRecord().getBytes()); if (mBackgroundLScanStartTime > 0) { LogManager.d(TAG, "got a filtered scan result in the background."); @@ -261,7 +244,7 @@ public void onScanResult(int callbackType, ScanResult scanResult) { public void onBatchScanResults(List results) { LogManager.d(TAG, "got batch records"); for (ScanResult scanResult : results) { - mCycledLeScanCallback.onLeScan(scanResult.getDevice(), + getCycledLeScanCallback().onLeScan(scanResult.getDevice(), scanResult.getRssi(), scanResult.getScanRecord().getBytes()); } if (mBackgroundLScanStartTime > 0) { From 16faeb32903017deae0431288cb6be67bc4460a2 Mon Sep 17 00:00:00 2001 From: olivier stevens Date: Tue, 8 Nov 2016 14:51:49 +0100 Subject: [PATCH 05/12] #ScanPeriods: introduce the ScanPeriods for esiest management of the scan periods --- .../service/scanner/CycledLeScanner.java | 152 +++++++++++++----- .../beacon/service/scanner/ScanPeriods.java | 62 +++++++ 2 files changed, 175 insertions(+), 39 deletions(-) create mode 100644 src/main/java/org/altbeacon/beacon/service/scanner/ScanPeriods.java diff --git a/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScanner.java b/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScanner.java index ee107889a..d9a7b3ceb 100644 --- a/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScanner.java +++ b/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScanner.java @@ -6,13 +6,11 @@ import android.app.AlarmManager; import android.app.PendingIntent; import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothManager; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.os.Build; import android.os.Handler; -import android.os.HandlerThread; import android.os.Looper; import android.os.SystemClock; @@ -32,15 +30,35 @@ public class CycledLeScanner { protected long mNextScanCycleStartTime = 0l; private long mScanCycleStopTime = 0l; private long mLastScanStopTime = 0l; + /** + * Link to the Active Scan Period + */ + private long mNextScanCycleActiveStartTime = 0l; + /** + * Link to the Active Scan Period + */ + private long mScanCycleActiveStopTime = 0l; private boolean mScanning; protected boolean mScanningPaused; + private boolean mInScanningPeriod; private boolean mScanCyclerStarted = false; private boolean mScanningEnabled = false; protected Context mContext; - private long mScanPeriod; - protected long mBetweenScanPeriod; + private ScanPeriods mCurrentScanPeriods; + /** + * We introduce the notion of "Active Scan". + * + * We notice that on Android 6 and before, that when scanning for 30s., + * the result of the scan comes mostly at the end of the 30s.. + * + * We notice as well, that if a device scans several times 1s. during 30s., + * it get a faster scan result (and more precise) than if scanning one time 30s. + * + * The activeScanPeriod permits to achieve to scan several time 1s during 30s to get a scan result as fast as possible. + */ + private ScanPeriods mActiveScanPeriods; protected final Handler mHandler = new Handler(Looper.getMainLooper()); @@ -49,11 +67,25 @@ public class CycledLeScanner { protected boolean mRestartNeeded = false; protected LeScanner leScanner; + private final Runnable runableStartScan = new Runnable() { + @Override + public void run() { + scanLeDevice(true); + } + }; + + private final Runnable runableStopScan = new Runnable() { + @Override + public void run() { + scheduleScanCycleStop(); + } + }; + private static final long ANDROID_N_MIN_SCAN_CYCLE_MILLIS = 6000l; public CycledLeScanner(long scanPeriod, long betweenScanPeriod, boolean backgroundFlag) { - mScanPeriod = scanPeriod; - mBetweenScanPeriod = betweenScanPeriod; + mCurrentScanPeriods = new ScanPeriods(scanPeriod, betweenScanPeriod); + createActiveScanPeriods(); mBackgroundFlag = backgroundFlag; } @@ -86,6 +118,11 @@ public boolean initScanner(Context context, CycledLeScanCallback cycledLeScanCal return true; } + private void createActiveScanPeriods(){ + mActiveScanPeriods = new ScanPeriods(Math.min(mCurrentScanPeriods.getScanPeriod(), BeaconManager.DEFAULT_FOREGROUND_SCAN_PERIOD), + Math.min(mCurrentScanPeriods.getBetweenScanPeriod(), BeaconManager.DEFAULT_BACKGROUND_BETWEEN_SCAN_PERIOD)); + } + /** * 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 @@ -105,8 +142,9 @@ public void setScanPeriods(long scanPeriod, long betweenScanPeriod, boolean back } else { leScanner.onForeground(); } - mScanPeriod = scanPeriod; - mBetweenScanPeriod = betweenScanPeriod; + + mCurrentScanPeriods = new ScanPeriods(scanPeriod, betweenScanPeriod); + createActiveScanPeriods(); if (mBackgroundFlag) { LogManager.d(TAG, "We are in the background. Setting wakeup alarm"); setWakeUpAlarm(); @@ -119,11 +157,19 @@ public void setScanPeriods(long scanPeriod, long betweenScanPeriod, boolean back // We are waiting to start scanning. We may need to adjust the next start time // only do an adjustment if we need to make it happen sooner. Otherwise, it will // take effect on the next cycle. - long proposedNextScanStartTime = (mLastScanCycleEndTime + betweenScanPeriod); - if (proposedNextScanStartTime < mNextScanCycleStartTime) { - mNextScanCycleStartTime = proposedNextScanStartTime; - LogManager.i(TAG, "Adjusted nextScanStartTime to be %s", - new Date(mNextScanCycleStartTime - SystemClock.elapsedRealtime() + System.currentTimeMillis())); + if(mBackgroundFlag) { + long proposedNextScanStartTime = (mLastScanCycleEndTime + betweenScanPeriod); + if (proposedNextScanStartTime < mNextScanCycleStartTime) { + mNextScanCycleStartTime = proposedNextScanStartTime; + LogManager.i(TAG, "Adjusted nextScanStartTime to be %s", + new Date(mNextScanCycleStartTime - SystemClock.elapsedRealtime() + System.currentTimeMillis())); + } + }else{ + //If we switch from background to foreground we would like the scan to start right now + cancelRunnableStartAndStopScan(); + mNextScanCycleActiveStartTime = now; + mNextScanCycleStartTime = now; + mHandler.post(runableStartScan); } } if (mScanCycleStopTime > now) { @@ -217,7 +263,13 @@ protected void scanLeDevice(final Boolean enable) { } else { LogManager.d(TAG, "We are already scanning"); } - mScanCycleStopTime = (SystemClock.elapsedRealtime() + mScanPeriod); + + long realTime = SystemClock.elapsedRealtime(); + if(mScanCycleStopTime < realTime && !mInScanningPeriod){ + mScanCycleStopTime = SystemClock.elapsedRealtime() + mCurrentScanPeriods.getScanPeriod(); + mInScanningPeriod = true; + } + mScanCycleActiveStopTime = SystemClock.elapsedRealtime() + mActiveScanPeriods.getScanPeriod(); scheduleScanCycleStop(); LogManager.d(TAG, "Scan started"); @@ -235,8 +287,12 @@ protected void stopScan() { leScanner.stopScan(); } + protected long calculateNextTimeToStartScan(){ + return calculateNextDelay(mNextScanCycleStartTime, mNextScanCycleActiveStartTime); + } + protected boolean deferScanIfNeeded() { - long millisecondsUntilStart = mNextScanCycleStartTime - SystemClock.elapsedRealtime(); + long millisecondsUntilStart = calculateNextTimeToStartScan(); boolean deferScanIsNeeded = millisecondsUntilStart > 0; LogManager.d(TAG, "defer scan is needed %b", deferScanIsNeeded); if (leScanner.onDeferScanIfNeeded(deferScanIsNeeded)) { @@ -244,12 +300,7 @@ protected boolean deferScanIfNeeded() { setWakeUpAlarm(); } if (deferScanIsNeeded) { - mHandler.postDelayed(new Runnable() { - @Override - public void run() { - scanLeDevice(true); - } - }, millisecondsUntilStart > 1000 ? 1000 : millisecondsUntilStart); + postDelayed(runableStartScan, millisecondsUntilStart > 1000 ? 1000 : millisecondsUntilStart); } return deferScanIsNeeded; } @@ -263,26 +314,41 @@ protected void finishScan() { mScanningPaused = true; } + protected long calculateNextDelay(long referenceTime1, long referenceTime2){ + return Math.min(referenceTime1,referenceTime2) - SystemClock.elapsedRealtime(); + } + + protected long calculateNextTimeForScanCycleStop(){ + return calculateNextDelay(mScanCycleStopTime, mScanCycleActiveStopTime); + } + protected void scheduleScanCycleStop() { // Stops scanning after a pre-defined scan period. - long millisecondsUntilStop = mScanCycleStopTime - SystemClock.elapsedRealtime(); + long millisecondsUntilStop = calculateNextTimeForScanCycleStop(); if (millisecondsUntilStop > 0) { LogManager.d(TAG, "Waiting to stop scan cycle for another %s milliseconds", millisecondsUntilStop); if (mBackgroundFlag) { setWakeUpAlarm(); } - mHandler.postDelayed(new Runnable() { - @Override - public void run() { - scheduleScanCycleStop(); - } - }, millisecondsUntilStop > 1000 ? 1000 : millisecondsUntilStop); + postDelayed(runableStopScan, millisecondsUntilStop > 1000 ? 1000 : millisecondsUntilStop); } else { finishScanCycle(); } } + protected void cancelRunnable(Runnable runnable){ + mHandler.removeCallbacks(runnable); + } + + protected void postDelayed(Runnable runnable, long delay){ + mHandler.postDelayed(runnable, delay); + } + + protected void cancelRunnableStartAndStopScan(){ + cancelRunnable(runableStartScan); + cancelRunnable(runableStopScan); + } private void finishScanCycle() { LogManager.d(TAG, "Done with scan cycle"); @@ -292,7 +358,7 @@ private void finishScanCycle() { if (bluetoothAdapter != null && bluetoothAdapter.isEnabled()) { long now = System.currentTimeMillis(); if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && - mBetweenScanPeriod + mScanPeriod < ANDROID_N_MIN_SCAN_CYCLE_MILLIS && + mCurrentScanPeriods.getFullPeriod() < ANDROID_N_MIN_SCAN_CYCLE_MILLIS && now - mLastScanStopTime < ANDROID_N_MIN_SCAN_CYCLE_MILLIS) { // As of Android N, only 5 scans may be started in a 30 second period (6 // seconds per cycle) otherwise they are blocked. So we check here to see @@ -314,7 +380,15 @@ private void finishScanCycle() { } else { LogManager.d(TAG, "Bluetooth is disabled. Cannot scan for beacons."); } - mNextScanCycleStartTime = getNextScanStartTime(); + + if(mScanCycleStopTime < SystemClock.elapsedRealtime() && mInScanningPeriod){ + mNextScanCycleStartTime = getNextScanStartTime(mCurrentScanPeriods); + mNextScanCycleActiveStartTime = mNextScanCycleStartTime; + mInScanningPeriod = false; + }else{ + mNextScanCycleActiveStartTime = getNextScanStartTime(mActiveScanPeriods); + } + if (mScanningEnabled) { scanLeDevice(true); } @@ -334,11 +408,11 @@ private void finishScanCycle() { protected void setWakeUpAlarm() { // wake up time will be the maximum of 5 minutes, the scan period, the between scan period long milliseconds = 1000l * 60 * 5; /* five minutes */ - if (milliseconds < mBetweenScanPeriod) { - milliseconds = mBetweenScanPeriod; + if (milliseconds < mCurrentScanPeriods.getBetweenScanPeriod()) { + milliseconds = mCurrentScanPeriods.getBetweenScanPeriod(); } - if (milliseconds < mScanPeriod) { - milliseconds = mScanPeriod; + if (milliseconds < mCurrentScanPeriods.getScanPeriod()) { + milliseconds = mCurrentScanPeriods.getScanPeriod(); } AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); @@ -367,7 +441,7 @@ protected void cancelWakeUpAlarm() { } - private long getNextScanStartTime() { + private long getNextScanStartTime(ScanPeriods referenceScanPeriods) { // Because many apps may use this library on the same device, we want to try to synchronize // scanning as much as possible in order to save battery. Therefore, we will set the scan // intervals to be on a predictable interval using a modulus of the system time. This may @@ -376,12 +450,12 @@ private long getNextScanStartTime() { // will all be doing scans at the same time, thereby saving battery when none are scanning. // This, of course, won't help at all if people set custom scan periods. But since most // people accept the defaults, this will likely have a positive effect. - if (mBetweenScanPeriod == 0) { + if (referenceScanPeriods.getBetweenScanPeriod() == 0) { return SystemClock.elapsedRealtime(); } - long fullScanCycle = mScanPeriod + mBetweenScanPeriod; - long normalizedBetweenScanPeriod = mBetweenScanPeriod - (SystemClock.elapsedRealtime() % fullScanCycle); - LogManager.d(TAG, "Normalizing between scan period from %s to %s", mBetweenScanPeriod, + long fullScanCycle = referenceScanPeriods.getFullPeriod(); + long normalizedBetweenScanPeriod = referenceScanPeriods.getBetweenScanPeriod() - (SystemClock.elapsedRealtime() % fullScanCycle); + LogManager.d(TAG, "Normalizing between scan period from %s to %s", referenceScanPeriods.getBetweenScanPeriod(), normalizedBetweenScanPeriod); return SystemClock.elapsedRealtime() + normalizedBetweenScanPeriod; diff --git a/src/main/java/org/altbeacon/beacon/service/scanner/ScanPeriods.java b/src/main/java/org/altbeacon/beacon/service/scanner/ScanPeriods.java new file mode 100644 index 000000000..26b3cd5c2 --- /dev/null +++ b/src/main/java/org/altbeacon/beacon/service/scanner/ScanPeriods.java @@ -0,0 +1,62 @@ +package org.altbeacon.beacon.service.scanner; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + */ +public class ScanPeriods implements Parcelable{ + + private long scanPeriod; + private long betweenScanPeriod; + private long fullPeriod; + + public ScanPeriods(long scanPeriod, long betweenScanPeriod) { + this.scanPeriod = scanPeriod; + this.betweenScanPeriod = betweenScanPeriod; + fullPeriod = scanPeriod + betweenScanPeriod; + } + + public long getBetweenScanPeriod() { + return betweenScanPeriod; + } + + public long getScanPeriod() { + return scanPeriod; + } + + public long getFullPeriod() { + return fullPeriod; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(scanPeriod); + dest.writeLong(betweenScanPeriod); + dest.writeLong(fullPeriod); + } + + protected ScanPeriods(Parcel in){ + scanPeriod = in.readLong(); + betweenScanPeriod = in.readLong(); + fullPeriod = in.readLong(); + } + + public static final Creator CREATOR + = new Creator() { + public ScanPeriods createFromParcel(Parcel in) { + return new ScanPeriods(in); + } + + public ScanPeriods[] newArray(int size) { + return new ScanPeriods[size]; + } + }; + + +} From 9ed7a932b111cd6523b8bca936c08173da83d138 Mon Sep 17 00:00:00 2001 From: olivier stevens Date: Tue, 8 Nov 2016 14:57:33 +0100 Subject: [PATCH 06/12] #Android N: use scanPeriods for a better management of the 6s. scan min --- .../java/org/altbeacon/beacon/service/scanner/ScanPeriods.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/altbeacon/beacon/service/scanner/ScanPeriods.java b/src/main/java/org/altbeacon/beacon/service/scanner/ScanPeriods.java index 26b3cd5c2..c52c8440d 100644 --- a/src/main/java/org/altbeacon/beacon/service/scanner/ScanPeriods.java +++ b/src/main/java/org/altbeacon/beacon/service/scanner/ScanPeriods.java @@ -1,5 +1,6 @@ package org.altbeacon.beacon.service.scanner; +import android.os.Build; import android.os.Parcel; import android.os.Parcelable; @@ -12,7 +13,7 @@ public class ScanPeriods implements Parcelable{ private long fullPeriod; public ScanPeriods(long scanPeriod, long betweenScanPeriod) { - this.scanPeriod = scanPeriod; + this.scanPeriod = android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.N?scanPeriod:Math.max(scanPeriod, 6000); this.betweenScanPeriod = betweenScanPeriod; fullPeriod = scanPeriod + betweenScanPeriod; } From 0649001c5414aaaaac3e2385170af7307db94e75 Mon Sep 17 00:00:00 2001 From: olivier stevens Date: Tue, 8 Nov 2016 18:01:36 +0100 Subject: [PATCH 07/12] #ScreenState: create a CycledScan that scans when the screen turns on or turns off --- build.gradle | 2 +- .../beacon/service/BeaconService.java | 14 ++- .../service/scanner/CycledLeScanner.java | 49 +++++++--- .../scanner/CycledLeScannerScreenState.java | 93 +++++++++++++++++++ .../ScreenStateBroadcastReceiver.java | 30 ++++++ .../screenstate/ScreenStateListener.java | 14 +++ 6 files changed, 188 insertions(+), 14 deletions(-) create mode 100644 src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerScreenState.java create mode 100644 src/main/java/org/altbeacon/beacon/service/scanner/screenstate/ScreenStateBroadcastReceiver.java create mode 100644 src/main/java/org/altbeacon/beacon/service/scanner/screenstate/ScreenStateListener.java diff --git a/build.gradle b/build.gradle index 428061933..621ffdb95 100644 --- a/build.gradle +++ b/build.gradle @@ -20,7 +20,7 @@ def getVersionName = { println("Can't get version from git: " + e); return "adhoc" }*/ - return "2.9.1-CS6" + return "2.9.1-CS7" } buildscript { diff --git a/src/main/java/org/altbeacon/beacon/service/BeaconService.java b/src/main/java/org/altbeacon/beacon/service/BeaconService.java index 18840f99f..174ae591c 100644 --- a/src/main/java/org/altbeacon/beacon/service/BeaconService.java +++ b/src/main/java/org/altbeacon/beacon/service/BeaconService.java @@ -31,6 +31,7 @@ import android.bluetooth.BluetoothDevice; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.os.AsyncTask; import android.os.Binder; @@ -51,6 +52,8 @@ import org.altbeacon.beacon.service.scanner.CycledLeScanCallback; import org.altbeacon.beacon.service.scanner.CycledLeScanner; import org.altbeacon.beacon.service.scanner.NonBeaconLeScanCallback; +import org.altbeacon.beacon.service.scanner.screenstate.ScreenStateBroadcastReceiver; +import org.altbeacon.beacon.service.scanner.screenstate.ScreenStateListener; import org.altbeacon.beacon.startup.StartupBroadcastReceiver; import org.altbeacon.bluetooth.BluetoothCrashResolver; @@ -91,6 +94,7 @@ public class BeaconService extends Service { private boolean mBackgroundFlag = false; private ExtraDataBeaconTracker mExtraDataBeaconTracker; private ExecutorService mExecutor; + private ScreenStateBroadcastReceiver screenStateBroadcastReceiver; /* * The scan period is how long we wait between restarting the BLE advertisement scans @@ -200,7 +204,12 @@ public void onCreate() { beaconManager = BeaconManager.getInstanceForApplication(getApplicationContext()); mCycledScanner = beaconManager.getCycledLeScanner(); mCycledScanner.initScanner(this, mCycledLeScanCallback, bluetoothCrashResolver); - + if(mCycledScanner instanceof ScreenStateListener){ + IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON); + filter.addAction(Intent.ACTION_SCREEN_OFF); + screenStateBroadcastReceiver = new ScreenStateBroadcastReceiver(); + registerReceiver(screenStateBroadcastReceiver, filter); + } //flatMap all beacon parsers boolean matchBeaconsByServiceUUID = true; if (beaconManager.getBeaconParsers() != null) { @@ -273,6 +282,9 @@ public void onDestroy() { mCycledScanner.stop(); mCycledScanner.destroy(); monitoringStatus.stopStatusPreservation(); + if(screenStateBroadcastReceiver != null){ + unregisterReceiver(screenStateBroadcastReceiver); + } } @Override diff --git a/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScanner.java b/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScanner.java index d9a7b3ceb..8467b83a4 100644 --- a/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScanner.java +++ b/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScanner.java @@ -27,7 +27,7 @@ public class CycledLeScanner { private long mLastScanCycleStartTime = 0l; private long mLastScanCycleEndTime = 0l; - protected long mNextScanCycleStartTime = 0l; + private long mNextScanCycleStartTime = 0l; private long mScanCycleStopTime = 0l; private long mLastScanStopTime = 0l; /** @@ -40,11 +40,11 @@ public class CycledLeScanner { private long mScanCycleActiveStopTime = 0l; private boolean mScanning; - protected boolean mScanningPaused; + private boolean mScanningPaused; private boolean mInScanningPeriod; private boolean mScanCyclerStarted = false; private boolean mScanningEnabled = false; - protected Context mContext; + private Context mContext; private ScanPeriods mCurrentScanPeriods; /** @@ -63,9 +63,9 @@ public class CycledLeScanner { protected final Handler mHandler = new Handler(Looper.getMainLooper()); - protected boolean mBackgroundFlag = false; - protected boolean mRestartNeeded = false; - protected LeScanner leScanner; + private boolean mBackgroundFlag = false; + private boolean mRestartNeeded = false; + private LeScanner leScanner; private final Runnable runableStartScan = new Runnable() { @Override @@ -118,6 +118,14 @@ public boolean initScanner(Context context, CycledLeScanCallback cycledLeScanCal return true; } + public boolean isBackgroundFlag() { + return mBackgroundFlag; + } + + public Context getContext() { + return mContext; + } + private void createActiveScanPeriods(){ mActiveScanPeriods = new ScanPeriods(Math.min(mCurrentScanPeriods.getScanPeriod(), BeaconManager.DEFAULT_FOREGROUND_SCAN_PERIOD), Math.min(mCurrentScanPeriods.getBetweenScanPeriod(), BeaconManager.DEFAULT_BACKGROUND_BETWEEN_SCAN_PERIOD)); @@ -131,6 +139,16 @@ private void createActiveScanPeriods(){ * @param backgroundFlag */ public void setScanPeriods(long scanPeriod, long betweenScanPeriod, boolean backgroundFlag) { + setScanPeriods(scanPeriod, betweenScanPeriod, backgroundFlag, false); + } + /** + * 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 + * between LOW_POWER_MODE vs. LOW_LATENCY_MODE + * + * @param backgroundFlag + */ + public void setScanPeriods(long scanPeriod, long betweenScanPeriod, boolean backgroundFlag, boolean scanNow) { LogManager.d(TAG, "Set scan periods called with %s, %s Background mode must have changed.", scanPeriod, betweenScanPeriod); if (mBackgroundFlag != backgroundFlag) { @@ -157,7 +175,7 @@ public void setScanPeriods(long scanPeriod, long betweenScanPeriod, boolean back // We are waiting to start scanning. We may need to adjust the next start time // only do an adjustment if we need to make it happen sooner. Otherwise, it will // take effect on the next cycle. - if(mBackgroundFlag) { + if(mBackgroundFlag && scanNow) { long proposedNextScanStartTime = (mLastScanCycleEndTime + betweenScanPeriod); if (proposedNextScanStartTime < mNextScanCycleStartTime) { mNextScanCycleStartTime = proposedNextScanStartTime; @@ -287,12 +305,16 @@ protected void stopScan() { leScanner.stopScan(); } - protected long calculateNextTimeToStartScan(){ + protected long calculateNextTimeToStartScanInBg(){ + return calculateNextDelay(mNextScanCycleStartTime, mNextScanCycleActiveStartTime); + } + + protected long calculateNextTimeToStartScanInFg(){ return calculateNextDelay(mNextScanCycleStartTime, mNextScanCycleActiveStartTime); } protected boolean deferScanIfNeeded() { - long millisecondsUntilStart = calculateNextTimeToStartScan(); + long millisecondsUntilStart = mBackgroundFlag?calculateNextTimeToStartScanInBg():calculateNextTimeToStartScanInFg(); boolean deferScanIsNeeded = millisecondsUntilStart > 0; LogManager.d(TAG, "defer scan is needed %b", deferScanIsNeeded); if (leScanner.onDeferScanIfNeeded(deferScanIsNeeded)) { @@ -318,13 +340,17 @@ protected long calculateNextDelay(long referenceTime1, long referenceTime2){ return Math.min(referenceTime1,referenceTime2) - SystemClock.elapsedRealtime(); } - protected long calculateNextTimeForScanCycleStop(){ + protected long calculateNextTimeForScanCycleStopInBg(){ + return calculateNextDelay(mScanCycleStopTime, mScanCycleActiveStopTime); + } + + protected long calculateNextTimeForScanCycleStopInFg(){ return calculateNextDelay(mScanCycleStopTime, mScanCycleActiveStopTime); } protected void scheduleScanCycleStop() { // Stops scanning after a pre-defined scan period. - long millisecondsUntilStop = calculateNextTimeForScanCycleStop(); + long millisecondsUntilStop = mBackgroundFlag?calculateNextTimeForScanCycleStopInBg():calculateNextTimeForScanCycleStopInFg(); if (millisecondsUntilStop > 0) { LogManager.d(TAG, "Waiting to stop scan cycle for another %s milliseconds", millisecondsUntilStop); @@ -438,7 +464,6 @@ protected void cancelWakeUpAlarm() { AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, milliseconds, getWakeUpOperation()); LogManager.d(TAG, "Set a wakeup alarm to go off in %s ms: %s", milliseconds - SystemClock.elapsedRealtime(), getWakeUpOperation()); - } private long getNextScanStartTime(ScanPeriods referenceScanPeriods) { diff --git a/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerScreenState.java b/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerScreenState.java new file mode 100644 index 000000000..b54b1c28b --- /dev/null +++ b/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerScreenState.java @@ -0,0 +1,93 @@ +package org.altbeacon.beacon.service.scanner; + +import org.altbeacon.beacon.BeaconManager; +import org.altbeacon.beacon.service.scanner.screenstate.ScreenStateListener; + +/** + * Created by Connecthings on 08/11/16. + */ + +public class CycledLeScannerScreenState extends CycledLeScanner implements ScreenStateListener { + + + public static enum SCAN_ON_SCREEN_STATE {ON_ONLY(false, true), OFF_ONLY(true, false), ON_OFF(true, true); + + final boolean scanWhenScreenOn; + final boolean scanWhenScreenOff; + + SCAN_ON_SCREEN_STATE(boolean scanOff, boolean scanOn) { + this.scanWhenScreenOff = scanOff; + this.scanWhenScreenOn = scanOn; + } + } + + private final Runnable runnableStopScanning = new Runnable(){ + public void run(){ + stopScanningOnSwitchScreenState(); + } + }; + + private boolean mScanIsEnabled; + private int mActiveScanningTimeOnScreenSwitchState; + private SCAN_ON_SCREEN_STATE mScanScreenState; + + public CycledLeScannerScreenState(){ + this(20000, SCAN_ON_SCREEN_STATE.ON_OFF); + } + + public CycledLeScannerScreenState(int activeScanningTimeOnScreenSwitchState, SCAN_ON_SCREEN_STATE scanScreenState){ + this(BeaconManager.DEFAULT_FOREGROUND_SCAN_PERIOD, BeaconManager.DEFAULT_FOREGROUND_BETWEEN_SCAN_PERIOD, false, activeScanningTimeOnScreenSwitchState, scanScreenState); + } + + public CycledLeScannerScreenState(long scanPeriod, long betweenScanPeriod, boolean backgroundFlag, int activeScanningTimeOnScreenSwitchState, SCAN_ON_SCREEN_STATE scanScreenState) { + super(scanPeriod, betweenScanPeriod, backgroundFlag); + mScanIsEnabled = false; + mActiveScanningTimeOnScreenSwitchState = activeScanningTimeOnScreenSwitchState; + mScanScreenState = scanScreenState; + } + + protected long calculateNextTimeToStartScanInBg(){ + if(mScanIsEnabled){ + return super.calculateNextTimeToStartScanInBg(); + } + return 1000; + } + + protected long calculateNextTimeForScanCycleStopInBg(){ + if(mScanIsEnabled){ + return super.calculateNextTimeForScanCycleStopInBg(); + } + return 1000; + } + + public synchronized void stopScanningOnSwitchScreenState(){ + mScanIsEnabled = false; + } + + public synchronized void startScanningOnSwitchScreenState(){ + if(isBackgroundFlag()) { + if (mScanIsEnabled) { + cancelRunnable(runnableStopScanning); + } else { + mScanIsEnabled = true; + setScanPeriods(BeaconManager.DEFAULT_FOREGROUND_SCAN_PERIOD, BeaconManager.DEFAULT_FOREGROUND_BETWEEN_SCAN_PERIOD, isBackgroundFlag(), true); + } + postDelayed(runnableStopScanning, mActiveScanningTimeOnScreenSwitchState); + } + } + + @Override + public void onScreenOn() { + if(mScanScreenState.scanWhenScreenOn){ + startScanningOnSwitchScreenState(); + } + } + + @Override + public void onScreenOff() { + if(mScanScreenState.scanWhenScreenOff){ + startScanningOnSwitchScreenState(); + } + } + +} diff --git a/src/main/java/org/altbeacon/beacon/service/scanner/screenstate/ScreenStateBroadcastReceiver.java b/src/main/java/org/altbeacon/beacon/service/scanner/screenstate/ScreenStateBroadcastReceiver.java new file mode 100644 index 000000000..e3f90783f --- /dev/null +++ b/src/main/java/org/altbeacon/beacon/service/scanner/screenstate/ScreenStateBroadcastReceiver.java @@ -0,0 +1,30 @@ +package org.altbeacon.beacon.service.scanner.screenstate; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import org.altbeacon.beacon.BeaconManager; +import org.altbeacon.beacon.service.scanner.CycledLeScanner; + +/** + * Permit to detect the screen state and to notify a CycleScanStrategy that implements the ScreenStateListener + * + * Created by Connecthings + */ +public class ScreenStateBroadcastReceiver extends BroadcastReceiver{ + + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + CycledLeScanner cycledLeScanner = BeaconManager.getInstanceForApplication(context).getCycledLeScanner(); + if(cycledLeScanner instanceof ScreenStateListener){ + ScreenStateListener screenStateListener = (ScreenStateListener) cycledLeScanner; + if (Intent.ACTION_SCREEN_ON.equals(action)) { + screenStateListener.onScreenOn(); + }else if (Intent.ACTION_SCREEN_OFF.equals(action)) { + screenStateListener.onScreenOff(); + } + } + } +} diff --git a/src/main/java/org/altbeacon/beacon/service/scanner/screenstate/ScreenStateListener.java b/src/main/java/org/altbeacon/beacon/service/scanner/screenstate/ScreenStateListener.java new file mode 100644 index 000000000..729cc33b6 --- /dev/null +++ b/src/main/java/org/altbeacon/beacon/service/scanner/screenstate/ScreenStateListener.java @@ -0,0 +1,14 @@ +package org.altbeacon.beacon.service.scanner.screenstate; + +/** + * Listener to monitor the screen state + * + * Created by Connecthings + */ +public interface ScreenStateListener { + + public void onScreenOn(); + + public void onScreenOff(); + +} From c694242e8aa3799a8ae7bc6e596d1cae47a9e4c9 Mon Sep 17 00:00:00 2001 From: olivier stevens Date: Wed, 9 Nov 2016 09:50:02 +0100 Subject: [PATCH 08/12] Ready to be merged --- build.gradle | 27 +++++++----------- .../scanner/CycledLeScannerScreenState.java | 28 ++++++++++++++----- 2 files changed, 31 insertions(+), 24 deletions(-) diff --git a/build.gradle b/build.gradle index 621ffdb95..5a675958b 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ ext { * Gets the version name from the latest Git tag */ def getVersionName = { - /*def stdout = new ByteArrayOutputStream() + def stdout = new ByteArrayOutputStream() try { exec { commandLine 'git', 'describe', '--tags' @@ -19,8 +19,7 @@ def getVersionName = { catch (e) { println("Can't get version from git: " + e); return "adhoc" - }*/ - return "2.9.1-CS7" + } } buildscript { @@ -32,18 +31,12 @@ buildscript { classpath 'com.android.tools.build:gradle:1.5.0' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.0' classpath 'org.jfrog.buildinfo:build-info-extractor-gradle:3.0.3' - classpath "net.researchgate:gradle-release:2.0.2" - classpath "com.github.dcendents:android-maven-plugin:1.2" } } apply plugin: 'com.android.library' -apply plugin: 'net.researchgate.release' -apply plugin: 'com.github.dcendents.android-maven' -apply from: 'gradle/eclipse.gradle' -archivesBaseName = 'android-beacon-library' -group = 'org.altbeacon' +apply from: 'gradle/eclipse.gradle' allprojects { version = "${getVersionName()}${isSnapshot == true ? "-SNAPSHOT" : ""}" @@ -73,11 +66,11 @@ android { targetCompatibility JavaVersion.VERSION_1_7 } - /* sourceSets { - androidTest { - setRoot('src/test') - } - }*/ + sourceSets { + androidTest { + setRoot('src/test') + } + } lintOptions { abortOnError false @@ -150,9 +143,9 @@ task distribution(dependsOn: [bundleEclipse, build, clean, renameAarForRelease]) println "Building with version=$version" } -/*task release(dependsOn: 'distribution') << { +task release(dependsOn: 'distribution') << { println('Doing release build') -}*/ +} android.libraryVariants.all { variant -> diff --git a/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerScreenState.java b/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerScreenState.java index b54b1c28b..016952385 100644 --- a/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerScreenState.java +++ b/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerScreenState.java @@ -1,6 +1,8 @@ package org.altbeacon.beacon.service.scanner; import org.altbeacon.beacon.BeaconManager; +import org.altbeacon.beacon.logging.LogManager; +import org.altbeacon.beacon.logging.Logger; import org.altbeacon.beacon.service.scanner.screenstate.ScreenStateListener; /** @@ -9,6 +11,7 @@ public class CycledLeScannerScreenState extends CycledLeScanner implements ScreenStateListener { + private static final String TAG = "CycledLeScannerScreenState"; public static enum SCAN_ON_SCREEN_STATE {ON_ONLY(false, true), OFF_ONLY(true, false), ON_OFF(true, true); @@ -27,7 +30,7 @@ public void run(){ } }; - private boolean mScanIsEnabled; + private boolean scanIsEnabled; private int mActiveScanningTimeOnScreenSwitchState; private SCAN_ON_SCREEN_STATE mScanScreenState; @@ -41,52 +44,63 @@ public CycledLeScannerScreenState(int activeScanningTimeOnScreenSwitchState, SCA public CycledLeScannerScreenState(long scanPeriod, long betweenScanPeriod, boolean backgroundFlag, int activeScanningTimeOnScreenSwitchState, SCAN_ON_SCREEN_STATE scanScreenState) { super(scanPeriod, betweenScanPeriod, backgroundFlag); - mScanIsEnabled = false; + scanIsEnabled = false; mActiveScanningTimeOnScreenSwitchState = activeScanningTimeOnScreenSwitchState; mScanScreenState = scanScreenState; } protected long calculateNextTimeToStartScanInBg(){ - if(mScanIsEnabled){ + if(scanIsEnabled){ return super.calculateNextTimeToStartScanInBg(); } return 1000; } protected long calculateNextTimeForScanCycleStopInBg(){ - if(mScanIsEnabled){ + if(scanIsEnabled){ return super.calculateNextTimeForScanCycleStopInBg(); } return 1000; } public synchronized void stopScanningOnSwitchScreenState(){ - mScanIsEnabled = false; + LogManager.d(TAG, "stopScanningOnSwitchScreenState"); + scanIsEnabled = false; } public synchronized void startScanningOnSwitchScreenState(){ if(isBackgroundFlag()) { - if (mScanIsEnabled) { + if (scanIsEnabled) { + LogManager.d(TAG, "startScanningOnSwitchScreenState - already in progress - delay the end"); cancelRunnable(runnableStopScanning); } else { - mScanIsEnabled = true; + LogManager.d(TAG, "startScanningOnSwitchScreenState - launch the scan"); + scanIsEnabled = true; setScanPeriods(BeaconManager.DEFAULT_FOREGROUND_SCAN_PERIOD, BeaconManager.DEFAULT_FOREGROUND_BETWEEN_SCAN_PERIOD, isBackgroundFlag(), true); } postDelayed(runnableStopScanning, mActiveScanningTimeOnScreenSwitchState); + }else{ + LogManager.d(TAG, "startScanningOnSwitchScreenState - app is in foreground - it does not start the scan"); } } @Override public void onScreenOn() { if(mScanScreenState.scanWhenScreenOn){ + LogManager.d(TAG, "onScreenOn - try to launch scanning"); startScanningOnSwitchScreenState(); + }else{ + LogManager.d(TAG, "onScreenOn - no permission to start scanning"); } } @Override public void onScreenOff() { if(mScanScreenState.scanWhenScreenOff){ + LogManager.d(TAG, "onScreenOff - try to launch scanning"); startScanningOnSwitchScreenState(); + }else{ + LogManager.d(TAG, "onScreenOff - no permission to start scanning"); } } From 7fc1809287fe83c9b08634c84571ac4bedb0232d Mon Sep 17 00:00:00 2001 From: olivier stevens Date: Wed, 16 Nov 2016 13:14:04 +0100 Subject: [PATCH 09/12] #ActiveCycledScan: removed --- .../service/scanner/CycledLeScanner.java | 79 +++++-------------- 1 file changed, 19 insertions(+), 60 deletions(-) diff --git a/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScanner.java b/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScanner.java index 8467b83a4..1c7d8d1db 100644 --- a/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScanner.java +++ b/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScanner.java @@ -30,35 +30,14 @@ public class CycledLeScanner { private long mNextScanCycleStartTime = 0l; private long mScanCycleStopTime = 0l; private long mLastScanStopTime = 0l; - /** - * Link to the Active Scan Period - */ - private long mNextScanCycleActiveStartTime = 0l; - /** - * Link to the Active Scan Period - */ - private long mScanCycleActiveStopTime = 0l; private boolean mScanning; private boolean mScanningPaused; - private boolean mInScanningPeriod; private boolean mScanCyclerStarted = false; private boolean mScanningEnabled = false; private Context mContext; private ScanPeriods mCurrentScanPeriods; - /** - * We introduce the notion of "Active Scan". - * - * We notice that on Android 6 and before, that when scanning for 30s., - * the result of the scan comes mostly at the end of the 30s.. - * - * We notice as well, that if a device scans several times 1s. during 30s., - * it get a faster scan result (and more precise) than if scanning one time 30s. - * - * The activeScanPeriod permits to achieve to scan several time 1s during 30s to get a scan result as fast as possible. - */ - private ScanPeriods mActiveScanPeriods; protected final Handler mHandler = new Handler(Looper.getMainLooper()); @@ -85,7 +64,6 @@ public void run() { public CycledLeScanner(long scanPeriod, long betweenScanPeriod, boolean backgroundFlag) { mCurrentScanPeriods = new ScanPeriods(scanPeriod, betweenScanPeriod); - createActiveScanPeriods(); mBackgroundFlag = backgroundFlag; } @@ -126,11 +104,6 @@ public Context getContext() { return mContext; } - private void createActiveScanPeriods(){ - mActiveScanPeriods = new ScanPeriods(Math.min(mCurrentScanPeriods.getScanPeriod(), BeaconManager.DEFAULT_FOREGROUND_SCAN_PERIOD), - Math.min(mCurrentScanPeriods.getBetweenScanPeriod(), BeaconManager.DEFAULT_BACKGROUND_BETWEEN_SCAN_PERIOD)); - } - /** * 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 @@ -162,7 +135,6 @@ public void setScanPeriods(long scanPeriod, long betweenScanPeriod, boolean back } mCurrentScanPeriods = new ScanPeriods(scanPeriod, betweenScanPeriod); - createActiveScanPeriods(); if (mBackgroundFlag) { LogManager.d(TAG, "We are in the background. Setting wakeup alarm"); setWakeUpAlarm(); @@ -185,7 +157,6 @@ public void setScanPeriods(long scanPeriod, long betweenScanPeriod, boolean back }else{ //If we switch from background to foreground we would like the scan to start right now cancelRunnableStartAndStopScan(); - mNextScanCycleActiveStartTime = now; mNextScanCycleStartTime = now; mHandler.post(runableStartScan); } @@ -282,12 +253,7 @@ protected void scanLeDevice(final Boolean enable) { LogManager.d(TAG, "We are already scanning"); } - long realTime = SystemClock.elapsedRealtime(); - if(mScanCycleStopTime < realTime && !mInScanningPeriod){ - mScanCycleStopTime = SystemClock.elapsedRealtime() + mCurrentScanPeriods.getScanPeriod(); - mInScanningPeriod = true; - } - mScanCycleActiveStopTime = SystemClock.elapsedRealtime() + mActiveScanPeriods.getScanPeriod(); + mScanCycleStopTime = SystemClock.elapsedRealtime() + mCurrentScanPeriods.getScanPeriod(); scheduleScanCycleStop(); LogManager.d(TAG, "Scan started"); @@ -305,14 +271,6 @@ protected void stopScan() { leScanner.stopScan(); } - protected long calculateNextTimeToStartScanInBg(){ - return calculateNextDelay(mNextScanCycleStartTime, mNextScanCycleActiveStartTime); - } - - protected long calculateNextTimeToStartScanInFg(){ - return calculateNextDelay(mNextScanCycleStartTime, mNextScanCycleActiveStartTime); - } - protected boolean deferScanIfNeeded() { long millisecondsUntilStart = mBackgroundFlag?calculateNextTimeToStartScanInBg():calculateNextTimeToStartScanInFg(); boolean deferScanIsNeeded = millisecondsUntilStart > 0; @@ -336,16 +294,24 @@ protected void finishScan() { mScanningPaused = true; } - protected long calculateNextDelay(long referenceTime1, long referenceTime2){ - return Math.min(referenceTime1,referenceTime2) - SystemClock.elapsedRealtime(); + protected long calculateNextDelay(long referenceTime){ + return referenceTime - SystemClock.elapsedRealtime(); + } + + protected long calculateNextTimeToStartScanInBg(){ + return calculateNextDelay(mNextScanCycleStartTime); + } + + protected long calculateNextTimeToStartScanInFg(){ + return calculateNextDelay(mNextScanCycleStartTime); } protected long calculateNextTimeForScanCycleStopInBg(){ - return calculateNextDelay(mScanCycleStopTime, mScanCycleActiveStopTime); + return calculateNextDelay(mScanCycleStopTime); } protected long calculateNextTimeForScanCycleStopInFg(){ - return calculateNextDelay(mScanCycleStopTime, mScanCycleActiveStopTime); + return calculateNextDelay(mScanCycleStopTime); } protected void scheduleScanCycleStop() { @@ -407,14 +373,7 @@ private void finishScanCycle() { LogManager.d(TAG, "Bluetooth is disabled. Cannot scan for beacons."); } - if(mScanCycleStopTime < SystemClock.elapsedRealtime() && mInScanningPeriod){ - mNextScanCycleStartTime = getNextScanStartTime(mCurrentScanPeriods); - mNextScanCycleActiveStartTime = mNextScanCycleStartTime; - mInScanningPeriod = false; - }else{ - mNextScanCycleActiveStartTime = getNextScanStartTime(mActiveScanPeriods); - } - + mNextScanCycleStartTime = getNextScanStartTime(); if (mScanningEnabled) { scanLeDevice(true); } @@ -466,7 +425,7 @@ protected void cancelWakeUpAlarm() { LogManager.d(TAG, "Set a wakeup alarm to go off in %s ms: %s", milliseconds - SystemClock.elapsedRealtime(), getWakeUpOperation()); } - private long getNextScanStartTime(ScanPeriods referenceScanPeriods) { + private long getNextScanStartTime() { // Because many apps may use this library on the same device, we want to try to synchronize // scanning as much as possible in order to save battery. Therefore, we will set the scan // intervals to be on a predictable interval using a modulus of the system time. This may @@ -475,12 +434,12 @@ private long getNextScanStartTime(ScanPeriods referenceScanPeriods) { // will all be doing scans at the same time, thereby saving battery when none are scanning. // This, of course, won't help at all if people set custom scan periods. But since most // people accept the defaults, this will likely have a positive effect. - if (referenceScanPeriods.getBetweenScanPeriod() == 0) { + if (mCurrentScanPeriods.getBetweenScanPeriod() == 0) { return SystemClock.elapsedRealtime(); } - long fullScanCycle = referenceScanPeriods.getFullPeriod(); - long normalizedBetweenScanPeriod = referenceScanPeriods.getBetweenScanPeriod() - (SystemClock.elapsedRealtime() % fullScanCycle); - LogManager.d(TAG, "Normalizing between scan period from %s to %s", referenceScanPeriods.getBetweenScanPeriod(), + long fullScanCycle = mCurrentScanPeriods.getFullPeriod(); + long normalizedBetweenScanPeriod = mCurrentScanPeriods.getBetweenScanPeriod() - (SystemClock.elapsedRealtime() % fullScanCycle); + LogManager.d(TAG, "Normalizing between scan period from %s to %s", mCurrentScanPeriods.getBetweenScanPeriod(), normalizedBetweenScanPeriod); return SystemClock.elapsedRealtime() + normalizedBetweenScanPeriod; From 66b083ac4d82ec4a24c3638ac79e66c8ea22495d Mon Sep 17 00:00:00 2001 From: olivier stevens Date: Tue, 6 Dec 2016 09:44:19 +0100 Subject: [PATCH 10/12] #BluetoothOn: code done --- .../beacon/service/scanner/LeScanner.java | 14 --------- .../service/scanner/LeScannerForLollipop.java | 29 +++++++++++++++++++ 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/altbeacon/beacon/service/scanner/LeScanner.java b/src/main/java/org/altbeacon/beacon/service/scanner/LeScanner.java index 45e65ea51..30b34e95e 100644 --- a/src/main/java/org/altbeacon/beacon/service/scanner/LeScanner.java +++ b/src/main/java/org/altbeacon/beacon/service/scanner/LeScanner.java @@ -71,20 +71,6 @@ protected void postStartLeScan() { mScanHandler.post(startRunnable); } - private boolean isBluetoothOn() { - try { - BluetoothAdapter bluetoothAdapter = getBluetoothAdapter(); - if (bluetoothAdapter != null) { - return (bluetoothAdapter.getState() == BluetoothAdapter.STATE_ON); - } - LogManager.w(TAG, "Cannot get bluetooth adapter"); - } - catch (SecurityException e) { - LogManager.w(TAG, "SecurityException checking if bluetooth is on"); - } - return false; - } - protected void stopScan() { postStopLeScan(); } diff --git a/src/main/java/org/altbeacon/beacon/service/scanner/LeScannerForLollipop.java b/src/main/java/org/altbeacon/beacon/service/scanner/LeScannerForLollipop.java index dd4d7571a..d782c4c29 100644 --- a/src/main/java/org/altbeacon/beacon/service/scanner/LeScannerForLollipop.java +++ b/src/main/java/org/altbeacon/beacon/service/scanner/LeScannerForLollipop.java @@ -140,12 +140,41 @@ protected boolean onDeferScanIfNeeded(boolean deferScanIsNeeded) { return setWakeUpAlarm; } + private boolean isBluetoothOn() { + try { + BluetoothAdapter bluetoothAdapter = getBluetoothAdapter(); + if (bluetoothAdapter != null) { + return (bluetoothAdapter.getState() == BluetoothAdapter.STATE_ON); + } + LogManager.w(TAG, "Cannot get bluetooth adapter"); + } + catch (SecurityException e) { + LogManager.w(TAG, "SecurityException checking if bluetooth is on"); + } + return false; + } + @Override protected void finishScan() { LogManager.d(TAG, "Stopping scan"); stopScan(); } + public void startScan(){ + if(isBluetoothOn()){ + super.stopScan(); + }else{ + LogManager.d(TAG, "Bluetooth off - don't stop scan"); + } + } + + public void stopScan(){ + if(isBluetoothOn()){ + super.stopScan(); + }else{ + LogManager.d(TAG, "Bluetooth off - don't start scan"); + } + } Runnable generateStartScanRunnable(){ List filters = new ArrayList(); From fbcb928f6305e08a4650a49c12a7e1544e0908c2 Mon Sep 17 00:00:00 2001 From: olivier stevens Date: Tue, 6 Dec 2016 09:45:59 +0100 Subject: [PATCH 11/12] #BluetoothOn: adjust message --- .../beacon/service/scanner/LeScannerForLollipop.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/altbeacon/beacon/service/scanner/LeScannerForLollipop.java b/src/main/java/org/altbeacon/beacon/service/scanner/LeScannerForLollipop.java index d782c4c29..e35a2492e 100644 --- a/src/main/java/org/altbeacon/beacon/service/scanner/LeScannerForLollipop.java +++ b/src/main/java/org/altbeacon/beacon/service/scanner/LeScannerForLollipop.java @@ -164,7 +164,7 @@ public void startScan(){ if(isBluetoothOn()){ super.stopScan(); }else{ - LogManager.d(TAG, "Bluetooth off - don't stop scan"); + LogManager.d(TAG, "Not starting scan because bluetooth is off"); } } @@ -172,7 +172,7 @@ public void stopScan(){ if(isBluetoothOn()){ super.stopScan(); }else{ - LogManager.d(TAG, "Bluetooth off - don't start scan"); + LogManager.d(TAG, "Not stopping scan because bluetooth is off"); } } From 5889cb4c0a72915282d71be66d2e3f410bcf5116 Mon Sep 17 00:00:00 2001 From: olivier stevens Date: Wed, 7 Dec 2016 11:20:57 +0100 Subject: [PATCH 12/12] #CycledScan: last adjustments after test --- build.gradle | 4 ++-- .../service/scanner/CycledLeScanner.java | 7 +++++- .../scanner/CycledLeScannerScreenState.java | 24 +++++++++++++++---- .../beacon/service/scanner/LeScanner.java | 2 ++ .../scanner/LeScannerForJellyBeanMr2.java | 5 ++-- .../service/scanner/LeScannerForLollipop.java | 4 ++-- 6 files changed, 33 insertions(+), 13 deletions(-) diff --git a/build.gradle b/build.gradle index 5a675958b..b3799c3b1 100644 --- a/build.gradle +++ b/build.gradle @@ -66,11 +66,11 @@ android { targetCompatibility JavaVersion.VERSION_1_7 } - sourceSets { + /*sourceSets { androidTest { setRoot('src/test') } - } + }*/ lintOptions { abortOnError false diff --git a/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScanner.java b/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScanner.java index c2f52783a..1d2a26af6 100644 --- a/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScanner.java +++ b/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScanner.java @@ -171,6 +171,9 @@ public void setScanPeriods(long scanPeriod, long betweenScanPeriod, boolean back LogManager.i(TAG, "Adjusted scanStopTime to be %s", mScanCycleStopTime); } } + if(!mScanningEnabled){ + start(); + } } public void start() { @@ -197,6 +200,7 @@ public void stop() { } public void destroy() { + cancelRunnableStartAndStopScan(); leScanner.onDestroy(); } @@ -276,7 +280,8 @@ protected void stopScan() { protected boolean deferScanIfNeeded() { long millisecondsUntilStart = mBackgroundFlag?calculateNextTimeToStartScanInBg():calculateNextTimeToStartScanInFg(); boolean deferScanIsNeeded = millisecondsUntilStart > 0; - LogManager.d(TAG, "defer scan is needed %b", deferScanIsNeeded); + LogManager.d(TAG, "Waiting to start next Bluetooth scan for another %s milliseconds", + millisecondsUntilStart); if (leScanner.onDeferScanIfNeeded(deferScanIsNeeded)) { LogManager.d(TAG, "plan a wakeup alarm"); setWakeUpAlarm(); diff --git a/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerScreenState.java b/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerScreenState.java index 016952385..7e4ffd60e 100644 --- a/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerScreenState.java +++ b/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerScreenState.java @@ -35,7 +35,7 @@ public void run(){ private SCAN_ON_SCREEN_STATE mScanScreenState; public CycledLeScannerScreenState(){ - this(20000, SCAN_ON_SCREEN_STATE.ON_OFF); + this(18000, SCAN_ON_SCREEN_STATE.ON_OFF); } public CycledLeScannerScreenState(int activeScanningTimeOnScreenSwitchState, SCAN_ON_SCREEN_STATE scanScreenState){ @@ -49,23 +49,30 @@ public CycledLeScannerScreenState(long scanPeriod, long betweenScanPeriod, boole mScanScreenState = scanScreenState; } - protected long calculateNextTimeToStartScanInBg(){ + protected synchronized long calculateNextTimeToStartScanInBg(){ if(scanIsEnabled){ + LogManager.d(TAG, "calculate next scan bg"); return super.calculateNextTimeToStartScanInBg(); } + LogManager.d(TAG, "lock next scan"); return 1000; } - protected long calculateNextTimeForScanCycleStopInBg(){ + protected synchronized long calculateNextTimeForScanCycleStopInBg(){ if(scanIsEnabled){ + LogManager.d(TAG, "calculate next stop bg"); return super.calculateNextTimeForScanCycleStopInBg(); } - return 1000; + LogManager.d(TAG, "stop scan definitivly"); + return 0; } public synchronized void stopScanningOnSwitchScreenState(){ LogManager.d(TAG, "stopScanningOnSwitchScreenState"); - scanIsEnabled = false; + if(isBackgroundFlag()) { + scanIsEnabled = false; + stop(); + } } public synchronized void startScanningOnSwitchScreenState(){ @@ -84,6 +91,13 @@ public synchronized void startScanningOnSwitchScreenState(){ } } + public synchronized void setScanPeriods(long scanPeriod, long betweenScanPeriod, boolean backgroundFlag, boolean scanNow) { + if(!backgroundFlag && isBackgroundFlag()){ + cancelRunnable(runnableStopScanning); + } + super.setScanPeriods(scanPeriod, betweenScanPeriod, backgroundFlag, scanNow); + } + @Override public void onScreenOn() { if(mScanScreenState.scanWhenScreenOn){ diff --git a/src/main/java/org/altbeacon/beacon/service/scanner/LeScanner.java b/src/main/java/org/altbeacon/beacon/service/scanner/LeScanner.java index 30b34e95e..44584a514 100644 --- a/src/main/java/org/altbeacon/beacon/service/scanner/LeScanner.java +++ b/src/main/java/org/altbeacon/beacon/service/scanner/LeScanner.java @@ -72,11 +72,13 @@ protected void postStartLeScan() { } protected void stopScan() { + LogManager.d(TAG, "stopScann"); postStopLeScan(); } protected void startScan() { + LogManager.d(TAG, "startScan"); postStartLeScan(); } diff --git a/src/main/java/org/altbeacon/beacon/service/scanner/LeScannerForJellyBeanMr2.java b/src/main/java/org/altbeacon/beacon/service/scanner/LeScannerForJellyBeanMr2.java index 0b8726ac1..5c2c539a8 100644 --- a/src/main/java/org/altbeacon/beacon/service/scanner/LeScannerForJellyBeanMr2.java +++ b/src/main/java/org/altbeacon/beacon/service/scanner/LeScannerForJellyBeanMr2.java @@ -11,14 +11,13 @@ @TargetApi(18) public class LeScannerForJellyBeanMr2 extends LeScanner { - private static final String TAG = "CycledLeScannerForJellyBeanMr2"; + private static final String TAG = "LeScannerForJellyBeanMr2"; private BluetoothAdapter.LeScanCallback leScanCallback; public LeScannerForJellyBeanMr2(Context context, CycledLeScanCallback cycledLeScanCallback, BluetoothCrashResolver crashResolver) { super(context, cycledLeScanCallback, crashResolver); } - protected boolean onDeferScanIfNeeded(boolean deferScanIsNeeded) { if (deferScanIsNeeded) { return getBackgroundFlag(); @@ -73,7 +72,7 @@ private BluetoothAdapter.LeScanCallback getLeScanCallback() { public void onLeScan(final BluetoothDevice device, final int rssi, final byte[] scanRecord) { LogManager.d(TAG, "got record"); - getLeScanCallback().onLeScan(device, rssi, scanRecord); + getCycledLeScanCallback().onLeScan(device, rssi, scanRecord); getBluetoothCrashResolver().notifyScannedDevice(device, getLeScanCallback()); } }; diff --git a/src/main/java/org/altbeacon/beacon/service/scanner/LeScannerForLollipop.java b/src/main/java/org/altbeacon/beacon/service/scanner/LeScannerForLollipop.java index e35a2492e..eb7f0af3b 100644 --- a/src/main/java/org/altbeacon/beacon/service/scanner/LeScannerForLollipop.java +++ b/src/main/java/org/altbeacon/beacon/service/scanner/LeScannerForLollipop.java @@ -23,7 +23,7 @@ @TargetApi(21) public class LeScannerForLollipop extends LeScanner { - private static final String TAG = "CycledLeScannerForLollipop"; + private static final String TAG = "LeScannerForLollipop"; private static final long BACKGROUND_L_SCAN_DETECTION_PERIOD_MILLIS = 10000l; private BluetoothLeScanner mScanner; private ScanCallback leScanCallback; @@ -162,7 +162,7 @@ protected void finishScan() { public void startScan(){ if(isBluetoothOn()){ - super.stopScan(); + super.startScan(); }else{ LogManager.d(TAG, "Not starting scan because bluetooth is off"); }