Using IntentService With AlarmManager to Schedule Alarms

Posted: November 27, 2012 in Android, WSU CS
Tags: , , , , , , ,

Many Linux developers look for a cron like service when starting to develop on Android, well luckily it exists in the form of AlarmManager. AlarmManager allows you to schedule your application to run at a time in the future, however it is cleared on boot–should/can be re registered with the system through OnBoot BroadcastReceiver.

AndroidManifest.xml; you need to register the two BroadcastReceivers, and the service. Also get permission to use the WakeLock, and to get Boot_Completed signal.

    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
<application>
     <receiver android:name=".service.OnBootReceiver" >
         <intent-filter>
            <action android:name="android.intent.action.BOOT_COMPLETED" />
         </intent-filter>
     </receiver>

     <receiver android:name=".service.OnAlarmReceiver" >
     </receiver>

     <service android:name=".service.TaskButlerService" >
     </service>
</application>

This is a BroadcastReceiver for the OnBoot complete, used to reschedule alarms with the AlarmManager since after boot the alarms are flashed out. There is only 2 lines of code in the onReceive() method, that is due to your own onReceive() needing to be short. The first line acquires a partial WakeLock to keep the CPU running, while our IntentService is executing.

package edu.worcester.cs499summer2012.service;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

/**
 * BroadCastReceiver for android.intent.action.BOOT_COMPLETED
 * passes all responsibility to TaskButlerService.
 * @author Dhimitraq Jorgji
 *
 */
public class OnBootReceiver extends BroadcastReceiver{
	@Override
	public void onReceive(Context context, Intent intent) {

		WakefulIntentService.acquireStaticLock(context); //acquire a partial WakeLock
		context.startService(new Intent(context, TaskButlerService.class)); //start TaskButlerService
	}
}

IntentService is my favorite way of getting things done in the background, separate from the main thread of my application. Usually I don’t inherit IntentService directly, and I suggest you do the same; define a synchronized method to acquire a WakeLock before you continue on with whatever you need to accomplish.

package edu.worcester.cs499summer2012.service;

import android.app.IntentService;
import android.content.Context;
import android.content.Intent;
import android.os.PowerManager;

/**
 * Acquires a partial WakeLock, allows TaskButtlerService to keep the CPU alive
 * until the work is done.
 * @author Dhimitraq Jorgji
 *
 */
public class WakefulIntentService extends IntentService {
	public static final String
	LOCK_NAME_STATIC="edu.worcester.cs499summer2012.TaskButlerService.Static";;
	public static final String
	LOCK_NAME_LOCAL="edu.worcester.cs499summer2012.TaskButlerService.Local";
	private static PowerManager.WakeLock lockStatic=null;
	private PowerManager.WakeLock lockLocal=null;

	public WakefulIntentService(String name) {
		super(name);
	}
	/**
	 * Acquire a partial static WakeLock, you need too call this within the class
	 * that calls startService()
	 * @param context
	 */
	public static void acquireStaticLock(Context context) {
		getLock(context).acquire();
	}

	synchronized private static PowerManager.WakeLock getLock(Context context) {
		if (lockStatic==null) {
			PowerManager
			mgr=(PowerManager)context.getSystemService(Context.POWER_SERVICE);
			lockStatic=mgr.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
					LOCK_NAME_STATIC);
			lockStatic.setReferenceCounted(true);
		}
		return(lockStatic);
	}

	@Override
	public void onCreate() {
		super.onCreate();
		PowerManager mgr=(PowerManager)getSystemService(Context.POWER_SERVICE);
		lockLocal=mgr.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
				LOCK_NAME_LOCAL);
		lockLocal.setReferenceCounted(true);
	}

	@Override
	public void onStart(Intent intent, final int startId) {
		lockLocal.acquire();
		super.onStart(intent, startId);
		getLock(this).release();
	}

	@Override
	protected void onHandleIntent(Intent intent) {
		lockLocal.release();
	}
}

Now we can simply inheriting the WakeFulIntentService, and do all our work with in one simple method onHandleIntent(Intent). The method can be called from within anywhere in your program and it will handle everything on a background thread like any Service, also safely since it holds a WakeLock until the method completes at which point it returns the lock and exits nicely.

package edu.worcester.cs499summer2012.service;

import java.util.List;

import android.content.Intent;

import edu.worcester.cs499summer2012.database.TasksDataSource;
import edu.worcester.cs499summer2012.task.Task;

/**
 * An IntentService that takes care of setting up alarms for Task Butler
 * to remind the user of upcoming events
 * @author Dhimitraq Jorgji
 *
 */
public class TaskButlerService extends WakefulIntentService{

	public TaskButlerService() {
		super("TaskButlerService");
	}

	@Override
	protected void onHandleIntent(Intent intent) {
		TasksDataSource db = TasksDataSource.getInstance(this); //get access to the instance of TasksDataSource
		TaskAlarm alarm = new TaskAlarm();

		List<Task> tasks = db.getAllTasks(); //Get a list of all the tasks there
		for (Task task : tasks) {
			// Cancel existing alarm
			alarm.cancelAlarm(this, task.getID());

			//Procrastinator and Reminder alarm
			if(task.isPastDue()){
				alarm.setReminder(this, task.getID());
			}

			//handle repeat alarms
			if(task.isRepeating() && task.isCompleted()){
				task = alarm.setRepeatingAlarm(this, task.getID());
			}

			//regular alarms
			if(!task.isCompleted() && (task.getDateDue() >= System.currentTimeMillis())){
				alarm.setAlarm(this, task);
			}
		}
		super.onHandleIntent(intent);
	}
}

At this point you just need a BroadcastReceiver to receive your alarms.

package edu.worcester.cs499summer2012.service;

import edu.worcester.cs499summer2012.database.TasksDataSource;
import edu.worcester.cs499summer2012.task.Task;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;

/**
 * BroadCastReceiver for Alarms, displays notifications as it receives alarm
 * and then starts TaskButlerService to update alarm schedule with AlarmManager
 * @author Dhimitraq Jorgji
 *
 */
public class OnAlarmReceiver extends BroadcastReceiver {
	@Override
	public void onReceive(Context context, Intent intent) {
		WakefulIntentService.acquireStaticLock(context); //acquire a partial WakeLock

		//send notification, bundle intent with taskID
		NotificationHelper notification = new NotificationHelper();
		Bundle bundle = intent.getExtras();
		int id = bundle.getInt(Task.EXTRA_TASK_ID);
		TasksDataSource db = TasksDataSource.getInstance(context);
		Task task = db.getTask(id);

		if(task.hasFinalDateDue() || task.getPriority() == Task.URGENT){
			notification.sendPersistentNotification(context, task); // send basic notification
		} else {
			notification.sendBasicNotification(context, task); // send basic notification
		}

		context.startService(new Intent(context, TaskButlerService.class)); //start TaskButlerService
	}
}

The setAlarm method:
NOTE:I use a method to create PendingIntent so my pending intents mach if the id passed in is the same, and because of of the FLAG_UPDATE_CURRENT the pending intent updates a possibly existing PendingIntent rather than duplicating.

	/**
	 * Set a One Time Alarm using the taskID
	 * @param context
	 * @param id id of task to retrieve task from SQLite database
	 */
	public void setAlarm(Context context, int id){
		TasksDataSource db = TasksDataSource.getInstance(context);
		Task task = db.getTask(id);
		AlarmManager am=(AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
		am.set(AlarmManager.RTC_WAKEUP, task.getDateDue(), getPendingIntent(context, id));
	}

	//get a PendingIntent
	PendingIntent getPendingIntent(Context context, int id) {
		Intent intent =  new Intent(context, OnAlarmReceiver.class)
		.putExtra(Task.EXTRA_TASK_ID, id);
		return PendingIntent.getBroadcast(context, id, intent, PendingIntent.FLAG_UPDATE_CURRENT);
	}

All of the source code for Task Butler can be found on GitHub, including the above code.

Any questions shoot away in the comments bellow.

Comments
  1. Kumar says:

    Hi, can you please explain about the flow you used in steps? When and why you have used services, receivers?

    • dhimitraq says:

      You schedule an alarm as a PendingIntent with the system’s AlarmManager, when time comes your defined BroadcastReceiver will be run by the system, the system wakes up the CPU to take care of the signal, and gives very little time to the BroadcastReceiver to do stuff which is why you need to get your own WakeLock to keep the CPU up until you are done. I simply call my IntentService for the sake of reusing code, but you could theoretically write the whole routine within the Receiver, also the way my code is written I need to call the IntentService to release the WakeLock.

      The PendingIntent can be registered from any thread UI or a Service.

      The OnBootReceiver works a bit differently since you don’t need to register the PendingIntent you are just being told by the OS that the system is ready after the boot. However you pretty much handle things the same way BroadcastReceiver->Service.

  2. munish says:

    can i perform any database operation by setting schedule date for that, like any alarm….please help me i need it in my project

    • dhimitraq says:

      Yes you can. The code above accesses the database after the alarm is received. BroadcastReceiver->IntentService(or any Service) and here you can do whatever work you need to do.

      All the source is on GitHub, there is a link at the last line of the blog.

  3. Ed Kawas says:

    Instead of holding wakelocks, why not use a wakefulbroadcast receiver?

    • dhimitraq says:

      What API level did they appear on? I did this a while ago, and I haven’t done anything android related in a while.

      • Steve Lohse says:

        WakefulBroadcastReceiver was included in revision 18 of the Android v4 Support Library (July 2013) which appeared after this article was written.

  4. Dear dhimitraq, I understand your code, but only thing confusing me is you used STATIC and LOCAL services can you explain me more on this…

  5. Johnny says:

    I get the error in line 58 of WakefuIntentService.java:

    Caused by: java.lang.RuntimeException: WakeLock under-locked test.mobile.core.IpdmsIntentService.Static
    at android.os.PowerManager$WakeLock.release(PowerManager.java:764)
    at android.os.PowerManager$WakeLock.release(PowerManager.java:735)
    at test.mobile.core.service_broadcastreceivers.WakefulIntentService.onStart(WakefulIntentService.java:58)

    I can only solve this with the following change in the onStart method:

    if (getLock(this).isHeld())
    getLock(this).release();

  6. […] EDIT: many longer write adult than creates clarity here, though take a feeling during this. […]

  7. […] onHandleIntent will run on a credentials thread, so we can perform your requests there. Check this link or this […]

  8. how else can i trigger the alarm aside from relying on a device boot. for instance, when a user installs my app but doesnt reboot in a very long time, how will the alarm trigger?

Leave a reply to Mohammed Audhil Cancel reply