Monday

Android - Reliable Alarms to Services

I love programming for Android. The APIs just make sense. You can tell that fluent Java developers designed this thing. Despite the fact that everyone's favorite criticism is that Android is "fragmented" I haven't had any significant cross-hardware issues. Follow the guidelines (What?!? I have to follow rules if I want my app to work? So much for freedom...) and 95% of apps should just work on any platform without much trouble. I'd say you're even a step ahead of iOS development because the platform has extremely simple ways to support multiple screen sizes, so your app can easily look good on any device. That said, there are occasional bugs, and this post is about one of them.

This may not even be a bug, but rather a case of poor documentation. The problem arrises when you use the AlarmManager to schedule a repeating alarm that is supposed to wake the phone and start a service. e.g.,

PendingIntent pi = PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, timeToStart, 15 * 60 * 1000, pi);

It will probably work great during most of your testing as the phone will be awake. However, once you stick your phone in your pocket and forget about it for a while, you'll realize, "Hey, wasn't an alarm supposed to go off?" You'll pull your phone out, turn it on, and suddenly it will go off. The first few times you'll think it's odd, until you realize that the alarms aren't going off when the phone has been asleep for awhile, but they are delivered as soon as it wakes.

Hopefully you find this post or something like it and don't have to spend days Googling to figure out that only a BroadcastReceiver can reliably receive a wakeup alarm and then spend another day learning that the Context that onReceive gets can't start services. The solution is pretty simple, but you'd never figure it out from the documentation.

EDIT: I may be totally wrong about the context being a problem as you can see in the comments. Try it without the using getContext first and if you don't have any problems getting the service to start, avoid the Cargo Cult code I'm using below. If you do have problems and the context trick helps, please let me know in the comments.

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager.NameNotFoundException;


public class AlarmProxy extends BroadcastReceiver {

    Context getContext(Context ctx) {
        try {
            return ctx.createPackageContext("com.yourpackage.ofyourapp", 0);
        } catch (NameNotFoundException ignore) {}
    }
    
    
    @Override
    public void onReceive(Context ctx, Intent intent) {
        Context context = getContext(ctx);
        Intent newIntent = new Intent(context, YourService.class);
        newIntent.setData(intent.getData());
        newIntent.putExtras(intent);
        context.startService(newIntent);
    }
    
}

The context your receiver gets is probably a ReceiverRestrictedContext which isn't connected to your application and can't start services, so you may need to use createPackageContext to get your app context and start your service. You'd then set alarms by sending them to your receiver:

Intent intent = new Intent(this,AlarmProxy.class);
// ...
PendingIntent pi = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, timeToStart, 15 * 60 * 1000, pi);

Happy alarming!

EDIT: Don't forget to register the receiver in your Android manifest:

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