Handling alarms

Now that we know how to schedule alarms, let's take a look at what we can schedule with them.

Essentially, we can schedule anything that can be started with a PendingIntent, which means we can use alarms to start Activities, Services, and BroadcastReceivers. To specify the target of our alarm, we need to use the static factory methods of PendingIntent:

PendingIntent.getActivities(…)
PendingIntent.getActivity(…)
PendingIntent.getService(…)
PendingIntent.getBroadcast(…)

In the following sections, we'll see how each type of PendingIntent can be used with AlarmManager.

Starting an Activity from an alarm is as simple as registering the alarm with a PendingIntent created by invoking the static getActivity method of PendingIntent.

When the alarm is delivered, the Activity will be started and brought to the foreground, displacing any app that was currently in use. Keep in mind that this is likely to surprise and perhaps annoy users!

When starting Activities with alarms, we will probably want to set Intent.FLAG_ACTIVITY_CLEAR_TOP; so that if the application is already running, our target Activity assumes its normal place in the navigation order within the app.

Not all Activities are suited to being started with getActivity. We might need to start an Activity that normally appears deep within the app, where pressing "back" does not exit to the home screen, but returns to the next Activity on the back-stack.

This is where getActivities comes in. With getActivities, we can push more than one Activity onto the back-stack of the application, allowing us to populate the back-stack to create the desired navigation flow when the user presses "back". To do this, we create our PendingIntent by sending an array of Intents to getActivities:

The array of Intents specifies the Activities to launch, in order. The logical sequence of events when this alarm is delivered is:

Handling alarms with Activities is good to know about, but is not a technique we will use often, since it is so intrusive. We are much more likely to want to handle alarms in the background, which we'll look at next.

We met BroadcastReceiver already in Chapter 6, Long-running Tasks with Service, where we used it in an Activity to receive broadcasts from an IntentService. In this section, we'll use BroadcastReceiver to handle alarms.

BroadcastReceivers can be registered and unregistered dynamically at runtime—like we did in Chapter 6, Long-running Tasks with Service, or statically in the Android manifest file with a <receiver> element, and can receive alarms regardless of how they are registered.

It is more common to use a statically registered receiver for alarms, because these are known to the system and can be invoked by alarms to start an application if it is not currently running. A receiver registration in the manifest looks like this:

<receiver android:name=".AlarmReceiver">
    <intent-filter>
        <action android:name="reminder"/>
    </intent-filter>
</receiver>

The <intent-filter> element gives us the opportunity to say which Intents we want to receive, by specifying the action, data, and categories that should match. Here we're just matching Intents with the action reminder, so we can set an alarm for this receiver with:

Intent intent = new Intent("reminder");
intent.putExtra(AlarmReceiver.MSG, "try the alarm examples!");
PendingIntent broadcast = PendingIntent.getBroadcast(
    context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
long at = calculateNextMidnightRTC();
am.set(AlarmManager.RTC_WAKEUP, at, broadcast);

When this alarm comes due, AlarmManager will wake the device—if it isn't already awake—and deliver the Intent to the BroadcastReceiver's onReceive method.

The AlarmManager guarantees that the device will remain awake at least until onReceive completes, which means we can be sure of getting some work done before the device will be allowed to return to sleep.

When the system delivers an alarm to our BroadcastReceiver it does so on the main thread, so the usual main thread limitations apply: we cannot perform networking and we should not perform heavy processing or use blocking operations.

In addition, a statically registered BroadcastReceiver has a very limited lifecycle. It cannot create user-interface elements other than toasts or notifications posted via NotificationManager; the onReceive method must complete within 10 seconds or its process may be killed; and once onReceive completes, the receiver's life is over.

If the work that we need to do in response to the alarm is not intensive, we can simply complete it during onReceive. A good use for BroadcastReceivers that receive alarms is posting notifications to the notification drawer as follows:

We can make this more useful by including a PendingIntent object in the notification, so that the user can tap the notification to open an Activity in our app:

Although we can spend up to 10 seconds doing work in our BroadcastReceiver, we really shouldn't—if the app is in use when the alarm is triggered the user will suffer noticeable lag if onReceive takes more than a hundred milliseconds to complete. Exceeding the 10 second budget will cause the system to kill the application and report a background ANR.

A second option is available to us only if we're targeting API level 11 and above, and allows onReceive to delegate work to a background thread for up to 10 seconds—we'll discuss this in the next section.

Just like starting Activities, starting a Service from an alarm involves scheduling an appropriate PendingIntent instance, this time using the static getService method.

We almost certainly want our Service to do its work off the main thread, so sending work to an IntentService this way seems ideal, and an IntentService will also stop itself when the work is finished.

This works beautifully if the device is awake.

However, if the device is asleep we have a potential problem. AlarmManager's documentation tells us that the only guarantee we have about the wakefulness of the device is that it will remain awake until a BroadcastReceiver's onReceive method completes.

Since directly starting a Service does not involve a BroadcastReceiver, and in any case is an asynchronous operation, there is no guarantee that the Service will have started up before the device returns to sleep, so the work may not get done until the device is next awakened.

This is almost certainly not the behavior we want. We want to ensure that the Service starts up and completes its work, regardless of whether the device was awake when the alarm was delivered. To do that, we'll need a BroadcastReceiver and a little explicit power management, as we'll see next.