Goal
EddyStone EID beacon detection when app is in the background. As soon as the user enters a beacon region, the application should start ranging and make a http call to my server so I am informed of the detection. Even for short visits.
Because of the background scanning limitations on Android I was thinking of using a region bootstrap and starting the foreground service as soon as I enter a beacon region. This has my preference over using the foreground service directly so I don't have the notification visible all the time.
Issue
I've based my application on the AltBeacon reference application. I tried starting the foreground service as soon as the user enters the region. Foreground service gets started, but the ranging notifier doesn't show any beacon detections. Alternative that I tried is starting the foregroundservice and ranging in the didDetermineState method callback, but this doesn't work out because I have to enable and disable the region bootstrap to do so, and this will trigger the didDetermineState callback method again.
How can I detect beacons in the background (without a delay) and start ranging without using the foreground service all the time?
Code + Log
public class AppController extends MultiDexApplication implements BootstrapNotifier,
RangeNotifier, BeaconConsumer {
private static final String TAG = "BEACON:";
private static AppComponent appComponent;
private static AppController instance;
private RegionBootstrap regionBootstrap;
private BackgroundPowerSaver backgroundPowerSaver;
private boolean isScanningOnForeground = false;
private BeaconManager beaconManager;
@Override
public void onCreate() {
super.onCreate();
beaconManager = BeaconManager.getInstanceForApplication(this);
beaconManager.setDebug(true);
beaconManager.getBeaconParsers().clear();
beaconManager.getBeaconParsers()
.add(new BeaconParser().setBeaconLayout(BeaconParser.EDDYSTONE_UID_LAYOUT));
Region region = new Region("backgroundRegion",
null, null, null);
regionBootstrap = new RegionBootstrap(this, region);
backgroundPowerSaver = new BackgroundPowerSaver(this);
instance = this;
this.getAppComponent().inject(this);
}
public AppComponent getAppComponent() {
if (appComponent == null) {
appComponent = DaggerAppComponent.builder().appModule(new AppModule(this)).build();
}
return appComponent;
}
public static AppController getInstance() {
return instance;
}
@Override
public void didEnterRegion(Region region) {
Log.d(TAG, "(didEnterRegion) Beacon detected in region!");
startMonitoringOnForeground();
}
public void disableMonitoring() {
if (regionBootstrap != null) {
regionBootstrap.disable();
regionBootstrap = null;
}
}
public void enableMonitoring() {
Region region = new Region("backgroundRegion",
null, null, null);
regionBootstrap = new RegionBootstrap(this, region);
}
private void enableRanging() {
Log.d(TAG, "Enable ranging");
beaconManager.removeAllRangeNotifiers();
beaconManager.addRangeNotifier(this);
try {
beaconManager.startRangingBeaconsInRegion(new Region("rangingRegion",
null, null, null));
Log.d(TAG, "Ranging started..");
} catch (RemoteException e) {
Log.d(TAG, ">>>>>>>>>>> START RANGING EXCEPTION!");
e.printStackTrace();
}
}
private void disableRanging() {
Log.d(TAG, "Disable ranging.");
try {
beaconManager.stopRangingBeaconsInRegion(new Region("rangingRegion",
null, null, null));
} catch (RemoteException e) {
Log.d(TAG, ">>>>>>>>>>> START RANGING EXCEPTION!");
e.printStackTrace();
}
beaconManager.removeAllRangeNotifiers();
}
private void startMonitoringOnForeground() {
if (isScanningOnForeground) {
Log.d(TAG, "Ignore method call, already scanning in foreground");
return;
}
isScanningOnForeground = true;
disableMonitoring();
Notification.Builder builder = new Notification.Builder(this);
builder.setSmallIcon(R.drawable.app_icon);
builder.setContentTitle("Scanning for Beacons");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel("My Notification Channel ID",
"My Notification Name", NotificationManager.IMPORTANCE_DEFAULT);
channel.setDescription("My Notification Channel Description");
NotificationManager notificationManager = (NotificationManager) getSystemService(
Context.NOTIFICATION_SERVICE);
notificationManager.createNotificationChannel(channel);
builder.setChannelId(channel.getId());
}
beaconManager.enableForegroundServiceScanning(builder.build(), 456);
// For the above foreground scanning service to be useful, you need to disable
// JobScheduler-based scans (used on Android 8+) and set a fast background scan
// cycle that would otherwise be disallowed by the operating system.
//
beaconManager.setEnableScheduledScanJobs(false);
beaconManager.setBackgroundBetweenScanPeriod(30000);
beaconManager.setBackgroundScanPeriod(1100);
enableRanging();
enableMonitoring();
}
private void stopMonitoringOnForeground() {
if (!isScanningOnForeground) {
Log.d(TAG, "Not stopping since foreground scanning isn't active.");
return;
}
isScanningOnForeground = false;
disableMonitoring();
beaconManager.disableForegroundServiceScanning();
enableMonitoring();
disableRanging();
}
@Override
public void didExitRegion(Region region) {
Log.d(TAG, "(didExitRegion) No beacons anymore");
stopMonitoringOnForeground();
}
@Override
public void didDetermineStateForRegion(int state, Region region) {
Log.d(TAG,
"Determine state for region " + (state == 1 ? "INSIDE" : "OUTSIDE (" + state + ")"));
}
@Override
public void didRangeBeaconsInRegion(Collection<Beacon> collection, Region region) {
Log.d(TAG, "Did range beacons in region");
for (Beacon beacon : collection) {
Log.d(TAG, "Beacon: " + beacon.getId1().toString());
}
}
@Override
public void onBeaconServiceConnect() {
Log.d(TAG, "OnBeaconServiceConnect");
}
}
D/BEACON:: Determine state for region OUTSIDE (0)
D/BEACON:: Determine state for region INSIDE
D/BEACON:: (didEnterRegion) Beacon detected in region!
D/BEACON:: Enable ranging
D/BEACON:: Ranging started..
D/BEACON:: Determine state for region OUTSIDE (0)
D/BEACON:: Determine state for region INSIDE
D/BEACON:: (didEnterRegion) Beacon detected in region!
D/BEACON:: Ignore method call, already scanning in foreground
D/BEACON:: (didEnterRegion) Beacon detected in region!
D/BEACON:: Ignore method call, already scanning in foreground