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/BeaconManager.java b/src/main/java/org/altbeacon/beacon/BeaconManager.java index e4b8cad8a..b769125c9 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; @@ -117,6 +118,7 @@ public class BeaconManager { protected final Set monitorNotifiers = new CopyOnWriteArraySet<>(); 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; @@ -253,6 +255,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); } /** @@ -997,4 +1001,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 5738a50f0..610c4df76 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 @@ -197,12 +201,15 @@ 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); + 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) { @@ -275,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 4d370d55e..1d2a26af6 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; @@ -24,55 +22,57 @@ 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; - protected long mNextScanCycleStartTime = 0l; + private long mNextScanCycleStartTime = 0l; private long mScanCycleStopTime = 0l; private long mLastScanStopTime = 0l; private boolean mScanning; - protected boolean mScanningPaused; + private boolean mScanningPaused; private boolean mScanCyclerStarted = false; private boolean mScanningEnabled = false; - protected final Context mContext; - private long mScanPeriod; + private Context mContext; - protected long mBetweenScanPeriod; + private ScanPeriods mCurrentScanPeriods; 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; + private boolean mBackgroundFlag = false; + private boolean mRestartNeeded = false; + private 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; - protected CycledLeScanner(Context context, long scanPeriod, long betweenScanPeriod, boolean backgroundFlag, CycledLeScanCallback cycledLeScanCallback, BluetoothCrashResolver crashResolver) { - mScanPeriod = scanPeriod; - mBetweenScanPeriod = betweenScanPeriod; - mContext = context; - mCycledLeScanCallback = cycledLeScanCallback; - mBluetoothCrashResolver = crashResolver; + public CycledLeScanner(long scanPeriod, long betweenScanPeriod, boolean backgroundFlag) { + mCurrentScanPeriods = new ScanPeriods(scanPeriod, betweenScanPeriod); 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,28 +89,52 @@ 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; + } + + public boolean isBackgroundFlag() { + return mBackgroundFlag; + } + public Context getContext() { + return mContext; } /** * 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) { + 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) { mRestartNeeded = true; } mBackgroundFlag = backgroundFlag; - mScanPeriod = scanPeriod; - mBetweenScanPeriod = betweenScanPeriod; + if (backgroundFlag) { + leScanner.onBackground(); + } else { + leScanner.onForeground(); + } + + mCurrentScanPeriods = new ScanPeriods(scanPeriod, betweenScanPeriod); if (mBackgroundFlag) { LogManager.d(TAG, "We are in the background. Setting wakeup alarm"); setWakeUpAlarm(); @@ -123,11 +147,18 @@ 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 && scanNow) { + 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(); + mNextScanCycleStartTime = now; + mHandler.post(runableStartScan); } } if (mScanCycleStopTime > now) { @@ -140,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() { @@ -159,27 +193,23 @@ public void stop() { if (mScanCyclerStarted) { scanLeDevice(false); } - if (mBluetoothAdapter != null) { + if (leScanner.getBluetoothAdapter() != null) { stopScan(); mLastScanCycleEndTime = SystemClock.elapsedRealtime(); } } public void destroy() { - mScanThread.quit(); + cancelRunnableStartAndStopScan(); + leScanner.onDestroy(); } - protected abstract void stopScan(); - - protected abstract boolean deferScanIfNeeded(); - - protected abstract void startScan(); @SuppressLint("NewApi") protected void scanLeDevice(final Boolean enable) { try { mScanCyclerStarted = true; - if (getBluetoothAdapter() == null) { + if (leScanner.getBluetoothAdapter() == null) { LogManager.e(TAG, "No Bluetooth adapter. beaconService cannot scan."); } if (enable) { @@ -191,9 +221,9 @@ protected void scanLeDevice(final Boolean enable) { mScanning = true; mScanningPaused = false; try { - if (getBluetoothAdapter() != null) { - if (getBluetoothAdapter().isEnabled()) { - if (mBluetoothCrashResolver != null && mBluetoothCrashResolver.isRecoveryInProgress()) { + if (leScanner.getBluetoothAdapter() != null) { + if (leScanner.getBluetoothAdapter().isEnabled()) { + if (leScanner.getBluetoothCrashResolver() != null && leScanner.getBluetoothCrashResolver().isRecoveryInProgress()) { LogManager.w(TAG, "Skipping scan because crash recovery is in progress."); } else { if (mScanningEnabled) { @@ -225,7 +255,7 @@ protected void scanLeDevice(final Boolean enable) { } else { LogManager.d(TAG, "We are already scanning"); } - mScanCycleStopTime = (SystemClock.elapsedRealtime() + mScanPeriod); + mScanCycleStopTime = (SystemClock.elapsedRealtime() + mCurrentScanPeriods.getScanPeriod()); scheduleScanCycleStop(); LogManager.d(TAG, "Scan started"); @@ -242,38 +272,93 @@ protected void scanLeDevice(final Boolean enable) { } } + + protected void stopScan() { + leScanner.stopScan(); + } + + protected boolean deferScanIfNeeded() { + long millisecondsUntilStart = mBackgroundFlag?calculateNextTimeToStartScanInBg():calculateNextTimeToStartScanInFg(); + boolean deferScanIsNeeded = millisecondsUntilStart > 0; + 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(); + } + if (deferScanIsNeeded) { + postDelayed(runableStartScan, millisecondsUntilStart > 1000 ? 1000 : millisecondsUntilStart); + } + return deferScanIsNeeded; + } + + protected void startScan() { + leScanner.startScan(); + } + + protected void finishScan() { + leScanner.finishScan(); + mScanningPaused = true; + } + + 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); + } + + protected long calculateNextTimeForScanCycleStopInFg(){ + return calculateNextDelay(mScanCycleStopTime); + } + protected void scheduleScanCycleStop() { // Stops scanning after a pre-defined scan period. - long millisecondsUntilStop = mScanCycleStopTime - SystemClock.elapsedRealtime(); + long millisecondsUntilStop = mBackgroundFlag?calculateNextTimeForScanCycleStopInBg():calculateNextTimeForScanCycleStopInFg(); 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 abstract void finishScan(); + 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"); try { - mCycledLeScanCallback.onCycleEnd(); + leScanner.getCycledLeScanCallback().onCycleEnd(); if (mScanning) { - if (getBluetoothAdapter() != null) { - if (getBluetoothAdapter().isEnabled()) { + if (leScanner.getBluetoothAdapter() != null) { + if (leScanner.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 && + 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,24 +399,7 @@ private void finishScanCycle() { } } - protected BluetoothAdapter getBluetoothAdapter() { - try { - 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"); - } - } - } - catch (SecurityException e) { - // Thrown by Samsung Knox devices if bluetooth access denied for an app - LogManager.e(TAG, "Cannot consruct bluetooth adapter. Security Exception"); - } - return mBluetoothAdapter; - } + private PendingIntent mWakeUpOperation = null; @@ -341,11 +409,11 @@ protected BluetoothAdapter getBluetoothAdapter() { 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); @@ -371,7 +439,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() { @@ -383,15 +450,15 @@ 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 (mCurrentScanPeriods.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 = 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; + return SystemClock.elapsedRealtime() + normalizedBetweenScanPeriod; } private boolean checkLocationPermission() { diff --git a/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerForJellyBeanMr2.java b/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerForJellyBeanMr2.java deleted file mode 100644 index b38f7281c..000000000 --- a/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerForJellyBeanMr2.java +++ /dev/null @@ -1,116 +0,0 @@ -package org.altbeacon.beacon.service.scanner; - -import android.annotation.TargetApi; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.content.Context; -import android.os.SystemClock; - -import org.altbeacon.beacon.logging.LogManager; -import org.altbeacon.bluetooth.BluetoothCrashResolver; - -@TargetApi(18) -public class CycledLeScannerForJellyBeanMr2 extends CycledLeScanner { - 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); - } - - @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; - } - return false; - } - - @Override - protected void startScan() { - postStartLeScan(); - } - - @Override - protected void finishScan() { - 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 = - new BluetoothAdapter.LeScanCallback() { - - @Override - 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()); - } - }; - } - return leScanCallback; - } -} 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..7e4ffd60e --- /dev/null +++ b/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerScreenState.java @@ -0,0 +1,121 @@ +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; + +/** + * Created by Connecthings on 08/11/16. + */ + +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); + + 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 scanIsEnabled; + private int mActiveScanningTimeOnScreenSwitchState; + private SCAN_ON_SCREEN_STATE mScanScreenState; + + public CycledLeScannerScreenState(){ + this(18000, 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); + scanIsEnabled = false; + mActiveScanningTimeOnScreenSwitchState = activeScanningTimeOnScreenSwitchState; + mScanScreenState = scanScreenState; + } + + 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 synchronized long calculateNextTimeForScanCycleStopInBg(){ + if(scanIsEnabled){ + LogManager.d(TAG, "calculate next stop bg"); + return super.calculateNextTimeForScanCycleStopInBg(); + } + LogManager.d(TAG, "stop scan definitivly"); + return 0; + } + + public synchronized void stopScanningOnSwitchScreenState(){ + LogManager.d(TAG, "stopScanningOnSwitchScreenState"); + if(isBackgroundFlag()) { + scanIsEnabled = false; + stop(); + } + } + + public synchronized void startScanningOnSwitchScreenState(){ + if(isBackgroundFlag()) { + if (scanIsEnabled) { + LogManager.d(TAG, "startScanningOnSwitchScreenState - already in progress - delay the end"); + cancelRunnable(runnableStopScanning); + } else { + 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"); + } + } + + 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){ + 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"); + } + } + +} 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..44584a514 --- /dev/null +++ b/src/main/java/org/altbeacon/beacon/service/scanner/LeScanner.java @@ -0,0 +1,135 @@ +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() { + LogManager.d(TAG, "stopScann"); + postStopLeScan(); + } + + + protected void startScan() { + LogManager.d(TAG, "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() { + try { + 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"); + } + } + } + catch (SecurityException e) { + // Thrown by Samsung Knox devices if bluetooth access denied for an app + LogManager.e(TAG, "Cannot consruct bluetooth adapter. Security Exception"); + } + return mBluetoothAdapter; + } + + public long getLastDetectionTime() { + return mLastDetectionTime; + } + public void recordDetection() { + mLastDetectionTime = SystemClock.elapsedRealtime(); + } + +} diff --git a/src/main/java/org/altbeacon/beacon/service/scanner/LeScannerForJellyBeanMr2.java b/src/main/java/org/altbeacon/beacon/service/scanner/LeScannerForJellyBeanMr2.java new file mode 100644 index 000000000..5c2c539a8 --- /dev/null +++ b/src/main/java/org/altbeacon/beacon/service/scanner/LeScannerForJellyBeanMr2.java @@ -0,0 +1,82 @@ +package org.altbeacon.beacon.service.scanner; + +import android.annotation.TargetApi; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.content.Context; +import android.os.SystemClock; + +import org.altbeacon.beacon.logging.LogManager; +import org.altbeacon.bluetooth.BluetoothCrashResolver; + +@TargetApi(18) +public class LeScannerForJellyBeanMr2 extends LeScanner { + 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(); + } + return false; + } + + Runnable generateStartScanRunnable() { + final BluetoothAdapter bluetoothAdapter = getBluetoothAdapter(); + if (bluetoothAdapter == null) { + return null; + } + final BluetoothAdapter.LeScanCallback leScanCallback = getLeScanCallback(); + return new Runnable() { + @Override + public void run() { + try { + //noinspection deprecation + bluetoothAdapter.startLeScan(leScanCallback); + } catch (Exception e) { + LogManager.e(e, TAG, "Internal Android exception in startLeScan()"); + } + } + }; + } + + Runnable generateStopScanRunnable() { + final BluetoothAdapter bluetoothAdapter = getBluetoothAdapter(); + if (bluetoothAdapter == null) { + return null; + } + final BluetoothAdapter.LeScanCallback leScanCallback = getLeScanCallback(); + return 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 = + new BluetoothAdapter.LeScanCallback() { + + @Override + public void onLeScan(final BluetoothDevice device, final int rssi, + final byte[] scanRecord) { + LogManager.d(TAG, "got record"); + getCycledLeScanCallback().onLeScan(device, rssi, scanRecord); + getBluetoothCrashResolver().notifyScannedDevice(device, getLeScanCallback()); + } + }; + } + return leScanCallback; + } +} 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 82251314d..eb7f0af3b 100644 --- a/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerForLollipop.java +++ b/src/main/java/org/altbeacon/beacon/service/scanner/LeScannerForLollipop.java @@ -21,8 +21,9 @@ import java.util.List; @TargetApi(21) -public class CycledLeScannerForLollipop extends CycledLeScanner { - private static final String TAG = "CycledLeScannerForLollipop"; +public class LeScannerForLollipop extends LeScanner { + + private static final String TAG = "LeScannerForLollipop"; private static final long BACKGROUND_L_SCAN_DETECTION_PERIOD_MILLIS = 10000l; private BluetoothLeScanner mScanner; private ScanCallback leScanCallback; @@ -32,15 +33,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) @@ -74,10 +72,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() - @@ -120,29 +118,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; @@ -150,19 +137,50 @@ public void run() { mScanDeferredBefore = false; mMainScanCycleActive = true; } + 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 startScan() { - if (!isBluetoothOn()) { + protected void finishScan() { + LogManager.d(TAG, "Stopping scan"); + stopScan(); + } + + public void startScan(){ + if(isBluetoothOn()){ + super.startScan(); + }else{ LogManager.d(TAG, "Not starting scan because bluetooth is off"); - return; } + } + + public void stopScan(){ + if(isBluetoothOn()){ + super.stopScan(); + }else{ + LogManager.d(TAG, "Not stopping scan because bluetooth is off"); + } + } + + 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( @@ -172,24 +190,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 { @@ -203,23 +210,17 @@ public void run() { // Thrown by Samsung Knox devices if bluetooth access denied for an app LogManager.e(TAG, "Cannot start scan. Security Exception"); } - } - }); + }; } - private void postStopLeScan() { - if (!isBluetoothOn()){ - LogManager.d(TAG, "Not stopping scan because bluetooth is off"); - return; - } + 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 { @@ -233,23 +234,8 @@ public void run() { // Thrown by Samsung Knox devices if bluetooth access denied for an app LogManager.e(TAG, "Cannot stop scan. Security Exception"); } - } - }); - } - - 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; + }; } private BluetoothLeScanner getScanner() { @@ -286,7 +272,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."); @@ -297,7 +283,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) { 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..c52c8440d --- /dev/null +++ b/src/main/java/org/altbeacon/beacon/service/scanner/ScanPeriods.java @@ -0,0 +1,63 @@ +package org.altbeacon.beacon.service.scanner; + +import android.os.Build; +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 = android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.N?scanPeriod:Math.max(scanPeriod, 6000); + 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]; + } + }; + + +} 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(); + +}