package nl.sense_os.service.scheduler; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import nl.sense_os.service.scheduler.Scheduler.Task; import android.app.AlarmManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.os.SystemClock; import android.util.Log; /** * * @author Kimon Tsitsikas <kimon@sense-os.nl> */ public class ScheduleAlarmTool { private static final int REQ_CODE_DETERMINISTIC = 0xf01c0de; private static final int REQ_CODE_OPPORTUNISTIC = 0xf21d0de; private static ScheduleAlarmTool sInstance; private static final String TAG = "ScheduleAlarmTool"; /** * Returns the greatest common divisor of p and q * * @param p * @param q */ private static long gcd(long p, long q) { if (q == 0) { return p; } return gcd(q, p % q); } /** * Factory method to get the singleton instance. * * @param context * @return instance */ public static ScheduleAlarmTool getInstance(Context context) { if (null == sInstance) { sInstance = new ScheduleAlarmTool(context); } return sInstance; } private long mBackwardsFlex; private Context mContext; private long mNextExecution = 0; private long mRemainingFlex; private List<Task> mTasks = new ArrayList<Scheduler.Task>(); /** * Constructor. * * @param context * @param tasksList * @see #getInstance(Context) */ protected ScheduleAlarmTool(Context context) { mContext = context; } /** * Cancels the alarm * * @param context * Context to access AlarmManager */ public void cancelDeterministicAlarm() { // re-create the operation that would go off Intent intent = new Intent(mContext, ExecutionAlarmReceiver.class); intent.putExtra(ExecutionAlarmReceiver.EXTRA_EXECUTION_TYPE, ExecutionAlarmReceiver.DETERMINISTIC_TYPE); PendingIntent operation = PendingIntent.getBroadcast(mContext, REQ_CODE_DETERMINISTIC, intent, PendingIntent.FLAG_ONE_SHOT); // cancel the alarm AlarmManager mgr = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); mgr.cancel(operation); } public void cancelOpportunisticAlarm() { // re-create the operation that would go off Intent intent = new Intent(mContext, ExecutionAlarmReceiver.class); intent.putExtra(ExecutionAlarmReceiver.EXTRA_EXECUTION_TYPE, ExecutionAlarmReceiver.OPPORTUNISTIC_TYPE); PendingIntent operation = PendingIntent.getBroadcast(mContext, REQ_CODE_OPPORTUNISTIC, intent, PendingIntent.FLAG_ONE_SHOT); // cancel the alarm AlarmManager mgr = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); mgr.cancel(operation); } /** * Calculates the next execution time and returns the batch of tasks that need to be executed at * that time. * * @return */ private Runnable getBatchTask() { // find the next upcoming execution time // TODO: use the normal Arrays.sort() method final List<Runnable> tasksToExecute = new CopyOnWriteArrayList<Runnable>(); Task temp; for (int i = 0; i < mTasks.size(); i++) { for (int j = 1; j < (mTasks.size() - i); j++) { if (mTasks.get(j - 1).nextExecution > mTasks.get(j).nextExecution) { temp = mTasks.get(j - 1); mTasks.set(j - 1, mTasks.get(j)); mTasks.set(j, temp); } } } // decide which tasks are to be executed at the next execution time mNextExecution = mTasks.get(0).nextExecution; tasksToExecute.add(mTasks.get(0).runnable); // try to delay the execution in order to group multiple tasks together mRemainingFlex = mTasks.get(0).flexibility; // flexibility for opportunistic execution if CPU is on mBackwardsFlex = mTasks.get(0).flexibility; int i; for (i = 1; i < mTasks.size(); i++) { mRemainingFlex -= (mTasks.get(i).nextExecution - mTasks.get(i - 1).nextExecution); // see if there is enough flexibility to batch with this task if (mRemainingFlex >= 0) { // postpone execution time of the batch to this task mNextExecution = mTasks.get(i).nextExecution; // update the backward flexibility if the new batched task is less flexible if (mTasks.get(i).flexibility < mBackwardsFlex) { mBackwardsFlex = mTasks.get(i).flexibility; } tasksToExecute.add(mTasks.get(i).runnable); } else { break; } } // prepare the next execution time of the tasks that are going to be executed for (int j = 0; j < i; j++) { mTasks.get(j).nextExecution = mNextExecution + mTasks.get(j).interval; } // create one summarized task Runnable batchTask = new Runnable() { @Override public void run() { for (Runnable task : tasksToExecute) { task.run(); } } }; return batchTask; } /** * Resets the next scheduled execution time. */ public void resetNextExecution() { Log.v(TAG, "Reset next execution"); mNextExecution = 0; // reset the next execution time for all tasks for (Task task : mTasks) { if (task.nextExecution > SystemClock.elapsedRealtime()) { // decrease execution time until it is back to the first possible time while (task.nextExecution - task.interval > SystemClock.elapsedRealtime()) { task.nextExecution -= task.interval; } } else { // task does not have a valid next execution time (yet) Task foundTask = null; long gcd = 1; // try to find GCD in the list for (Task gcdTask : mTasks) { if (task.equals(gcdTask)) { // do not get the gcd with yourself, dummy continue; } long tempGcd = gcd(task.interval, gcdTask.interval); if (tempGcd > gcd) { gcd = tempGcd; foundTask = gcdTask; } } if (gcd == 1) { // schedule the next execution in ¨interval¨ milliseconds from now task.nextExecution = SystemClock.elapsedRealtime() + task.interval; } else { task.nextExecution = foundTask.nextExecution; while (task.nextExecution - task.interval > SystemClock.elapsedRealtime()) { task.nextExecution -= task.interval; } } } } } /** * Schedules the next task to execute. * */ public void schedule() { // check if there is anything to schedule if (mTasks.isEmpty()) { mNextExecution = 0; return; } // get the next batch of tasks Runnable batchTask = getBatchTask(); ExecutionAlarmReceiver.setBatchTask(batchTask); // schedule the alarms AlarmManager mgr = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); // set the alarm for opportunistic execution Intent opportunisticIntent = new Intent(mContext, ExecutionAlarmReceiver.class); opportunisticIntent.putExtra(ExecutionAlarmReceiver.EXTRA_EXECUTION_TYPE, ExecutionAlarmReceiver.OPPORTUNISTIC_TYPE); PendingIntent opportunisticOperation = PendingIntent.getBroadcast(mContext, REQ_CODE_OPPORTUNISTIC, opportunisticIntent, PendingIntent.FLAG_ONE_SHOT); mgr.set(AlarmManager.ELAPSED_REALTIME, (mNextExecution - mBackwardsFlex), opportunisticOperation); // set the alarm for deterministic execution Intent deterministicIntent = new Intent(mContext, ExecutionAlarmReceiver.class); deterministicIntent.putExtra(ExecutionAlarmReceiver.EXTRA_EXECUTION_TYPE, ExecutionAlarmReceiver.DETERMINISTIC_TYPE); PendingIntent deterministicOperation = PendingIntent.getBroadcast(mContext, REQ_CODE_DETERMINISTIC, deterministicIntent, PendingIntent.FLAG_ONE_SHOT); mgr.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, mNextExecution, deterministicOperation); } /** * Sets the task list. */ public void setTasks(List<Task> tasks) { mTasks = new CopyOnWriteArrayList<Task>(tasks); } }