Watts App Part 2 — Fixing the update frequency

In Part 1 I wrote about my inspiration and my first version of a simple little app that would show me the battery level of my phone on my Android Wear watch, while the phone was charging. Some problems became apparent once I’d finished. The main one was that the JobScheduler on Nougat enforces a minimum of fifteen-minutes between running jobs. In a world where quick-charging can give you five hours worth of battery life in five minutes, restricting battery charge updates to fifteen minutes makes the app all but useless.

Let’s try the AlarmManager

JobScheduler proved itself to be overkill for my simple use case. Its optimisations to the running of repeated background tasks made it unsuitable for short-term frequent updates. Maybe this was because the job I wanted to schedule is much more basic than the type of operations it’s intended for (network calls etc).

I decided to try out AlarmManager instead. This has much less aggressive batching of scheduled tasks, and uses a BroadcastReceiver rather than a Service. The documentation contains this interesting note:

The Alarm Manager is intended for cases where you want to have your application code run at a specific time, even if your application is not currently running. For normal timing operations (ticks, timeouts, etc) it is easier and much more efficient to useHandler.

The first sentence sounds about right — I want my application code to run at a specific time, even if it’s not running. The second part makes me wonder if Handler would work better for me, or if the documentation is referring to things like updating something on-screen during the lifetime of an Activity. I’ll have to try that approach sometime. For the moment, I decided to go with AlarmManager. It’s an API I’ve not used before, and I was curious about it.

AlarmManager — Implementation

The first step was to replace my old JobService with a BroadcastReceiver. The receiver has to be registered in the AndroidManifest, but doesn’t require any intent filters, so the declaration is simple:

<receiver android:name=".AlarmReceiver"/>

The reason it doesn’t need any intent filters is because we’ll creating a PendingIntent which calls the receiver directly, like so:

val alarmIntent = Intent(context, AlarmReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(context, JOB_ID, alarmIntent, 0)

The AlarmReceiver class itself looks almost exactly like the old PowerService class. It gets the NotificationManager system service, gets the latest battery information, and then fires a notification:

In my PowerConnectionReceiver, I replaced the scheduling of a JobInfo in JobScheduler with the scheduling of my AlarmReceiver PendingIntent on the AlarmManager system service. I’ve supplied a value of zero for the triggerAtMillis parameter and a value of one minute (my JOB_INTERVAL constant) for intervalMillis, telling the AlarmManager to fire the first broadcast as soon as the charging event is detected, and repeat at one-minute intervals. Now it looks like this:

Side note: The batteryStatus variable in this snippet is the Intent which holds the latest battery info, but I’ve added a couple of extension functions to it so I can perform operations directly on the object, rather than passing it to another method as a parameter. In case you haven’t seen this before, it’s a lovely (and very powerful) feature of the Kotlin programming language. Try it out!

AlarmManager kind of threatens to be a harsh mistress, but isn’t really

As per the documentation:

Note: Beginning with API 19 (KITKAT) alarm delivery is inexact: the OS will shift alarms in order to minimize wakeups and battery use… Applications whose targetSdkVersion is earlier than API 19 will continue to see the previous behavior in which all alarms are delivered exactly when requested.

This sounds familiar! There’s definitely some overlap with JobScheduler's batching optimisations, and these go back as far as Kitkat! But AlarmManager seems to be a lot more relaxed than JobScheduler. It might be “inexact”, but it’s not “a minimum of fifteen minutes”. I haven’t run any actual timing tests, but it’s at least been in the right ballpark. The shifting is likely to be more visible if working with shorter intervals.

Great success!

Once I’d swapped out the JobScheduler for AlarmManager (and remembered to register my AlarmReceiver in the Manifest, which was another brain-far moment), it worked like a charm! Again, most of the caveats and drawbacks of an AlarmManager strategy are not applicable to my use case, since my app only runs when the device is charging anyway.

But there’s still more room for improvement. The notification shows on both devices, so maybe I’ll create a Wear module so I can show the notification only on the watch. I realised I’d also like this functionality in reverse — a way to tell if my watch is charged yet, from my phone.

The code for WattsApp is available on my github.