Android JobScheduler job not running - android-6.0-marshmallow

My hybrid Cordova Android app uses one custom plugin in which I do a great deal of background work at intervals of ca 30 minutes. Up until now I have been using AlarmManger with a setInexact alarm to perform the work. The only real issue I have with that route is that the alarm does not survive a reboot. Given that I am now only supporting Android 6 (API 23)+ devices I am now experimenting with replacing AlarmManager with JobScheduler. My efforts thus far are shown below
public class UnyService extends JobService
{
#Override
public boolean onStartJob(JobParameters params)
{
UnyHandler.sendMessage(Message.obtain(UnyHandler,1,params));
return true;
}
#Override
public boolean onStopJob(JobParameters params)
{
UnyHandler.removeMessages(1);
return false;
}
where I am using a Handler to perform the actual work. The code for Handler is shown below
private Handler UnyHandler = new Handler(new Handler.Callback()
{
#Override
public boolean handleMessage(Message msg)
{
Feedback.postBackInfo("Handled it!");
jobFinished((JobParameters)msg.obj,false);
return true;
}
});
I then use the following code to get the job up and running
private void launchTimerJob()
{
timerJob =
(JobScheduler)context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
JobInfo.Builder builder = new JobInfo.Builder(1,new
ComponentName(Utils.packName,UnyService.class.getName()));
builder.setPeriodic(30000);
builder.setPersisted(true);
builder.setBackoffCriteria(30000,JobInfo.BACKOFF_POLICY_LINEAR);
if (0 >= timerJob.schedule(builder.build()))
Feedback.postBackInfo("Job Build Error");
else Feedback.postBackInfo("Job Created and Scheduled");
}
where Feedback and Utils are other classes in my app which provide support services. To facilitate testing I am using a relatively small period of 30 seconds.
When I install and start the app the plugin init code calls launchTimerJob() and I get the "Job Created and Scheduled" notification back as expected.
From that point forward I had expected to get Handled It! notifications from Handler above at intervals of roughly 30s. A notification has turned up on the odd occasion but a totally arbitrary time measuring from App startup and has not obliged by repeating. Clearly, I am doing something wrong here.

Android docs could do a better job of mentioning that the minimum interval allowed for periodic jobs is 900,000 milliseconds, i.e.
15 minutes !!!!
I gather that prior to API 24 (Nogat) it was possible to use smaller intervals but no longer. Be wary of the various JobScheduler tutorials you will find out there. There are many that are quite dated and Android Jobs appears to be a still evolving API.
My own reason for originally using AlarmManager was to enable background tasks to be performed when the app was, well, backgrounded. However, with the coming of doze mode this strategy fails since the app will simply not get broadcast messages when the device is dozing.
Consider the following strategy instead
When the app is foregrounded you can quite simply use a Handler to manage periodic tasks - even those that happen at an interval of a few seconds.
No normal app should ever have to carry out background tasks at that frequency (every few seconds) when the phone is dozing - and when that is required there is a route via a specific request for the app to be exempted from battery optimizations. In such instances a periodic JobScheduler with a 15 minute period is the best you can do.

Related

How to make command to wait until all events triggered against it are completed successfully

I have came across a requirement where i want axon to wait untill all events in the eventbus fired against a particular Command finishes their execution. I will the brief the scenario:
I have a RestController which fires below command to create an application entity:
#RestController
class myController{
#PostMapping("/create")
#ResponseBody
public String create(
org.axonframework.commandhandling.gateway.CommandGateway.sendAndWait(new CreateApplicationCommand());
System.out.println(“in myController:: after sending CreateApplicationCommand”);
}
}
This command is being handled in the Aggregate, The Aggregate class is annotated with org.axonframework.spring.stereotype.Aggregate:
#Aggregate
class MyAggregate{
#CommandHandler //org.axonframework.commandhandling.CommandHandler
private MyAggregate(CreateApplicationCommand command) {
org.axonframework.modelling.command.AggregateLifecycle.apply(new AppCreatedEvent());
System.out.println(“in MyAggregate:: after firing AppCreatedEvent”);
}
#EventSourcingHandler //org.axonframework.eventsourcing.EventSourcingHandler
private void on(AppCreatedEvent appCreatedEvent) {
// Updates the state of the aggregate
this.id = appCreatedEvent.getId();
this.name = appCreatedEvent.getName();
System.out.println(“in MyAggregate:: after updating state”);
}
}
The AppCreatedEvent is handled at 2 places:
In the Aggregate itself, as we can see above.
In the projection class as below:
#EventHandler //org.axonframework.eventhandling.EventHandler
void on(AppCreatedEvent appCreatedEvent){
// persists into database
System.out.println(“in Projection:: after saving into database”);
}
The problem here is after catching the event at first place(i.e., inside aggregate) the call gets returned to myController.
i.e. The output here is:
in MyAggregate:: after firing AppCreatedEvent
in MyAggregate:: after updating state
in myController:: after sending CreateApplicationCommand
in Projection:: after saving into database
The output which i want is:
in MyAggregate:: after firing AppCreatedEvent
in MyAggregate:: after updating state
in Projection:: after saving into database
in myController:: after sending CreateApplicationCommand
In simple words, i want axon to wait untill all events triggered against a particular command are executed completely and then return to the class which triggered the command.
After searching on the forum i got to know that all sendAndWait does is wait until the handling of the command and publication of the events is finalized, and then i tired with Reactor Extension as well using below but got same results: org.axonframework.extensions.reactor.commandhandling.gateway.ReactorCommandGateway.send(new CreateApplicationCommand()).block();
Can someone please help me out.
Thanks in advance.
What would be best in your situation, #rohit, is to embrace the fact you are using an eventually consistent solution here. Thus, Command Handling is entirely separate from Event Handling, making the Query Models you create eventually consistent with the Command Model (your aggregates). Therefore, you wouldn't necessarily wait for the events exactly but react when the Query Model is present.
Embracing this comes down to building your application such that "yeah, I know my response might not be up to date now, but it might be somewhere in the near future." It is thus recommended to subscribe to the result you are interested in after or before the fact you have dispatched a command.
For example, you could see this as using WebSockets with the STOMP protocol, or you could tap into Project Reactor and use the Flux result type to receive the results as they go.
From your description, I assume you or your business have decided that the UI component should react in the (old-fashioned) synchronous way. There's nothing wrong with that, but it will bite your *ss when it comes to using something inherently eventually consistent like CQRS. You can, however, spoof the fact you are synchronous in your front-end, if you will.
To achieve this, I would recommend using Axon's Subscription Query to subscribe to the query model you know will be updated by the command you will send.
In pseudo-code, that would look a little bit like this:
public Result mySynchronousCall(String identifier) {
// Subscribe to the updates to come
SubscriptionQueryResult<Result> result = QueryGateway.subscriptionQuery(...);
// Issue command to update
CommandGateway.send(...);
// Wait on the Flux for the first result, and then close it
return result.updates()
.next()
.map(...)
.timeout(...)
.doFinally(it -> result.close());
}
You could see this being done in this sample WebFluxRest class, by the way.
Note that you are essentially closing the door to the front-end to tap into the asynchronous goodness by doing this. It'll work and allow you to wait for the result to be there as soon as it is there, but you'll lose some flexibility.

FirebaseMessagingService Crashes on Android O due to background execution limits

Our app is crashing on Android O due to the new background execution limits. We are on Firebase version 10.2.1, which is the one that added Android O support.
Seems like an issue with Firebase? Or is there some change needed to support this on our end?
java.lang.IllegalStateException: Not allowed to start service Intent { act=com.google.firebase.INSTANCE_ID_EVENT pkg=my.package.name cmp=my.package.name/my.package.name.MyFcmIdService (has extras) }: app is in background uid UidRecord{30558fa u0a327 RCVR idle procs:1 seq(0,0,0)}
at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1505)
at android.app.ContextImpl.startService(ContextImpl.java:1461)
at android.content.ContextWrapper.startService(ContextWrapper.java:644)
at android.support.v4.content.WakefulBroadcastReceiver.startWakefulService(WakefulBroadcastReceiver.java:99)
at com.google.firebase.iid.zzg.b(zzg.java:9)
at com.google.firebase.iid.zzg.a(zzg.java:72)
at com.google.firebase.iid.zzg.a(zzg.java:2)
at com.google.firebase.iid.FirebaseInstanceIdService.a(FirebaseInstanceIdService.java:23)
at com.google.firebase.iid.FirebaseInstanceIdService.a(FirebaseInstanceIdService.java:34)
at com.google.firebase.iid.FirebaseInstanceId.<init>(FirebaseInstanceId.java:31)
at com.google.firebase.iid.FirebaseInstanceId.getInstance(FirebaseInstanceId.java:47)
at com.google.firebase.iid.FirebaseInstanceId.a(FirebaseInstanceId.java:4)
at com.google.firebase.iid.FirebaseInstanceIdService.a(FirebaseInstanceIdService.java:19)
at com.google.firebase.iid.FirebaseInstanceIdService.b(FirebaseInstanceIdService.java:35)
at com.google.firebase.iid.zzb$zza$1.run(zzb.java:24)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
at java.lang.Thread.run(Thread.java:764)
Update Upgrading to 11.4.2 resolves this issue.
#KaMyLL is right. I had the same issue with our app and could solve it by replacing the IntentService (which we have started within onTokenRefresh()) with an JobIntentService.
Because I found the JobScheduler and JobIntentService docs a bit confusing, I would like to some everything up with some code snippets. I hope this makes everything clear to everyone having this issue.
What is causing this issue?
Due to the new Background Execution Limits of Android 8, you should not start background services anymore when the app could be in background:
While an app is in the foreground, it can create and run both foreground and background services freely. When an app goes into the background, it has a window of several minutes in which it is still allowed to create and use services. At the end of that window, the app is considered to be idle. At this time, the system stops the app's background services, just as if the app had called the services' Service.stopSelf() methods.
And also:
In many cases, your app can replace background services with JobScheduler jobs.
So for Android 7.x and below, using startService() when the app is in background is (as far as I know) no problem. But in Android 8, this results in a crash. In consequence, you should use a JobScheduler now. The behavioral difference between JobScheduler and an IntentService is that an IntentService is executed immediately. On the other hand, a job enqueued to a JobScheduler is not guaranteed to be executed immediately. The Android OS will determine when there is a good point of time to do so in order to be more energy efficient. So there might be a delay. And I have no idea so far how long this could take.
So one solution could be to check the OS version and branch your code using if-else. Fortunately, the support library helps us to solve this in a more elegant way without duplicating any code: JobIntentService, which basically does this for you under the hood.
How to reproduce the issue?
The first quote above states that the app still "has a window of several minutes in which it is still allowed to create and use services.", so in order to reproduce and debug the issue (with the example of onTokenRefresh() in Firebase), you could set a breakpoint before your start your service with startService(). Close the app and wait there for 5-10 minutes. Continue the execution and you will see the IllegalStateException from this question.
Being able to reproduce the issue as fundamental to make sure that our fixes really solve the problem.
How to migrate my IntenService to JobIntentService?
I use FirebaseInstanceIdService.onTokenRefresh() as an example:
a) Add the BIND_JOB_SERVICE permission to your service:
<service android:name=".fcm.FcmRegistrationJobIntentService"
android:exported="false"
android:permission="android.permission.BIND_JOB_SERVICE"/>
b) Instead of extending from IntentService, simply extend from android.support.v4.app.JobIntentService, rename the onHandleIntent(Intent) method to onHandleWork(Intent), and add a enqueueWork(Context, Intent) convenient function:
public class FcmRegistrationJobIntentService extends JobIntentService
{
// Unique job ID for this service.
static final int JOB_ID = 42;
// Convenience method for enqueuing work in to this service.
public static void enqueueWork(Context context, Intent work) {
enqueueWork(context, FcmRegistrationJobIntentService.class, JOB_ID, work);
}
#Override
protected void onHandleWork(#NonNull Intent intent) {
// the code from IntentService.onHandleIntent() ...
}
}
c) Start the job using the enqueueWork() convenient function:
public class ComfyFirebaseInstanceIdService extends FirebaseInstanceIdService {
#Override
public void onTokenRefresh() {
Intent intent = new Intent(this, FcmRegistrationJobIntentService.class);
// startService(intent);
FcmRegistrationJobIntentService.enqueueWork(this, intent);
}
}
I hope this example is helpful. At least after following these steps, I was not able to reproduce the issue on my Android 8 device anymore, and it continues to work an my Android 7 device.
Update
as FirebaseInstanceIdService deprecated we should remove this from the code, and use onNewToken from FirebaseMessagingService instead.
I've done some research about it and the best option is to transform IntentService into JobIntentService available in app compat library. It would behave like IntentService on all pre-Oreo devices. On Android 8 and above it will enqueue job to android system JobScheduler. This job by default have set deadline parameter to 0, so theoretically it should fire as fast as possible.
as of today (26/10/2018), FirebaseInstanceIDServie isdeprecated, try this to fix the above issue link

reloadTimeline() doesn't update complication

I'm trying to make a watchOS 3 app, and I want to update my complication in a background task.
First, I get new data from a server in a background task within handle(). After that, I update my active complications by calling complicationServer.reloadTimeline(for:).
In the console I do see the message "UPDATE COMPLICATION," so the code is executed.
Yet after reloading, the complication still shows the old data. If I switch the watch face and switch back, then the complication sometimes reloads. Do I have to do something else to reload the complication from the background task?
func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
for task : WKRefreshBackgroundTask in backgroundTasks {
if (WKExtension.shared().applicationState == .background) {
if task is WKApplicationRefreshBackgroundTask {
let dataProvider = DataProvider()
dataProvider.getData(station: "Name", completion: { (data, error) in
self.updateComplication()
self.scheduleNextBackgroundRefresh()
task.setTaskCompleted()
})
}
} else {
task.setTaskCompleted()
}
}
}
func updateComplication() {
let complicationServer = CLKComplicationServer.sharedInstance()
for complication in complicationServer.activeComplications! {
print("UPDATE COMPLICATION")
complicationServer.reloadTimeline(for: complication)
}
}
Your current approach:
You've got a mix of watchOS 2 and watchOS 3 approaches there.
WKApplicationRefreshBackgroundTask (new watchOS 3 approach)
DataProvider starts asynchronous transfer (old watchOS 2 approach which assumes it's in the foreground, not the background).
That background thread (of a background task which may or may not be suspended yet not completed) expects to have the background task handle its completion, once the asynchronous transfer completes.
In short, you've expected your background refresh task to wait in the background for an asynchronous transfer. This is bit convoluted (since the refresh task is supposed to be doing work in the background, not waiting on other work to complete).
A better way for watchOS 3:
Since an asynchronous transfer can be suspended, you would be better off using a URLSession background transfer.
Always upload and download data using an URLSession background transfer. Background transfers occur in a separate process. They continue to transfer the data even after your app has terminated. Asynchronous uploads and downloads, on the other hand, are suspended with your app. Given the short run time of watchOS apps, you cannot guarantee that an asynchronous transfer will finish before the app is suspended.
By letting a WKURLSessionRefreshBackgroundTask respond to the background transfer, your extension can be woken in the background once the session completes, hand off that session's data to the data provider, and then update the complication.
An suggestion about the data provider:
It seems to have the responsibility of both transferring data, and providing data. You may want to consider splitting off the network portion into a separate component, and simply let it be a repository of data.
I get the impression that it's meant to be some manner of singleton (behind the scenes) yet you initialize an instance as a DataProvider().
From a readability viewpoint, it's not apparent from the provided code that the complication data source will be using the same data provider as the one who received the data.
You should avoid force unwrapping optionals:
When activeComplications is nil (such as when the complication had been removed from the watch face between the last update and this update), your code will ungraciously crash.
You should use a guard or if let to first check that you still have an active complication.

Submit a task to an ExecutorService using a SheduleExecutorService

I'm developing a JavaFX application for read data from a serial device and show a notification when a new device is connected to the computer.
I have a task DeviceDetectorTask which scans all the ports and creates an event when a new device is connected. This task must be submited every 3 seconds.
When a device is detected the user can press a button to read all the data contained in it. This is performed by another task ReadDeviceTask. At this point and while the ReadDeviceTask is running scan operations should not be performed (I cannot read and scan one port at the same time). So only one of the two task can be running at a time.
My actual solution is:
public class DeviceTaskQueue {
private ExecutorService executorService = Executors.newSingleThreadExecutor();
public void submit(Runnable task) {
executorService.submit(task);
}
}
public class ScanScheduler {
private ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
public void start() {
AddScanTask task = new AddScanTask();
executor.scheduleAtFixedRate(task, 0, 3, TimeUnit.SECONDS);
}
}
public class AddScanTask implements Runnable {
#Autowired
DeviceTaskQueue deviceTaskQueue;
#Override
public void run() {
deviceTaskQueue.submit(new DeviceDetectorTask());
}
}
public class ViewController {
#Autowired
DeviceTaskQueue deviceTaskQueue;
#FXML
private readDataFromDevice() {
deviceTaskQueue.submit(new ReadDeviceTask());
}
}
My question is: is it ok to add a task to the ExecutorService from the task AddScanTask which has been scheduled by the ScheduledExecutorService?
Yes, An Executor May Post Task To Another Executor
To answer your simple question in last line:
is it ok to add a task to the ExecutorService from the task AddScanTask which has been scheduled by the ScheduledExecutorService?
Yes. Certainly you can submit a Callable/Runnable from any other code. That the submitting code happens to be running from another executor is irrelevant, as code run from an executor is still “normal” Java code, just running on a different thread.
That is the whole point of the executor, to handle the juggling of threads in a manner convenient to you the programmer. Making multi-threaded coding easier and less error-prone is why these classes were added to Java. See the extremely helpful book, Java Concurrency in Practice by Brian Goetz et al. And see other writings by Goetz.
In your case you have two executors each with their own thread, each executing a series of submitted tasks. One has tasks submitted automatically (timed) while the other has tasks submitted manually (arbitrarily). Each executes on their own thread independent of one another. With multiple cores they may execute simultaneously.
Therein lies the bigger problem: In your scenario you don't want them to be independent. You want the reading tasks to block the scanning tasks.
Bigger Problem
The problem you present is that a regularly occurring activity (scanning) must halt when an arbitrary event (reading) happens. That means the two activities must coordinate with one another. The question is how to coordinate.
Semaphores
When the arbitrary event is happening, it should raise a flag. The recurring activity, when it runs, should always check for that flag. If raised, wait until the flag lowers before proceeding with scan. The ScheduledExecutorService is designed for this, tolerating a task that may run for a time longer than the scheduled period. If one execution of the task runs long, the SES does not run again, so it does not pile up a backlog of executions. That is just the behavior you want.
Vice versa, if the recurring activity is executing, it should raise a flag. The arbitrary event’s first to-do item is to check for that flag. If raised, wait until lowered. Then proceed, first raising its own flag and then proceeding with the task at hand (scanning).
Perhaps your scenario should be designed with a single flag rather than scanner and reader each having their own. I would have to think about it more and probably know more about your scenario.
The technical term for such flags is semaphore.
Unfortunately your comment says you cannot alter the scanner’s source code. So you cannot implement the semaphores and coordinate the activities. So I am stuck, cannot see a solution.
Hack
Given your frozen code, one hack solution, which I do not recommend, is that the regularly occurring activity (the scanning) not actually do the work but instead post a scanning task on another thread (another executor). That other executor would also be the same executor used to post the arbitrary activity (the reading). So there is one single queue of to-do items, a mix of scanning and reading jobs, submitted to a single-thread executor. The single-thread means they get done one at a time in sequence of their submission.
I do not like this hack because if any of the to-do items takes a long while you will begin to accumulate a backlog. That could be a mess.
By the way, no need for the DeviceTaskQueue in your example code. Just call the instance of the ExecutorService directly to submit a task. That is the job of an ExecutorService, and wrapping it adds no value that I can see.

Decreased BLE startScan detected devices on Android 5.0 Lollipop

Short version:
In my tests with Android 5.0 Lollipop I have noticed android.bluetooth.le.BluetoothLeScanner detects BLE devices less frequently than Android 4.4 KitKat. Why is this and is there an alternative?
Long version:
I am developing an Android application, specifically for the Nexus 7 tablet, that focuses on detecting Bluetooth Low Energy (BLE) devices. The app is mainly interested in the RSSI value of the beacons, to determine their proximity to the tablet. This means I won't need to connect to the BLE device, since the RSSI value is passed to the scan callback when the device is detected.
In Android 4.4 KitKat, when I call BluetoothAdapter.startLeScan(LeScanCallback), my callback gets called only ONCE for every detected BLE device. (I have seen some discussions claim that this behaviour can differ per device) However, I am interested in the constantly changing RSSI value, so the currently recommended way is to continuously do startLeScan and stopLeScan with a set interval (250ms in my case):
public class TheOldWay {
private static final int SCAN_INTERVAL_MS = 250;
private Handler scanHandler = new Handler();
private boolean isScanning = false;
public void beginScanning() {
scanHandler.post(scanRunnable);
}
private Runnable scanRunnable = new Runnable() {
#Override
public void run() {
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (isScanning) {
adapter.stopLeScan(leScanCallback);
} else if (!adapter.startLeScan(leScanCallback)) {
// an error occurred during startLeScan
}
isScanning = !isScanning;
scanHandler.postDelayed(this, SCAN_INTERVAL_MS);
}
};
private BluetoothAdapter.LeScanCallback leScanCallback = new BluetoothAdapter.LeScanCallback() {
#Override
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
// use the RSSI value
}
};
}
Essentially this gives me the required results, but this process is very resource intensive and eventually leads to an unresponsive bluetooth adapter.
For these reasons I upgraded my Nexus 7 to Android 5.0 Lollipop, to see whether my BLE issues would be fixed. In Lollipop BluetoothAdapter.startLeScan(LeScanCallback) is deprecated and replaced with a new API that allows for some more control over the scanning process. From my first tests, it appears startScan does not continuously call my callback (on my Nexus 7) when the RSSI values change, so I still need to use the startScan / stopScan implementation:
#TargetApi(21)
public class TheNewWay {
private static final int SCAN_INTERVAL_MS = 250;
private Handler scanHandler = new Handler();
private List<ScanFilter> scanFilters = new ArrayList<ScanFilter>();
private ScanSettings scanSettings;
private boolean isScanning = false;
public void beginScanning() {
ScanSettings.Builder scanSettingsBuilder = new ScanSettings.Builder();
scanSettingsBuilder.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY);
scanSettings = scanSettingsBuilder.build();
scanHandler.post(scanRunnable);
}
private Runnable scanRunnable = new Runnable() {
#Override
public void run() {
BluetoothLeScanner scanner = BluetoothAdapter.getDefaultAdapter().getBluetoothLeScanner();
if (isScanning) {
scanner.stopScan(scanCallback);
} else {
scanner.startScan(scanFilters, scanSettings, scanCallback);
}
isScanning = !isScanning;
scanHandler.postDelayed(this, SCAN_INTERVAL_MS);
}
};
private ScanCallback scanCallback = new ScanCallback() {
#Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
int rssi = result.getRssi();
// do something with RSSI value
}
#Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
// a scan error occurred
}
};
}
As you can see, I have configured the scanner using the ScanSettings class, which allows you to set the scanMode. I use ScanSettings.SCAN_MODE_LOW_LATENCY, which has the following documentation: "Scan using highest duty cycle. It's recommended to only use this mode when the application is running in the foreground." Sounds exactly like what I want, but unfortunately I only get a beacon detect every 15 - 30 seconds, where the KitKat version shows me the same beacon every 1 - 2 seconds on this scan interval.
Do you have any idea what could be the reason for this difference? Am I missing something, maybe some new settings? Are there alternative ways of doing the above?
Thanks a lot in advance!
Abel
PS: I wanted to include more links to resources I've used, but I don't have the rep points for it yet.
I have gotten very different results with a Nexus 5 running the new Android 5.0 scanning APIs. Detections of BLE packets came in at near real time when using SCAN_MODE_LOW_LATENCY, at every 100ms for BLE beacons transmitting at 10Hz.
You can read the full results here:
http://developer.radiusnetworks.com/2014/10/28/android-5.0-scanning.html
These tests are based off of running the open source Android Beacon Library 2.0's experimental android-l-apis branch here.
It is not obvious what the difference is in your test results, but it is possible that starting and stopping scanning is changing the results.
EDIT: it is possible the hardware is the difference. See a report of similar timings on the Nexus 4: https://github.com/AltBeacon/android-beacon-library/issues/59#issuecomment-64281446
I don't have 50 reputation for a comment yet, so bear with me, this comment will be in the form of an answer. In your code, shouldn't this part:
if (isScanning) {
scanner.startScan(...)
be this instead:
if (!isScanning) {
scanner.startScan(...)
Because following your code, you're calling stopScan() before starting a scan. It may not have a direct effect on the result if the stopScan() method is idempotent/safe. But you know, for the sake of code intelligibility you should edit the question. And do the same to your code, sometimes byzantine things are at play ;)
Have you tried larger values for SCAN_INTERVAL_MS? If yes, how large?
I have experienced very similar results with my Nexus 4, in both KitKat and Lollipop.
With KitKat the bluetooth adapter also eventually went unresponsive; at first I though that it could be related to a short scan interval (200ms) but increasing that number to even a second didn't help, in that matter I found that, when unresponsive disabling and enabling the adapter programmatically, sometimes solves the problem. Unfortunately I can't say that it works all the time.
Now with Lollipop, in which I had high hopes to solve this issues, I experienced the same behaviour that you describe. I also had to use the startScan / stopScan implementation, getting similar results regarding the detection times. Sadly, I haven't found a work around to get results more quickly.
Based on what you describe I suppose it could be a hardware issue, even though the Nexus 7 and Nexus 4 are from different manufacturers (Asus and LG).
I know I'm not providing much help here besides trying to answer your question about you missing something; I don't think so, I think the problem is something like the hardware or the bluetooth API that still doesn't behave the way it should across different devices.
Beyond API 21 android uses SCAN_MODE_LOW_POWER by default.
SCAN_MODE_LOW_POWER
Try SCAN_MODE_BALANCED and see if it gets better.
SCAN_MODE_BALANCED
if you search for BW13_DayOne_Session1 Bluetooth Advanced on google, you will find a pdf document that gives you the latencies for devices based on the settings for discovery (see page 8). I'm guessing your problem has to do with these timings. You can verify by figuring out the advertising configuration for the device you are testing (Adv Int, Duty Cycle) then figure out what the API settings are doing for configuring the scan interval, etc. Once you have these, you can then use that table to interpolate to see if your getting the results you expect.
I know this is a software site, but often when interfacing with hardware you need to know the protocol otherwise your shooting in the dark.

Resources