blob: 72f312c36f43830a6ba47e3e0a6bfb96f71c8def [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.server.task.controllers;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.SystemClock;
import com.android.server.task.TaskManagerService;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
/**
* This class sets an alarm for the next expiring task, and determines whether a task's minimum
* delay has been satisfied.
*/
public class TimeController extends StateController {
private static final String TAG = "TaskManager.Time";
private static final String ACTION_TASK_EXPIRED =
"android.content.taskmanager.TASK_EXPIRED";
private static final String ACTION_TASK_DELAY_EXPIRED =
"android.content.taskmanager.TASK_DELAY_EXPIRED";
/** Set an alarm for the next task expiry. */
private final PendingIntent mTaskExpiredAlarmIntent;
/** Set an alarm for the next task delay expiry. This*/
private final PendingIntent mNextDelayExpiredAlarmIntent;
private long mNextTaskExpiredElapsedMillis;
private long mNextDelayExpiredElapsedMillis;
private AlarmManager mAlarmService = null;
/** List of tracked tasks, sorted asc. by deadline */
private final List<TaskStatus> mTrackedTasks = new LinkedList<TaskStatus>();
/** Singleton. */
private static TimeController mSingleton;
public static synchronized TimeController get(TaskManagerService taskManager) {
if (mSingleton == null) {
mSingleton = new TimeController(taskManager);
}
return mSingleton;
}
private TimeController(TaskManagerService service) {
super(service);
mTaskExpiredAlarmIntent =
PendingIntent.getBroadcast(mContext, 0 /* ignored */,
new Intent(ACTION_TASK_EXPIRED), 0);
mNextDelayExpiredAlarmIntent =
PendingIntent.getBroadcast(mContext, 0 /* ignored */,
new Intent(ACTION_TASK_DELAY_EXPIRED), 0);
// Register BR for these intents.
IntentFilter intentFilter = new IntentFilter(ACTION_TASK_EXPIRED);
intentFilter.addAction(ACTION_TASK_DELAY_EXPIRED);
mContext.registerReceiver(mAlarmExpiredReceiver, intentFilter);
}
/**
* Check if the task has a timing constraint, and if so determine where to insert it in our
* list.
*/
@Override
public synchronized void maybeStartTrackingTask(TaskStatus task) {
if (task.hasTimingDelayConstraint()) {
ListIterator<TaskStatus> it = mTrackedTasks.listIterator(mTrackedTasks.size());
while (it.hasPrevious()) {
TaskStatus ts = it.previous();
if (ts.equals(task)) {
// Update
it.remove();
it.add(task);
break;
} else if (ts.getLatestRunTimeElapsed() < task.getLatestRunTimeElapsed()) {
// Insert
it.add(task);
break;
}
}
maybeUpdateAlarms(task.getEarliestRunTime(), task.getLatestRunTimeElapsed());
}
}
/**
* If the task passed in is being tracked, figure out if we need to update our alarms, and if
* so, update them.
*/
@Override
public synchronized void maybeStopTrackingTask(TaskStatus taskStatus) {
if (mTrackedTasks.remove(taskStatus)) {
if (mNextDelayExpiredElapsedMillis <= taskStatus.getEarliestRunTime()) {
handleTaskDelayExpired();
}
if (mNextTaskExpiredElapsedMillis <= taskStatus.getLatestRunTimeElapsed()) {
handleTaskDeadlineExpired();
}
}
}
/**
* Set an alarm with the {@link android.app.AlarmManager} for the next time at which a task's
* delay will expire.
* This alarm <b>will not</b> wake up the phone.
*/
private void setDelayExpiredAlarm(long alarmTimeElapsedMillis) {
ensureAlarmService();
mAlarmService.set(AlarmManager.ELAPSED_REALTIME, alarmTimeElapsedMillis,
mNextDelayExpiredAlarmIntent);
}
/**
* Set an alarm with the {@link android.app.AlarmManager} for the next time at which a task's
* deadline will expire.
* This alarm <b>will</b> wake up the phone.
*/
private void setDeadlineExpiredAlarm(long alarmTimeElapsedMillis) {
ensureAlarmService();
mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTimeElapsedMillis,
mTaskExpiredAlarmIntent);
}
/**
* Determines whether this controller can stop tracking the given task.
* The controller is no longer interested in a task once its time constraint is satisfied, and
* the task's deadline is fulfilled - unlike other controllers a time constraint can't toggle
* back and forth.
*/
private boolean canStopTrackingTask(TaskStatus taskStatus) {
return (!taskStatus.hasTimingDelayConstraint() ||
taskStatus.timeDelayConstraintSatisfied.get()) &&
(!taskStatus.hasDeadlineConstraint() ||
taskStatus.deadlineConstraintSatisfied.get());
}
private void maybeUpdateAlarms(long delayExpiredElapsed, long deadlineExpiredElapsed) {
if (delayExpiredElapsed < mNextDelayExpiredElapsedMillis) {
mNextDelayExpiredElapsedMillis = delayExpiredElapsed;
setDelayExpiredAlarm(mNextDelayExpiredElapsedMillis);
}
if (deadlineExpiredElapsed < mNextTaskExpiredElapsedMillis) {
mNextTaskExpiredElapsedMillis = deadlineExpiredElapsed;
setDeadlineExpiredAlarm(mNextTaskExpiredElapsedMillis);
}
}
private void ensureAlarmService() {
if (mAlarmService == null) {
mAlarmService = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
}
}
/**
* Handles alarm that notifies that a task has expired. When this function is called at least
* one task must be run.
*/
private synchronized void handleTaskDeadlineExpired() {
long nextExpiryTime = Long.MAX_VALUE;
final long nowElapsedMillis = SystemClock.elapsedRealtime();
Iterator<TaskStatus> it = mTrackedTasks.iterator();
while (it.hasNext()) {
TaskStatus ts = it.next();
final long taskDeadline = ts.getLatestRunTimeElapsed();
if (taskDeadline <= nowElapsedMillis) {
ts.deadlineConstraintSatisfied.set(true);
mStateChangedListener.onTaskDeadlineExpired(ts);
it.remove();
} else { // Sorted by expiry time, so take the next one and stop.
nextExpiryTime = taskDeadline;
break;
}
}
maybeUpdateAlarms(Long.MAX_VALUE, nextExpiryTime);
}
/**
* Handles alarm that notifies us that a task's delay has expired. Iterates through the list of
* tracked tasks and marks them as ready as appropriate.
*/
private synchronized void handleTaskDelayExpired() {
final long nowElapsedMillis = SystemClock.elapsedRealtime();
long nextDelayTime = Long.MAX_VALUE;
Iterator<TaskStatus> it = mTrackedTasks.iterator();
while (it.hasNext()) {
final TaskStatus ts = it.next();
if (!ts.hasTimingDelayConstraint()) {
continue;
}
final long taskDelayTime = ts.getEarliestRunTime();
if (taskDelayTime < nowElapsedMillis) {
ts.timeDelayConstraintSatisfied.set(true);
if (canStopTrackingTask(ts)) {
it.remove();
}
} else { // Keep going through list to get next delay time.
if (nextDelayTime > taskDelayTime) {
nextDelayTime = taskDelayTime;
}
}
}
mStateChangedListener.onControllerStateChanged();
maybeUpdateAlarms(nextDelayTime, Long.MAX_VALUE);
}
private final BroadcastReceiver mAlarmExpiredReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// An task has just expired, so we run through the list of tasks that we have and
// notify our StateChangedListener.
if (ACTION_TASK_EXPIRED.equals(intent.getAction())) {
handleTaskDeadlineExpired();
} else if (ACTION_TASK_DELAY_EXPIRED.equals(intent.getAction())) {
handleTaskDelayExpired();
}
}
};
}