“Watts App” — Is my phone charged yet? Let me check my watch

Brendan Fahy
11 min readJan 19, 2017

--

I’ve had an Android Wear watch for the last 18 months or so, a first generation Moto 360, and I’ve always had a vague intention to do something with it. Something cool, some project I could pump a load of code into that would make this smartwatch truly mine. I’d looked at documentation about Android Wear. I’d read blog posts. I’d seen watch apps written live on stage at conferences, and deployed, in front of an ooohing, aaahing audience. I was going to do that too, and it was going to be awesome.

Except I could never figure out what to do. There’s no documentation for the APIs in our heads that return project ideas. Well, no Javadocs anyway. Definitely nothing Swagger-compliant.

Last week I found myself looking at my watch to see if my phone was fully charged yet. I was ensconced for the day in a meeting room with some co-workers, discussing very secret things, while my phone charged at my desk, a few meters away. Surely, in this day and age, my watch can tell me if it’s done yet, right?

I did zero research. I bet there’s watch faces, and watch apps, and all sorts of tools that could solve the problem for me. But I just looked at my watch and I couldn’t figure out a way to see it. And that’s when I finally had an idea for something to code on Android Wear.

TL;DR — If you’d rather not read all the words I wrote, and would rather see the code I wrote, check out the repo on my github.

A New Idea has Entered the Game!

I wanted something to show on my watch when my phone was charging, which showed the charge level of the device as a percentage, and was updated periodically. The Creative Vision for Android Wear says its experiences should be:

  • Automatically launched
  • Glanceable
  • All about suggest and demand
  • Zero or low interaction

I’ve always thought this could be summed up as: “Notifications. Just do notifications. Properly.”

The mirroring of notifications from my phone to my watch is about 90% of the value I get from it. The other 10% is in context-specific use cases: handy controls for Google Play Music, Pocket Casts, and Runkeeper. I think I can count on one hand the number of times I’ve gone swiping through the interface to launch an app or send a message — and even less times when I’ve used voice input. And I reckon every one of those instances was when I was demoing that functionality to someone who’d never seen a smartwatch before.

So all I wanted was a notification on my phone. Cool, those are easy, the NotificationManager is my friend. What about responding to and reading battery-charging events? That’s easy too, there’s a handy article on the Android developer docs about it. What about scheduling a task to update the notification once we’d created it?

There are multiple ways to set up a repeating task in the background, like JobScheduler and AlarmManager. There’s other tools too, like GcmNetworkManager, which has a confusingly network-and-GCM-centric name, and straight-up Services were never much fun for me. Evernote released their own library, android-job, that allows you to get on with coding and not worry about which mechanism is being used under the hood. But I was all fired up and enthusiastic, and I’d used JobScheduler once before on a little project, so I went with that. It’s the latest cool new toy for scheduling background jobs! It’s supposed to be great! It’s the way forward for Android! And hey, I didn’t want have to go read a bunch of documentation!

So I chose to use JobScheduler, and immediately had to go read a bunch of documentation. It was added in API 21, Lollipop. That’s OK, my phone runs Nougat (API 25), so we’re all good. JobScheduler allows us to leverage the operating system’s intelligence to decide when to run our jobs, batching them appropriately, in order to minimise operational costs to the device. This rang a couple of bells in my head, since I only wanted my job to run when my phone was charging anyway, so any potential impact on the battery wasn’t a problem (unless it was huge). I didn’t have to worry about the consequences of waking up the CPU unnecessarily. But I forged on ahead anyway (this is commonly known as foreshadowing).

“Watts App” — The Implementation

The app I built is very, very simple. It’s written in Kotlin, because Kotlin is awesome and the semi-colon key on my keyboard is broken. I decided to call it “Watts App”, because it was about charging my battery and I don’t understand how electricity works.

The app contains:

  • An AndroidManifest,
  • A BroadcastReceiver which gets called anytime the phone gets plugged in or out,
  • A JobService which gets scheduled if it was an ACTION_POWER_CONNECTED event or cancelled if it was an ACTION_POWER_DISCONNECTED event
  • And a single Activity, which is really only there for debug purposes. I’d never made an app without an Activity before. It felt weird.

Declaring the BroadcastReceiver

The first thing to do with a BroadcastReceiver is register it in your manifest file. Well, second thing — you need to decide on a name for it first. I took “inspiration” from the training article and called mine PowerConnectionReceiver. You need to specify the broadcasts you want it to respond to. In my case, these are the battery charging/not-charging events:

You’ll notice there’s a third intent-filter action added here, named brendan.wattsapp.debug. While working out the kinks in the code, it was frustrating to have to rely on plugging the phone in and out to make things happen — especially given that unplugging the phone severs the ADB connection, and therefore any debugging or logging.

Side note: I was about to write, “Yes, I know wireless ADB exists, but you need root for that, and this is the first reason I’ve had to root a phone in years” and then I decided to Google it to back up my assumption, and found that the very first result is an app called ADB Wireless (no root), so I’m feeling rather silly right now.

You can trigger a BroadcastReceiver directly by broadcasting an Intent with the same action as the one you defined in the manifest file. So I added a Button to my MainActivity, and had the button call:

And then I had a handy way to hit my BroadcastReceiver without having to unplug my phone!

Writing the BroadcastReceiver

The onReceive method of your BroadcastReceiver receives the Intent used to trigger it, as a parameter. According to the training article, you can use this to extract the current charging state and method.

This didn’t work as advertised for me. If someone can point out a mistake, I’d be glad to learn where I went wrong. Guided by the article, I tried:

isCharging, isUnknown and hasBeenDisconnected all came out of this false for me. status was -1, the default value if BatteryManager.EXTRA_STATUS didn’t match an int extra. ¯\_(ツ)_/¯

Luckily, you can register a null BroadcastReceiver with an IntentFilter for Intent.ACTION_BATTERY_CHANGED any time you want, and this immediately returns an Intent with the latest battery state. I modified my PowerConnectionReceiver to ignore the Intent passed as a parameter to onReceive and request one from the system.

My status was now showing meaningful values. Once I knew whether the phone had been plugged in or out, I could decide whether to schedule or cancel my job:

Building the JobInfo object is more interesting:

You need to instantiate a ComponentName for the JobService class — the actual work that you want to schedule — and use it to create a Builder for the JobInfo, along with a job ID. The job ID can be used later to cancel a specific job (using JobScheduler’s cancel method), or, if on API 24 or above, to retrieve the JobInfo object for a job which has not been executed yet, using getPendingJob(jobId).

JobScheduler’s power comes from its flexibility. You can build your JobInfo with constraints on available network type, device charging state, timing and scheduling, backoff strategy — I’d highly recommend having a look at the documentation if you haven’t seen it already.

For me, I didn’t require a network, required the device to be charging (lol), and wanted the job to run every 60 seconds. Of course, the interval you send to the operating system tells it how often you want it to run the job. The OS reserves the right to do whatever the hell it wants with that information, but it’s nice to express your opinions, even if nobody is listening (see Twitter, this post, and so forth).

Declaring the JobService

Like all Service class, your JobService needs to be declared in your AndroidManifest.xml. You need to give it a name, and tell the system it’s allowed to be bound. Here’s mine:

Writing the JobService

In my PowerService, I request another Intent with the latest battery state, so I can show a notification with the charge level. (I had a momentary brain-fart trying to figure out where to get a Context on which to call registerReceiver, which made me kick myself and facepalm at the same time, in a new gesture I call the “kickface”. On the plus side, it also led me to the most efficient StackOverflow answer I’ve ever seen.)

The two important parts of the PowerService are its interaction with the JobScheduler and its use of the NotificationManager.

onJobFinished is a JobService method that must be called in your onStartJob, with the JobParameters which were passed in, and an interesting boolean, needsReschedule. This should be true if the work you wanted to perform failed for some reason. true tells the JobScheduler to start applying whatever back-off strategy you set in the JobInfo in an attempt to successfully complete the job. I like to live dangerously, so I assume that my notification was successfully posted, and return false. The return value of the method is another boolean, which tells JobScheduler whether your job is still doing work on another thread or not. Useful for asynchronous operations, not so much for this case. I return false.

It’s almost beginning to seem like JobScheduler wasn’t the right tool for the job. Almost.

NotificationManager

NotificationManager’s notify method tells us:

If a notification with the same id has already been posted by your application and has not yet been canceled, it will be replaced by the updated information.

So now we know we can spam the notify method with the same notification ID, safe in the knowledge that we’ll only ever have one notification with the most up-to-date information.

Creating Notifications is a breeze using the Notification.Builder:

The only interesting thing here is the BatteryManager.EXTRA_LEVEL retrieved from the battery status Intent, which gives us the battery level as an integer between 0 and 100, and the setOnlyAlertOnce(true) method on the Builder. I want a notification I can look at, but I don’t want the updates bothering.

But honestly, I’m not too familiar with this API. I think you need to manually setLights, setSound, and setVibrate if you want those things anyway, which I’m not doing, so there’s a good chance I can do without this method.

And that’s pretty much it! In onStopJob, I clear out the notification with notificationManager.cancel(NOTIFICATION_ID). Here’s how it looks in action:

National Geographic here I come

So there we have it! My first watch app, and I didn’t write a single line of code that had anything to do with Android wear! A notification that gets updated with the latest battery level every sixty seconds or so, and gets cleared when the phone is unplugged. Right?

JobScheduler is a harsh mistress

From onStopJob:

This method is called if the system has determined that you must stop execution of your job even before you’ve had a chance to call jobFinished(JobParameters, boolean).

So this will only work if the phone gets unplugged in the time between onStartJob being called and it calling jobFinished. This is extremely unlikely to ever happen.

I was very excited the first time I ran the app. This was before I’d added my handy debug button in MainActivity, so I unplugged the phone, re-plugged it in again to trigger the BroadcastReceiver, and sat back to wait for my notification to show up. The interval for the job was set to 60 seconds, so I shouldn’t have to wait long. After much waiting, and unplugging, and re-plugging, and waiting again, I noticed this in the Logcat output:

brendan.wattsapp W/JobInfo: Specified interval for 123 is +1m0s0ms. Clamped to +15m0s0ms`

JobScheduler gets to make its own mind up about when to run your job — that’s part of the point. But I’d expected this to be pretty inconsequential for lax parameters like mine. This log makes it look like I have a 1500% multiplier on my interval to look forward to. What gives?

Nougat is also a harsh mistress

I’m not the first person to notice this. I found an explanation on StackOverflow. As of Nougat, this tidbit appears in the documentation for JobInfo.Builder.setPeriodic’s intervalMillis parameter:

long: Millisecond interval for which this job will repeat. A minimum value of getMinPeriodMillis() is enforced.

getMinPeriodMillis apparently returns fifteen minutes. You can usesetOverrideDeadline and force the job to run — but a job can’t be both periodic and override the deadline.

Conclusion

This was a lot of fun. I wrote the first version of Watts App in a couple of hours after work earlier this week, and by the time I finished, it was mostly working. Mostly.

The notification only updates every fifteen minutes or so. That’s workable, but it’s not what I wanted. I added a really hacky line to my PowerConnectionReceiver to cancel the notification when it was stopping the job:

So now at least it goes away automatically. But it’s clearly the wrong way to achieve that result.

One idea to improve things is to schedule two jobs: one with setOverrideDeadline set to something very small, to show the first notification, and a second like the one above, to update periodically. Another, probably smarter idea, is to use the AlarmManager to show and update the notification instead. The caveats of AlarmManager are things like “it‘s not as responsible/efficient as JobScheduler” and “it doesn’t persist across reboots”, but they don’t matter to this particular use case. And I’ve never used it before. So I’ll probably try that.

Another foible of the current implementation is that the notification will show on the phone as well. That’s not really a problem, but it is a bit pointless. You can build a notification to be local-only — that is, to show only on the device that created it. So I can easily make this notification only show on my phone, and not on my watch, but that’s the opposite of what I want. to make it show on the watch, and not on the phone, I’ll have to write an Android Wear app, and create my local-only notification from there. I think.

Even More Conclusion

Writing this blog post was also a lot of fun. I’ve never written a technical blog post before. If you’ve read this far, thank you, and I hope you enjoyed it. There will be a follow-up to this post, hopefully sometime soon. In the meantime, I occasionally say things on Twitter and help organise the Dublin Android Developer Meetup.

The wattsapp repo is located on my github.

--

--