summaryrefslogtreecommitdiffstats
path: root/services
diff options
context:
space:
mode:
authorMatthew Williams <mjwilliams@google.com>2014-05-14 11:06:04 -0700
committerMatthew Williams <mjwilliams@google.com>2014-05-29 00:45:02 +0000
commit9b9244b6941110ea2d940d9fc8eed0cdff96a016 (patch)
tree264c2df2c71b23437e66bf829e61b9ff46b2aa72 /services
parent3326a8782f7aae58b76b96d52d7756787ab401d0 (diff)
downloadframeworks_base-9b9244b6941110ea2d940d9fc8eed0cdff96a016.zip
frameworks_base-9b9244b6941110ea2d940d9fc8eed0cdff96a016.tar.gz
frameworks_base-9b9244b6941110ea2d940d9fc8eed0cdff96a016.tar.bz2
DO NOT MERGE Implementation of TaskManager reschedule/cancel
Issues here: "Reschedule" of an idle-mode task is not well-defined. In the API I throw an error if you try to set a back-off policy on an idle mode task. Implementation-wise, i add a delay for a reschedule request of an idle mode task. This means that if the phone's still in idle mode after the delay they app will get a call back, but otherwise it'll have to wait til the next one. Implemented all API functions
Diffstat (limited to 'services')
-rw-r--r--services/core/java/com/android/server/task/StateChangedListener.java3
-rw-r--r--services/core/java/com/android/server/task/TaskCompletedListener.java12
-rw-r--r--services/core/java/com/android/server/task/TaskManagerService.java520
-rw-r--r--services/core/java/com/android/server/task/TaskServiceContext.java522
-rw-r--r--services/core/java/com/android/server/task/TaskStore.java141
-rw-r--r--services/core/java/com/android/server/task/controllers/ConnectivityController.java43
-rw-r--r--services/core/java/com/android/server/task/controllers/IdleController.java8
-rw-r--r--services/core/java/com/android/server/task/controllers/StateController.java4
-rw-r--r--services/core/java/com/android/server/task/controllers/TaskStatus.java129
-rw-r--r--services/core/java/com/android/server/task/controllers/TimeController.java43
10 files changed, 908 insertions, 517 deletions
diff --git a/services/core/java/com/android/server/task/StateChangedListener.java b/services/core/java/com/android/server/task/StateChangedListener.java
index db2d4ee..b1a4636 100644
--- a/services/core/java/com/android/server/task/StateChangedListener.java
+++ b/services/core/java/com/android/server/task/StateChangedListener.java
@@ -27,9 +27,8 @@ public interface StateChangedListener {
/**
* Called by the controller to notify the TaskManager that it should check on the state of a
* task.
- * @param taskStatus The state of the task which has changed.
*/
- public void onTaskStateChanged(TaskStatus taskStatus);
+ public void onControllerStateChanged();
/**
* Called by the controller to notify the TaskManager that regardless of the state of the task,
diff --git a/services/core/java/com/android/server/task/TaskCompletedListener.java b/services/core/java/com/android/server/task/TaskCompletedListener.java
index 0210442..c53f5ca 100644
--- a/services/core/java/com/android/server/task/TaskCompletedListener.java
+++ b/services/core/java/com/android/server/task/TaskCompletedListener.java
@@ -16,6 +16,8 @@
package com.android.server.task;
+import com.android.server.task.controllers.TaskStatus;
+
/**
* Used for communication between {@link com.android.server.task.TaskServiceContext} and the
* {@link com.android.server.task.TaskManagerService}.
@@ -26,13 +28,5 @@ public interface TaskCompletedListener {
* Callback for when a task is completed.
* @param needsReschedule Whether the implementing class should reschedule this task.
*/
- public void onTaskCompleted(int serviceToken, int taskId, boolean needsReschedule);
-
- /**
- * Callback for when the implementing class needs to clean up the
- * {@link com.android.server.task.TaskServiceContext}. The scheduler can get this callback
- * several times if the TaskServiceContext got into a bad state (for e.g. the client crashed
- * and it needs to clean up).
- */
- public void onAllTasksCompleted(int serviceToken);
+ public void onTaskCompleted(TaskStatus taskStatus, boolean needsReschedule);
}
diff --git a/services/core/java/com/android/server/task/TaskManagerService.java b/services/core/java/com/android/server/task/TaskManagerService.java
index 80030b4..d5b70e6 100644
--- a/services/core/java/com/android/server/task/TaskManagerService.java
+++ b/services/core/java/com/android/server/task/TaskManagerService.java
@@ -19,10 +19,12 @@ package com.android.server.task;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
import android.app.task.ITaskManager;
import android.app.task.Task;
+import android.app.task.TaskManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Binder;
@@ -30,11 +32,17 @@ import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
-import android.os.UserHandle;
+import android.os.SystemClock;
import android.util.Slog;
import android.util.SparseArray;
+import com.android.server.task.controllers.ConnectivityController;
+import com.android.server.task.controllers.IdleController;
+import com.android.server.task.controllers.StateController;
import com.android.server.task.controllers.TaskStatus;
+import com.android.server.task.controllers.TimeController;
+
+import java.util.LinkedList;
/**
* Responsible for taking tasks representing work to be performed by a client app, and determining
@@ -44,61 +52,148 @@ import com.android.server.task.controllers.TaskStatus;
*/
public class TaskManagerService extends com.android.server.SystemService
implements StateChangedListener, TaskCompletedListener {
+ // TODO: Switch this off for final version.
+ private static final boolean DEBUG = true;
+ /** The number of concurrent tasks we run at one time. */
+ private static final int MAX_TASK_CONTEXTS_COUNT = 3;
static final String TAG = "TaskManager";
+ /**
+ * When a task fails, it gets rescheduled according to its backoff policy. To be nice, we allow
+ * this amount of time from the rescheduled time by which the retry must occur.
+ */
+ private static final long RESCHEDULE_WINDOW_SLOP_MILLIS = 5000L;
/** Master list of tasks. */
private final TaskStore mTasks;
+ static final int MSG_TASK_EXPIRED = 0;
+ static final int MSG_CHECK_TASKS = 1;
+
+ // Policy constants
+ /**
+ * Minimum # of idle tasks that must be ready in order to force the TM to schedule things
+ * early.
+ */
+ private static final int MIN_IDLE_COUNT = 1;
+ /**
+ * Minimum # of connectivity tasks that must be ready in order to force the TM to schedule
+ * things early.
+ */
+ private static final int MIN_CONNECTIVITY_COUNT = 2;
+ /**
+ * Minimum # of tasks (with no particular constraints) for which the TM will be happy running
+ * some work early.
+ */
+ private static final int MIN_READY_TASKS_COUNT = 4;
+
/**
* Track Services that have currently active or pending tasks. The index is provided by
* {@link TaskStatus#getServiceToken()}
*/
- private final SparseArray<TaskServiceContext> mActiveServices =
- new SparseArray<TaskServiceContext>();
+ private final List<TaskServiceContext> mActiveServices = new LinkedList<TaskServiceContext>();
+ /** List of controllers that will notify this service of updates to tasks. */
+ private List<StateController> mControllers;
+ /**
+ * Queue of pending tasks. The TaskServiceContext class will receive tasks from this list
+ * when ready to execute them.
+ */
+ private final LinkedList<TaskStatus> mPendingTasks = new LinkedList<TaskStatus>();
private final TaskHandler mHandler;
private final TaskManagerStub mTaskManagerStub;
- /** Check the pending queue and start any tasks. */
- static final int MSG_RUN_PENDING = 0;
- /** Initiate the stop task flow. */
- static final int MSG_STOP_TASK = 1;
- /** */
- static final int MSG_CHECK_TASKS = 2;
-
- private class TaskHandler extends Handler {
+ /**
+ * Entry point from client to schedule the provided task.
+ * This will add the task to the
+ * @param task Task object containing execution parameters
+ * @param uId The package identifier of the application this task is for.
+ * @param canPersistTask Whether or not the client has the appropriate permissions for persisting
+ * of this task.
+ * @return Result of this operation. See <code>TaskManager#RESULT_*</code> return codes.
+ */
+ public int schedule(Task task, int uId, boolean canPersistTask) {
+ TaskStatus taskStatus = new TaskStatus(task, uId, canPersistTask);
+ return startTrackingTask(taskStatus) ?
+ TaskManager.RESULT_SUCCESS : TaskManager.RESULT_FAILURE;
+ }
- public TaskHandler(Looper looper) {
- super(looper);
+ public List<Task> getPendingTasks(int uid) {
+ ArrayList<Task> outList = new ArrayList<Task>();
+ synchronized (mTasks) {
+ for (TaskStatus ts : mTasks.getTasks()) {
+ if (ts.getUid() == uid) {
+ outList.add(ts.getTask());
+ }
+ }
}
+ return outList;
+ }
- @Override
- public void handleMessage(Message message) {
- switch (message.what) {
- case MSG_RUN_PENDING:
-
- break;
- case MSG_STOP_TASK:
-
- break;
- case MSG_CHECK_TASKS:
- checkTasks();
- break;
+ /**
+ * Entry point from client to cancel all tasks originating from their uid.
+ * This will remove the task from the master list, and cancel the task if it was staged for
+ * execution or being executed.
+ * @param uid To check against for removal of a task.
+ */
+ public void cancelTaskForUid(int uid) {
+ // Remove from master list.
+ synchronized (mTasks) {
+ if (!mTasks.removeAllByUid(uid)) {
+ // If it's not in the master list, it's nowhere.
+ return;
}
}
+ // Remove from pending queue.
+ synchronized (mPendingTasks) {
+ Iterator<TaskStatus> it = mPendingTasks.iterator();
+ while (it.hasNext()) {
+ TaskStatus ts = it.next();
+ if (ts.getUid() == uid) {
+ it.remove();
+ }
+ }
+ }
+ // Cancel if running.
+ synchronized (mActiveServices) {
+ for (TaskServiceContext tsc : mActiveServices) {
+ if (tsc.getRunningTask().getUid() == uid) {
+ tsc.cancelExecutingTask();
+ }
+ }
+ }
+ }
- /**
- * Called when we need to run through the list of all tasks and start/stop executing one or
- * more of them.
- */
- private void checkTasks() {
- synchronized (mTasks) {
- final SparseArray<TaskStatus> tasks = mTasks.getTasks();
- for (int i = 0; i < tasks.size(); i++) {
- TaskStatus ts = tasks.valueAt(i);
- if (ts.isReady() && ! isCurrentlyActive(ts)) {
- assignTaskToServiceContext(ts);
- }
+ /**
+ * Entry point from client to cancel the task corresponding to the taskId provided.
+ * This will remove the task from the master list, and cancel the task if it was staged for
+ * execution or being executed.
+ * @param uid Uid of the calling client.
+ * @param taskId Id of the task, provided at schedule-time.
+ */
+ public void cancelTask(int uid, int taskId) {
+ synchronized (mTasks) {
+ if (!mTasks.remove(uid, taskId)) {
+ // If it's not in the master list, it's nowhere.
+ return;
+ }
+ }
+ synchronized (mPendingTasks) {
+ Iterator<TaskStatus> it = mPendingTasks.iterator();
+ while (it.hasNext()) {
+ TaskStatus ts = it.next();
+ if (ts.getUid() == uid && ts.getTaskId() == taskId) {
+ it.remove();
+ // If we got it from pending, it didn't make it to active so return.
+ return;
+ }
+ }
+ }
+ synchronized (mActiveServices) {
+ for (TaskServiceContext tsc : mActiveServices) {
+ if (tsc.getRunningTask().getUid() == uid &&
+ tsc.getRunningTask().getTaskId() == taskId) {
+ tsc.cancelExecutingTask();
+ return;
}
}
}
@@ -118,6 +213,17 @@ public class TaskManagerService extends com.android.server.SystemService
mTasks = new TaskStore(context);
mHandler = new TaskHandler(context.getMainLooper());
mTaskManagerStub = new TaskManagerStub();
+ // Create the "runners".
+ for (int i = 0; i < MAX_TASK_CONTEXTS_COUNT; i++) {
+ mActiveServices.add(
+ new TaskServiceContext(this, context.getMainLooper()));
+ }
+
+ mControllers = new LinkedList<StateController>();
+ mControllers.add(ConnectivityController.get(this));
+ mControllers.add(TimeController.get(this));
+ mControllers.add(IdleController.get(this));
+ // TODO: Add BatteryStateController when implemented.
}
@Override
@@ -126,53 +232,128 @@ public class TaskManagerService extends com.android.server.SystemService
}
/**
- * Entry point from client to schedule the provided task.
- * This will add the task to the
- * @param task Task object containing execution parameters
- * @param userId The id of the user this task is for.
- * @param uId The package identifier of the application this task is for.
- * @param canPersistTask Whether or not the client has the appropriate permissions for
- * persisting of this task.
- * @return Result of this operation. See <code>TaskManager#RESULT_*</code> return codes.
+ * Called when we have a task status object that we need to insert in our
+ * {@link com.android.server.task.TaskStore}, and make sure all the relevant controllers know
+ * about.
*/
- public int schedule(Task task, int userId, int uId, boolean canPersistTask) {
- TaskStatus taskStatus = mTasks.addNewTaskForUser(task, userId, uId, canPersistTask);
- return 0;
+ private boolean startTrackingTask(TaskStatus taskStatus) {
+ boolean added = false;
+ synchronized (mTasks) {
+ added = mTasks.add(taskStatus);
+ }
+ if (added) {
+ for (StateController controller : mControllers) {
+ controller.maybeStartTrackingTask(taskStatus);
+ }
+ }
+ return added;
}
- public List<Task> getPendingTasks(int uid) {
- ArrayList<Task> outList = new ArrayList<Task>(3);
+ /**
+ * Called when we want to remove a TaskStatus object that we've finished executing. Returns the
+ * object removed.
+ */
+ private boolean stopTrackingTask(TaskStatus taskStatus) {
+ boolean removed;
synchronized (mTasks) {
- final SparseArray<TaskStatus> tasks = mTasks.getTasks();
- final int N = tasks.size();
- for (int i = 0; i < N; i++) {
- TaskStatus ts = tasks.get(i);
- if (ts.getUid() == uid) {
- outList.add(ts.getTask());
- }
+ // Remove from store as well as controllers.
+ removed = mTasks.remove(taskStatus);
+ }
+ if (removed) {
+ for (StateController controller : mControllers) {
+ controller.maybeStopTrackingTask(taskStatus);
}
}
- return outList;
+ return removed;
}
- // StateChangedListener implementations.
+ private boolean cancelTaskOnServiceContext(TaskStatus ts) {
+ synchronized (mActiveServices) {
+ for (TaskServiceContext tsc : mActiveServices) {
+ if (tsc.getRunningTask() == ts) {
+ tsc.cancelExecutingTask();
+ return true;
+ }
+ }
+ return false;
+ }
+ }
/**
- * Off-board work to our handler thread as quickly as possible, b/c this call is probably being
- * made on the main thread.
- * For now this takes the task and if it's ready to run it will run it. In future we might not
- * provide the task, so that the StateChangedListener has to run through its list of tasks to
- * see which are ready. This will further decouple the controllers from the execution logic.
+ * @param ts TaskStatus we are querying against.
+ * @return Whether or not the task represented by the status object is currently being run or
+ * is pending.
*/
- @Override
- public void onTaskStateChanged(TaskStatus taskStatus) {
- postCheckTasksMessage();
-
+ private boolean isCurrentlyActive(TaskStatus ts) {
+ synchronized (mActiveServices) {
+ for (TaskServiceContext serviceContext : mActiveServices) {
+ if (serviceContext.getRunningTask() == ts) {
+ return true;
+ }
+ }
+ return false;
+ }
}
- @Override
- public void onTaskDeadlineExpired(TaskStatus taskStatus) {
+ /**
+ * A task is rescheduled with exponential back-off if the client requests this from their
+ * execution logic.
+ * A caveat is for idle-mode tasks, for which the idle-mode constraint will usurp the
+ * timeliness of the reschedule. For an idle-mode task, no deadline is given.
+ * @param failureToReschedule Provided task status that we will reschedule.
+ * @return A newly instantiated TaskStatus with the same constraints as the last task except
+ * with adjusted timing constraints.
+ */
+ private TaskStatus getRescheduleTaskForFailure(TaskStatus failureToReschedule) {
+ final long elapsedNowMillis = SystemClock.elapsedRealtime();
+ final Task task = failureToReschedule.getTask();
+
+ final long initialBackoffMillis = task.getInitialBackoffMillis();
+ final int backoffAttempt = failureToReschedule.getNumFailures() + 1;
+ long newEarliestRuntimeElapsed = elapsedNowMillis;
+
+ switch (task.getBackoffPolicy()) {
+ case Task.BackoffPolicy.LINEAR:
+ newEarliestRuntimeElapsed += initialBackoffMillis * backoffAttempt;
+ break;
+ default:
+ if (DEBUG) {
+ Slog.v(TAG, "Unrecognised back-off policy, defaulting to exponential.");
+ }
+ case Task.BackoffPolicy.EXPONENTIAL:
+ newEarliestRuntimeElapsed += Math.pow(initialBackoffMillis, backoffAttempt);
+ break;
+ }
+ long newLatestRuntimeElapsed = failureToReschedule.hasIdleConstraint() ? Long.MAX_VALUE
+ : newEarliestRuntimeElapsed + RESCHEDULE_WINDOW_SLOP_MILLIS;
+ return new TaskStatus(failureToReschedule, newEarliestRuntimeElapsed,
+ newLatestRuntimeElapsed, backoffAttempt);
+ }
+ /**
+ * Called after a periodic has executed so we can to re-add it. We take the last execution time
+ * of the task to be the time of completion (i.e. the time at which this function is called).
+ * This could be inaccurate b/c the task can run for as long as
+ * {@link com.android.server.task.TaskServiceContext#EXECUTING_TIMESLICE_MILLIS}, but will lead
+ * to underscheduling at least, rather than if we had taken the last execution time to be the
+ * start of the execution.
+ * @return A new task representing the execution criteria for this instantiation of the
+ * recurring task.
+ */
+ private TaskStatus getRescheduleTaskForPeriodic(TaskStatus periodicToReschedule) {
+ final long elapsedNow = SystemClock.elapsedRealtime();
+ // Compute how much of the period is remaining.
+ long runEarly = Math.max(periodicToReschedule.getLatestRunTimeElapsed() - elapsedNow, 0);
+ long newEarliestRunTimeElapsed = elapsedNow + runEarly;
+ long period = periodicToReschedule.getTask().getIntervalMillis();
+ long newLatestRuntimeElapsed = newEarliestRunTimeElapsed + period;
+
+ if (DEBUG) {
+ Slog.v(TAG, "Rescheduling executed periodic. New execution window [" +
+ newEarliestRunTimeElapsed/1000 + ", " + newLatestRuntimeElapsed/1000 + "]s");
+ }
+ return new TaskStatus(periodicToReschedule, newEarliestRunTimeElapsed,
+ newLatestRuntimeElapsed, 0 /* backoffAttempt */);
}
// TaskCompletedListener implementations.
@@ -181,54 +362,155 @@ public class TaskManagerService extends com.android.server.SystemService
* A task just finished executing. We fetch the
* {@link com.android.server.task.controllers.TaskStatus} from the store and depending on
* whether we want to reschedule we readd it to the controllers.
- * @param serviceToken key for the service context in {@link #mActiveServices}.
- * @param taskId Id of the task that is complete.
+ * @param taskStatus Completed task.
* @param needsReschedule Whether the implementing class should reschedule this task.
*/
@Override
- public void onTaskCompleted(int serviceToken, int taskId, boolean needsReschedule) {
- final TaskServiceContext serviceContext = mActiveServices.get(serviceToken);
- if (serviceContext == null) {
- Slog.e(TAG, "Task completed for invalid service context; " + serviceToken);
+ public void onTaskCompleted(TaskStatus taskStatus, boolean needsReschedule) {
+ if (!stopTrackingTask(taskStatus)) {
+ if (DEBUG) {
+ Slog.e(TAG, "Error removing task: could not find task to remove. Was task" +
+ "removed while executing?");
+ }
return;
}
+ if (needsReschedule) {
+ TaskStatus rescheduled = getRescheduleTaskForFailure(taskStatus);
+ startTrackingTask(rescheduled);
+ } else if (taskStatus.getTask().isPeriodic()) {
+ TaskStatus rescheduledPeriodic = getRescheduleTaskForPeriodic(taskStatus);
+ startTrackingTask(rescheduledPeriodic);
+ }
+ mHandler.obtainMessage(MSG_CHECK_TASKS).sendToTarget();
+ }
+
+ // StateChangedListener implementations.
+ /**
+ * Off-board work to our handler thread as quickly as possible, b/c this call is probably being
+ * made on the main thread.
+ * For now this takes the task and if it's ready to run it will run it. In future we might not
+ * provide the task, so that the StateChangedListener has to run through its list of tasks to
+ * see which are ready. This will further decouple the controllers from the execution logic.
+ */
+ @Override
+ public void onControllerStateChanged() {
+ // Post a message to to run through the list of tasks and start/stop any that are eligible.
+ mHandler.obtainMessage(MSG_CHECK_TASKS).sendToTarget();
}
@Override
- public void onAllTasksCompleted(int serviceToken) {
-
+ public void onTaskDeadlineExpired(TaskStatus taskStatus) {
+ mHandler.obtainMessage(MSG_TASK_EXPIRED, taskStatus);
}
- private void assignTaskToServiceContext(TaskStatus ts) {
- TaskServiceContext serviceContext =
- mActiveServices.get(ts.getServiceToken());
- if (serviceContext == null) {
- serviceContext = new TaskServiceContext(this, mHandler.getLooper(), ts);
- mActiveServices.put(ts.getServiceToken(), serviceContext);
+ private class TaskHandler extends Handler {
+
+ public TaskHandler(Looper looper) {
+ super(looper);
}
- serviceContext.addPendingTask(ts);
- }
- /**
- * @param ts TaskStatus we are querying against.
- * @return Whether or not the task represented by the status object is currently being run or
- * is pending.
- */
- private boolean isCurrentlyActive(TaskStatus ts) {
- TaskServiceContext serviceContext = mActiveServices.get(ts.getServiceToken());
- if (serviceContext == null) {
- return false;
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MSG_TASK_EXPIRED:
+ final TaskStatus expired = (TaskStatus) message.obj; // Unused for now.
+ queueReadyTasksForExecutionH();
+ break;
+ case MSG_CHECK_TASKS:
+ // Check the list of tasks and run some of them if we feel inclined.
+ maybeQueueReadyTasksForExecutionH();
+ break;
+ }
+ maybeRunNextPendingTaskH();
+ // Don't remove TASK_EXPIRED in case one came along while processing the queue.
+ removeMessages(MSG_CHECK_TASKS);
}
- return serviceContext.hasTaskPending(ts);
- }
- /**
- * Post a message to {@link #mHandler} to run through the list of tasks and start/stop any that
- * are eligible.
- */
- private void postCheckTasksMessage() {
- mHandler.obtainMessage(MSG_CHECK_TASKS).sendToTarget();
+ /**
+ * Run through list of tasks and execute all possible - at least one is expired so we do
+ * as many as we can.
+ */
+ private void queueReadyTasksForExecutionH() {
+ synchronized (mTasks) {
+ for (TaskStatus ts : mTasks.getTasks()) {
+ final boolean criteriaSatisfied = ts.isReady();
+ final boolean isRunning = isCurrentlyActive(ts);
+ if (criteriaSatisfied && !isRunning) {
+ synchronized (mPendingTasks) {
+ mPendingTasks.add(ts);
+ }
+ } else if (!criteriaSatisfied && isRunning) {
+ cancelTaskOnServiceContext(ts);
+ }
+ }
+ }
+ }
+
+ /**
+ * The state of at least one task has changed. Here is where we could enforce various
+ * policies on when we want to execute tasks.
+ * Right now the policy is such:
+ * If >1 of the ready tasks is idle mode we send all of them off
+ * if more than 2 network connectivity tasks are ready we send them all off.
+ * If more than 4 tasks total are ready we send them all off.
+ * TODO: It would be nice to consolidate these sort of high-level policies somewhere.
+ */
+ private void maybeQueueReadyTasksForExecutionH() {
+ synchronized (mTasks) {
+ int idleCount = 0;
+ int connectivityCount = 0;
+ List<TaskStatus> runnableTasks = new ArrayList<TaskStatus>();
+ for (TaskStatus ts : mTasks.getTasks()) {
+ final boolean criteriaSatisfied = ts.isReady();
+ final boolean isRunning = isCurrentlyActive(ts);
+ if (criteriaSatisfied && !isRunning) {
+ if (ts.hasIdleConstraint()) {
+ idleCount++;
+ }
+ if (ts.hasConnectivityConstraint() || ts.hasMeteredConstraint()) {
+ connectivityCount++;
+ }
+ runnableTasks.add(ts);
+ } else if (!criteriaSatisfied && isRunning) {
+ cancelTaskOnServiceContext(ts);
+ }
+ }
+ if (idleCount >= MIN_IDLE_COUNT || connectivityCount >= MIN_CONNECTIVITY_COUNT ||
+ runnableTasks.size() >= MIN_READY_TASKS_COUNT) {
+ for (TaskStatus ts : runnableTasks) {
+ synchronized (mPendingTasks) {
+ mPendingTasks.add(ts);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks the state of the pending queue against any available
+ * {@link com.android.server.task.TaskServiceContext} that can run a new task.
+ * {@link com.android.server.task.TaskServiceContext}.
+ */
+ private void maybeRunNextPendingTaskH() {
+ TaskStatus nextPending;
+ synchronized (mPendingTasks) {
+ nextPending = mPendingTasks.poll();
+ }
+ if (nextPending == null) {
+ return;
+ }
+
+ synchronized (mActiveServices) {
+ for (TaskServiceContext tsc : mActiveServices) {
+ if (tsc.isAvailable()) {
+ if (tsc.executeRunnableTask(nextPending)) {
+ return;
+ }
+ }
+ }
+ }
+ }
}
/**
@@ -268,11 +550,10 @@ public class TaskManagerService extends com.android.server.SystemService
public int schedule(Task task) throws RemoteException {
final boolean canPersist = canCallerPersistTasks();
final int uid = Binder.getCallingUid();
- final int userId = UserHandle.getCallingUserId();
long ident = Binder.clearCallingIdentity();
try {
- return TaskManagerService.this.schedule(task, userId, uid, canPersist);
+ return TaskManagerService.this.schedule(task, uid, canPersist);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -280,15 +561,38 @@ public class TaskManagerService extends com.android.server.SystemService
@Override
public List<Task> getAllPendingTasks() throws RemoteException {
- return null;
+ final int uid = Binder.getCallingUid();
+
+ long ident = Binder.clearCallingIdentity();
+ try {
+ return TaskManagerService.this.getPendingTasks(uid);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
}
@Override
public void cancelAll() throws RemoteException {
+ final int uid = Binder.getCallingUid();
+
+ long ident = Binder.clearCallingIdentity();
+ try {
+ TaskManagerService.this.cancelTaskForUid(uid);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
}
@Override
public void cancel(int taskId) throws RemoteException {
+ final int uid = Binder.getCallingUid();
+
+ long ident = Binder.clearCallingIdentity();
+ try {
+ TaskManagerService.this.cancelTask(uid, taskId);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
}
/**
@@ -311,9 +615,7 @@ public class TaskManagerService extends com.android.server.SystemService
synchronized (mTasks) {
pw.print("Registered tasks:");
if (mTasks.size() > 0) {
- SparseArray<TaskStatus> tasks = mTasks.getTasks();
- for (int i = 0; i < tasks.size(); i++) {
- TaskStatus ts = tasks.get(i);
+ for (TaskStatus ts : mTasks.getTasks()) {
pw.println();
ts.dump(pw, " ");
}
diff --git a/services/core/java/com/android/server/task/TaskServiceContext.java b/services/core/java/com/android/server/task/TaskServiceContext.java
index 165445a..75e9212 100644
--- a/services/core/java/com/android/server/task/TaskServiceContext.java
+++ b/services/core/java/com/android/server/task/TaskServiceContext.java
@@ -24,6 +24,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -36,20 +37,18 @@ import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.task.controllers.TaskStatus;
import java.util.concurrent.atomic.AtomicBoolean;
/**
- * Maintains information required to bind to a {@link android.app.task.TaskService}. This binding
- * is reused to start concurrent tasks on the TaskService. Information here is unique
- * to the service.
- * Functionality provided by this class:
- * - Manages wakelock for the service.
- * - Sends onStartTask() and onStopTask() messages to client app, and handles callbacks.
- * -
+ * Handles client binding and lifecycle of a task. A task will only execute one at a time on an
+ * instance of this class.
*/
public class TaskServiceContext extends ITaskCallback.Stub implements ServiceConnection {
+ private static final boolean DEBUG = true;
private static final String TAG = "TaskServiceContext";
/** Define the maximum # of tasks allowed to run on a service at once. */
private static final int defaultMaxActiveTasksPerService =
@@ -66,10 +65,10 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon
};
// States that a task occupies while interacting with the client.
- private static final int VERB_STARTING = 0;
- private static final int VERB_EXECUTING = 1;
- private static final int VERB_STOPPING = 2;
- private static final int VERB_PENDING = 3;
+ static final int VERB_BINDING = 0;
+ static final int VERB_STARTING = 1;
+ static final int VERB_EXECUTING = 2;
+ static final int VERB_STOPPING = 3;
// Messages that result from interactions with the client service.
/** System timed out waiting for a response. */
@@ -77,178 +76,173 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon
/** Received a callback from client. */
private static final int MSG_CALLBACK = 1;
/** Run through list and start any ready tasks.*/
- private static final int MSG_CHECK_PENDING = 2;
- /** Cancel an active task. */
+ private static final int MSG_SERVICE_BOUND = 2;
+ /** Cancel a task. */
private static final int MSG_CANCEL = 3;
- /** Add a pending task. */
- private static final int MSG_ADD_PENDING = 4;
- /** Client crashed, so we need to wind things down. */
- private static final int MSG_SHUTDOWN = 5;
-
- /** Used to identify this task service context when communicating with the TaskManager. */
- final int token;
- final ComponentName component;
- final int userId;
- ITaskService service;
+ /** Shutdown the Task. Used when the client crashes and we can't die gracefully.*/
+ private static final int MSG_SHUTDOWN_EXECUTION = 4;
+
private final Handler mCallbackHandler;
- /** Tasks that haven't been sent to the client for execution yet. */
- private final SparseArray<ActiveTask> mPending;
+ /** Make callbacks to {@link TaskManagerService} to inform on task completion status. */
+ private final TaskCompletedListener mCompletedListener;
/** Used for service binding, etc. */
private final Context mContext;
- /** Make callbacks to {@link TaskManagerService} to inform on task completion status. */
- final private TaskCompletedListener mCompletedListener;
- private final PowerManager.WakeLock mWakeLock;
+ private PowerManager.WakeLock mWakeLock;
- /** Whether this service is actively bound. */
- boolean mBound;
+ // Execution state.
+ private TaskParams mParams;
+ @VisibleForTesting
+ int mVerb;
+ private AtomicBoolean mCancelled = new AtomicBoolean();
- TaskServiceContext(TaskManagerService taskManager, Looper looper, TaskStatus taskStatus) {
- mContext = taskManager.getContext();
- this.component = taskStatus.getServiceComponent();
- this.token = taskStatus.getServiceToken();
- this.userId = taskStatus.getUserId();
- mCallbackHandler = new TaskServiceHandler(looper);
- mPending = new SparseArray<ActiveTask>();
- mCompletedListener = taskManager;
- final PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
- mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
- TM_WAKELOCK_PREFIX + component.getPackageName());
- mWakeLock.setWorkSource(new WorkSource(taskStatus.getUid()));
- mWakeLock.setReferenceCounted(false);
- }
+ /** All the information maintained about the task currently being executed. */
+ private TaskStatus mRunningTask;
+ /** Binder to the client service. */
+ ITaskService service;
- @Override
- public void taskFinished(int taskId, boolean reschedule) {
- mCallbackHandler.obtainMessage(MSG_CALLBACK, taskId, reschedule ? 1 : 0)
- .sendToTarget();
- }
+ private final Object mAvailableLock = new Object();
+ /** Whether this context is free. */
+ @GuardedBy("mAvailableLock")
+ private boolean mAvailable;
- @Override
- public void acknowledgeStopMessage(int taskId, boolean reschedule) {
- mCallbackHandler.obtainMessage(MSG_CALLBACK, taskId, reschedule ? 1 : 0)
- .sendToTarget();
+ TaskServiceContext(TaskManagerService service, Looper looper) {
+ this(service.getContext(), service, looper);
}
- @Override
- public void acknowledgeStartMessage(int taskId, boolean ongoing) {
- mCallbackHandler.obtainMessage(MSG_CALLBACK, taskId, ongoing ? 1 : 0).sendToTarget();
+ @VisibleForTesting
+ TaskServiceContext(Context context, TaskCompletedListener completedListener, Looper looper) {
+ mContext = context;
+ mCallbackHandler = new TaskServiceHandler(looper);
+ mCompletedListener = completedListener;
}
/**
- * Queue up this task to run on the client. This will execute the task as quickly as possible.
- * @param ts Status of the task to run.
+ * Give a task to this context for execution. Callers must first check {@link #isAvailable()}
+ * to make sure this is a valid context.
+ * @param ts The status of the task that we are going to run.
+ * @return True if the task was accepted and is going to run.
*/
- public void addPendingTask(TaskStatus ts) {
- final TaskParams params = new TaskParams(ts.getTaskId(), ts.getExtras(), this);
- final ActiveTask newTask = new ActiveTask(params, VERB_PENDING);
- mCallbackHandler.obtainMessage(MSG_ADD_PENDING, newTask).sendToTarget();
- if (!mBound) {
- Intent intent = new Intent().setComponent(component);
- boolean binding = mContext.bindServiceAsUser(intent, this,
- Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND,
- new UserHandle(userId));
- if (!binding) {
- Log.e(TAG, component.getShortClassName() + " unavailable.");
- cancelPendingTask(ts);
+ boolean executeRunnableTask(TaskStatus ts) {
+ synchronized (mAvailableLock) {
+ if (!mAvailable) {
+ Slog.e(TAG, "Starting new runnable but context is unavailable > Error.");
+ return false;
+ }
+ mAvailable = false;
+ }
+
+ final PowerManager pm =
+ (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+ TM_WAKELOCK_PREFIX + ts.getServiceComponent().getPackageName());
+ mWakeLock.setWorkSource(new WorkSource(ts.getUid()));
+ mWakeLock.setReferenceCounted(false);
+
+ mRunningTask = ts;
+ mParams = new TaskParams(ts.getTaskId(), ts.getExtras(), this);
+
+ mVerb = VERB_BINDING;
+ final Intent intent = new Intent().setComponent(ts.getServiceComponent());
+ boolean binding = mContext.bindServiceAsUser(intent, this,
+ Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND,
+ new UserHandle(ts.getUserId()));
+ if (!binding) {
+ if (DEBUG) {
+ Slog.d(TAG, ts.getServiceComponent().getShortClassName() + " unavailable.");
}
+ return false;
}
+
+ return true;
}
- /**
- * Called externally when a task that was scheduled for execution should be cancelled.
- * @param ts The status of the task to cancel.
- */
- public void cancelPendingTask(TaskStatus ts) {
- mCallbackHandler.obtainMessage(MSG_CANCEL, ts.getTaskId(), -1 /* arg2 */)
- .sendToTarget();
+ /** Used externally to query the running task. Will return null if there is no task running. */
+ TaskStatus getRunningTask() {
+ return mRunningTask;
+ }
+
+ /** Called externally when a task that was scheduled for execution should be cancelled. */
+ void cancelExecutingTask() {
+ mCallbackHandler.obtainMessage(MSG_CANCEL).sendToTarget();
}
/**
- * MSG_TIMEOUT is sent with the {@link com.android.server.task.TaskServiceContext.ActiveTask}
- * set in the {@link Message#obj} field. This makes it easier to remove timeouts for a given
- * ActiveTask.
- * @param op Operation that is taking place.
+ * @return Whether this context is available to handle incoming work.
*/
- private void scheduleOpTimeOut(ActiveTask op) {
- mCallbackHandler.removeMessages(MSG_TIMEOUT, op);
-
- final long timeoutMillis = (op.verb == VERB_EXECUTING) ?
- EXECUTING_TIMESLICE_MILLIS : OP_TIMEOUT_MILLIS;
- if (Log.isLoggable(TaskManagerService.TAG, Log.DEBUG)) {
- Slog.d(TAG, "Scheduling time out for '" + component.getShortClassName() + "' tId: " +
- op.params.getTaskId() + ", in " + (timeoutMillis / 1000) + " s");
+ boolean isAvailable() {
+ synchronized (mAvailableLock) {
+ return mAvailable;
}
- Message m = mCallbackHandler.obtainMessage(MSG_TIMEOUT, op);
- mCallbackHandler.sendMessageDelayed(m, timeoutMillis);
}
- /**
- * @return true if this task is pending or active within this context.
- */
- public boolean hasTaskPending(TaskStatus taskStatus) {
- synchronized (mPending) {
- return mPending.get(taskStatus.getTaskId()) != null;
+ @Override
+ public void taskFinished(int taskId, boolean reschedule) {
+ if (!verifyCallingUid()) {
+ return;
+ }
+ mCallbackHandler.obtainMessage(MSG_CALLBACK, taskId, reschedule ? 1 : 0)
+ .sendToTarget();
+ }
+
+ @Override
+ public void acknowledgeStopMessage(int taskId, boolean reschedule) {
+ if (!verifyCallingUid()) {
+ return;
}
+ mCallbackHandler.obtainMessage(MSG_CALLBACK, taskId, reschedule ? 1 : 0)
+ .sendToTarget();
}
- public boolean isBound() {
- return mBound;
+ @Override
+ public void acknowledgeStartMessage(int taskId, boolean ongoing) {
+ if (!verifyCallingUid()) {
+ return;
+ }
+ mCallbackHandler.obtainMessage(MSG_CALLBACK, taskId, ongoing ? 1 : 0).sendToTarget();
}
/**
- * We acquire/release the wakelock on onServiceConnected/unbindService. This mirrors the work
+ * We acquire/release a wakelock on onServiceConnected/unbindService. This mirrors the work
* we intend to send to the client - we stop sending work when the service is unbound so until
* then we keep the wakelock.
- * @param name The concrete component name of the service that has
- * been connected.
+ * @param name The concrete component name of the service that has been connected.
* @param service The IBinder of the Service's communication channel,
*/
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
- mBound = true;
+ if (!name.equals(mRunningTask.getServiceComponent())) {
+ mCallbackHandler.obtainMessage(MSG_SHUTDOWN_EXECUTION).sendToTarget();
+ return;
+ }
this.service = ITaskService.Stub.asInterface(service);
- // Remove all timeouts. We've just connected to the client so there are no other
- // MSG_TIMEOUTs at this point.
+ // Remove all timeouts.
mCallbackHandler.removeMessages(MSG_TIMEOUT);
mWakeLock.acquire();
- mCallbackHandler.obtainMessage(MSG_CHECK_PENDING).sendToTarget();
+ mCallbackHandler.obtainMessage(MSG_SERVICE_BOUND).sendToTarget();
}
/**
- * When the client service crashes we can have a couple tasks executing, in various stages of
- * undress. We'll cancel all of them and request that they be rescheduled.
+ * If the client service crashes we reschedule this task and clean up.
* @param name The concrete component name of the service whose
*/
@Override
public void onServiceDisconnected(ComponentName name) {
- // Service disconnected... probably client crashed.
- startShutdown();
+ mCallbackHandler.obtainMessage(MSG_SHUTDOWN_EXECUTION).sendToTarget();
}
/**
- * We don't just shutdown outright - we make sure the scheduler isn't going to send us any more
- * tasks, then we do the shutdown.
+ * This class is reused across different clients, and passes itself in as a callback. Check
+ * whether the client exercising the callback is the client we expect.
+ * @return True if the binder calling is coming from the client we expect.
*/
- private void startShutdown() {
- mCompletedListener.onAllTasksCompleted(token);
- mCallbackHandler.obtainMessage(MSG_SHUTDOWN).sendToTarget();
- }
-
- /** Tracks a task across its various state changes. */
- private static class ActiveTask {
- final TaskParams params;
- int verb;
- AtomicBoolean cancelled = new AtomicBoolean();
-
- ActiveTask(TaskParams params, int verb) {
- this.params = params;
- this.verb = verb;
- }
-
- @Override
- public String toString() {
- return params.getTaskId() + " " + VERB_STRINGS[verb];
+ private boolean verifyCallingUid() {
+ if (mRunningTask == null || Binder.getCallingUid() != mRunningTask.getUid()) {
+ if (DEBUG) {
+ Slog.d(TAG, "Stale callback received, ignoring.");
+ }
+ return false;
}
+ return true;
}
/**
@@ -264,52 +258,67 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon
@Override
public void handleMessage(Message message) {
switch (message.what) {
- case MSG_ADD_PENDING:
- if (message.obj != null) {
- ActiveTask pendingTask = (ActiveTask) message.obj;
- mPending.put(pendingTask.params.getTaskId(), pendingTask);
- }
- // fall through.
- case MSG_CHECK_PENDING:
- checkPendingTasksH();
+ case MSG_SERVICE_BOUND:
+ handleServiceBoundH();
break;
case MSG_CALLBACK:
- ActiveTask receivedCallback = mPending.get(message.arg1);
- removeMessages(MSG_TIMEOUT, receivedCallback);
-
- if (Log.isLoggable(TaskManagerService.TAG, Log.DEBUG)) {
- Log.d(TAG, "MSG_CALLBACK of : " + receivedCallback);
+ if (DEBUG) {
+ Slog.d(TAG, "MSG_CALLBACK of : " + mRunningTask);
}
+ removeMessages(MSG_TIMEOUT);
- if (receivedCallback.verb == VERB_STARTING) {
+ if (mVerb == VERB_STARTING) {
final boolean workOngoing = message.arg2 == 1;
- handleStartedH(receivedCallback, workOngoing);
- } else if (receivedCallback.verb == VERB_EXECUTING ||
- receivedCallback.verb == VERB_STOPPING) {
+ handleStartedH(workOngoing);
+ } else if (mVerb == VERB_EXECUTING ||
+ mVerb == VERB_STOPPING) {
final boolean reschedule = message.arg2 == 1;
- handleFinishedH(receivedCallback, reschedule);
+ handleFinishedH(reschedule);
} else {
- if (Log.isLoggable(TaskManagerService.TAG, Log.DEBUG)) {
- Log.d(TAG, "Unrecognised callback: " + receivedCallback);
+ if (DEBUG) {
+ Slog.d(TAG, "Unrecognised callback: " + mRunningTask);
}
}
break;
case MSG_CANCEL:
- ActiveTask cancelled = mPending.get(message.arg1);
- handleCancelH(cancelled);
+ handleCancelH();
break;
case MSG_TIMEOUT:
- // Timeout msgs have the ActiveTask ref so we can remove them easily.
- handleOpTimeoutH((ActiveTask) message.obj);
- break;
- case MSG_SHUTDOWN:
- handleShutdownH();
+ handleOpTimeoutH();
break;
+ case MSG_SHUTDOWN_EXECUTION:
+ closeAndCleanupTaskH(true /* needsReschedule */);
default:
Log.e(TAG, "Unrecognised message: " + message);
}
}
+ /** Start the task on the service. */
+ private void handleServiceBoundH() {
+ if (mVerb != VERB_BINDING) {
+ Slog.e(TAG, "Sending onStartTask for a task that isn't pending. "
+ + VERB_STRINGS[mVerb]);
+ closeAndCleanupTaskH(false /* reschedule */);
+ return;
+ }
+ if (mCancelled.get()) {
+ if (DEBUG) {
+ Slog.d(TAG, "Task cancelled while waiting for bind to complete. "
+ + mRunningTask);
+ }
+ closeAndCleanupTaskH(true /* reschedule */);
+ return;
+ }
+ try {
+ mVerb = VERB_STARTING;
+ scheduleOpTimeOut();
+ service.startTask(mParams);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error sending onStart message to '" +
+ mRunningTask.getServiceComponent().getShortClassName() + "' ", e);
+ }
+ }
+
/**
* State behaviours.
* VERB_STARTING -> Successful start, change task to VERB_EXECUTING and post timeout.
@@ -317,24 +326,25 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon
* _EXECUTING -> Error
* _STOPPING -> Error
*/
- private void handleStartedH(ActiveTask started, boolean workOngoing) {
- switch (started.verb) {
+ private void handleStartedH(boolean workOngoing) {
+ switch (mVerb) {
case VERB_STARTING:
- started.verb = VERB_EXECUTING;
+ mVerb = VERB_EXECUTING;
if (!workOngoing) {
// Task is finished already so fast-forward to handleFinished.
- handleFinishedH(started, false);
+ handleFinishedH(false);
return;
- } else if (started.cancelled.get()) {
+ }
+ if (mCancelled.get()) {
// Cancelled *while* waiting for acknowledgeStartMessage from client.
- handleCancelH(started);
+ handleCancelH();
return;
- } else {
- scheduleOpTimeOut(started);
}
+ scheduleOpTimeOut();
break;
default:
- Log.e(TAG, "Handling started task but task wasn't starting! " + started);
+ Log.e(TAG, "Handling started task but task wasn't starting! Was "
+ + VERB_STRINGS[mVerb] + ".");
return;
}
}
@@ -345,155 +355,104 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon
* _STARTING -> Error
* _PENDING -> Error
*/
- private void handleFinishedH(ActiveTask executedTask, boolean reschedule) {
- switch (executedTask.verb) {
+ private void handleFinishedH(boolean reschedule) {
+ switch (mVerb) {
case VERB_EXECUTING:
case VERB_STOPPING:
- closeAndCleanupTaskH(executedTask, reschedule);
+ closeAndCleanupTaskH(reschedule);
break;
default:
- Log.e(TAG, "Got an execution complete message for a task that wasn't being" +
- "executed. " + executedTask);
+ Slog.e(TAG, "Got an execution complete message for a task that wasn't being" +
+ "executed. Was " + VERB_STRINGS[mVerb] + ".");
}
}
/**
* A task can be in various states when a cancel request comes in:
- * VERB_PENDING -> Remove from queue.
- * _STARTING -> Mark as cancelled and wait for {@link #acknowledgeStartMessage(int)}.
+ * VERB_BINDING -> Cancelled before bind completed. Mark as cancelled and wait for
+ * {@link #onServiceConnected(android.content.ComponentName, android.os.IBinder)}
+ * _STARTING -> Mark as cancelled and wait for
+ * {@link TaskServiceContext#acknowledgeStartMessage(int, boolean)}
* _EXECUTING -> call {@link #sendStopMessageH}}.
* _ENDING -> No point in doing anything here, so we ignore.
*/
- private void handleCancelH(ActiveTask cancelledTask) {
- switch (cancelledTask.verb) {
- case VERB_PENDING:
- mPending.remove(cancelledTask.params.getTaskId());
- break;
+ private void handleCancelH() {
+ switch (mVerb) {
+ case VERB_BINDING:
case VERB_STARTING:
- cancelledTask.cancelled.set(true);
+ mCancelled.set(true);
break;
case VERB_EXECUTING:
- cancelledTask.verb = VERB_STOPPING;
- sendStopMessageH(cancelledTask);
+ sendStopMessageH();
break;
case VERB_STOPPING:
// Nada.
break;
default:
- Log.e(TAG, "Cancelling a task without a valid verb: " + cancelledTask);
+ Slog.e(TAG, "Cancelling a task without a valid verb: " + mVerb);
break;
}
}
- /**
- * This TaskServiceContext is shutting down. Remove all the tasks from the pending queue
- * and reschedule them as if they had failed.
- * Before posting this message, caller must invoke
- * {@link com.android.server.task.TaskCompletedListener#onAllTasksCompleted(int)}.
- */
- private void handleShutdownH() {
- for (int i = 0; i < mPending.size(); i++) {
- ActiveTask at = mPending.valueAt(i);
- closeAndCleanupTaskH(at, true /* needsReschedule */);
- }
- mWakeLock.release();
- mContext.unbindService(TaskServiceContext.this);
- service = null;
- mBound = false;
- }
-
- /**
- * MSG_TIMEOUT gets processed here.
- * @param timedOutTask The task that timed out.
- */
- private void handleOpTimeoutH(ActiveTask timedOutTask) {
+ /** Process MSG_TIMEOUT here. */
+ private void handleOpTimeoutH() {
if (Log.isLoggable(TaskManagerService.TAG, Log.DEBUG)) {
- Log.d(TAG, "MSG_TIMEOUT of " + component.getShortClassName() + " : "
- + timedOutTask.params.getTaskId());
+ Log.d(TAG, "MSG_TIMEOUT of " +
+ mRunningTask.getServiceComponent().getShortClassName() + " : "
+ + mParams.getTaskId());
}
- final int taskId = timedOutTask.params.getTaskId();
- switch (timedOutTask.verb) {
+ final int taskId = mParams.getTaskId();
+ switch (mVerb) {
case VERB_STARTING:
// Client unresponsive - wedged or failed to respond in time. We don't really
// know what happened so let's log it and notify the TaskManager
// FINISHED/NO-RETRY.
Log.e(TAG, "No response from client for onStartTask '" +
- component.getShortClassName() + "' tId: " + taskId);
- closeAndCleanupTaskH(timedOutTask, false /* needsReschedule */);
+ mRunningTask.getServiceComponent().getShortClassName() + "' tId: "
+ + taskId);
+ closeAndCleanupTaskH(false /* needsReschedule */);
break;
case VERB_STOPPING:
// At least we got somewhere, so fail but ask the TaskManager to reschedule.
Log.e(TAG, "No response from client for onStopTask, '" +
- component.getShortClassName() + "' tId: " + taskId);
- closeAndCleanupTaskH(timedOutTask, true /* needsReschedule */);
+ mRunningTask.getServiceComponent().getShortClassName() + "' tId: "
+ + taskId);
+ closeAndCleanupTaskH(true /* needsReschedule */);
break;
case VERB_EXECUTING:
// Not an error - client ran out of time.
Log.i(TAG, "Client timed out while executing (no taskFinished received)." +
" Reporting failure and asking for reschedule. " +
- component.getShortClassName() + "' tId: " + taskId);
- sendStopMessageH(timedOutTask);
+ mRunningTask.getServiceComponent().getShortClassName() + "' tId: "
+ + taskId);
+ sendStopMessageH();
break;
default:
Log.e(TAG, "Handling timeout for an unknown active task state: "
- + timedOutTask);
+ + mRunningTask);
return;
}
}
/**
- * Called on the handler thread. Checks the state of the pending queue and starts the task
- * if it can. The task only starts if there is capacity on the service.
- */
- private void checkPendingTasksH() {
- if (!mBound) {
- return;
- }
- for (int i = 0; i < mPending.size() && i < defaultMaxActiveTasksPerService; i++) {
- ActiveTask at = mPending.valueAt(i);
- if (at.verb != VERB_PENDING) {
- continue;
- }
- sendStartMessageH(at);
- }
- }
-
- /**
- * Already running, need to stop. Rund on handler.
- * @param stoppingTask Task we are sending onStopMessage for. This task will be moved from
- * VERB_EXECUTING -> VERB_STOPPING.
+ * Already running, need to stop. Will switch {@link #mVerb} from VERB_EXECUTING ->
+ * VERB_STOPPING.
*/
- private void sendStopMessageH(ActiveTask stoppingTask) {
- mCallbackHandler.removeMessages(MSG_TIMEOUT, stoppingTask);
- if (stoppingTask.verb != VERB_EXECUTING) {
- Log.e(TAG, "Sending onStopTask for a task that isn't started. " + stoppingTask);
- // TODO: Handle error?
+ private void sendStopMessageH() {
+ mCallbackHandler.removeMessages(MSG_TIMEOUT);
+ if (mVerb != VERB_EXECUTING) {
+ Log.e(TAG, "Sending onStopTask for a task that isn't started. " + mRunningTask);
+ closeAndCleanupTaskH(false /* reschedule */);
return;
}
try {
- service.stopTask(stoppingTask.params);
- stoppingTask.verb = VERB_STOPPING;
- scheduleOpTimeOut(stoppingTask);
+ mVerb = VERB_STOPPING;
+ scheduleOpTimeOut();
+ service.stopTask(mParams);
} catch (RemoteException e) {
Log.e(TAG, "Error sending onStopTask to client.", e);
- closeAndCleanupTaskH(stoppingTask, false);
- }
- }
-
- /** Start the task on the service. */
- private void sendStartMessageH(ActiveTask pendingTask) {
- if (pendingTask.verb != VERB_PENDING) {
- Log.e(TAG, "Sending onStartTask for a task that isn't pending. " + pendingTask);
- // TODO: Handle error?
- }
- try {
- service.startTask(pendingTask.params);
- pendingTask.verb = VERB_STARTING;
- scheduleOpTimeOut(pendingTask);
- } catch (RemoteException e) {
- Log.e(TAG, "Error sending onStart message to '" + component.getShortClassName()
- + "' ", e);
+ closeAndCleanupTaskH(false);
}
}
@@ -503,13 +462,42 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon
* or from acknowledging the stop message we sent. Either way, we're done tracking it and
* we want to clean up internally.
*/
- private void closeAndCleanupTaskH(ActiveTask completedTask, boolean reschedule) {
- removeMessages(MSG_TIMEOUT, completedTask);
- mPending.remove(completedTask.params.getTaskId());
- if (mPending.size() == 0) {
- startShutdown();
+ private void closeAndCleanupTaskH(boolean reschedule) {
+ removeMessages(MSG_TIMEOUT);
+ mWakeLock.release();
+ mContext.unbindService(TaskServiceContext.this);
+ mWakeLock = null;
+
+ mRunningTask = null;
+ mParams = null;
+ mVerb = -1;
+ mCancelled.set(false);
+
+ service = null;
+
+ mCompletedListener.onTaskCompleted(mRunningTask, reschedule);
+ synchronized (mAvailableLock) {
+ mAvailable = true;
+ }
+ }
+
+ /**
+ * Called when sending a message to the client, over whose execution we have no control. If we
+ * haven't received a response in a certain amount of time, we want to give up and carry on
+ * with life.
+ */
+ private void scheduleOpTimeOut() {
+ mCallbackHandler.removeMessages(MSG_TIMEOUT);
+
+ final long timeoutMillis = (mVerb == VERB_EXECUTING) ?
+ EXECUTING_TIMESLICE_MILLIS : OP_TIMEOUT_MILLIS;
+ if (DEBUG) {
+ Slog.d(TAG, "Scheduling time out for '" +
+ mRunningTask.getServiceComponent().getShortClassName() + "' tId: " +
+ mParams.getTaskId() + ", in " + (timeoutMillis / 1000) + " s");
}
- mCompletedListener.onTaskCompleted(token, completedTask.params.getTaskId(), reschedule);
+ Message m = mCallbackHandler.obtainMessage(MSG_TIMEOUT);
+ mCallbackHandler.sendMessageDelayed(m, timeoutMillis);
}
}
}
diff --git a/services/core/java/com/android/server/task/TaskStore.java b/services/core/java/com/android/server/task/TaskStore.java
index 81187c8..f72ab22 100644
--- a/services/core/java/com/android/server/task/TaskStore.java
+++ b/services/core/java/com/android/server/task/TaskStore.java
@@ -18,10 +18,16 @@ package com.android.server.task;
import android.app.task.Task;
import android.content.Context;
+import android.util.ArraySet;
+import android.util.Slog;
import android.util.SparseArray;
import com.android.server.task.controllers.TaskStatus;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
/**
* Maintain a list of classes, and accessor methods/logic for these tasks.
* This class offers the following functionality:
@@ -35,76 +41,131 @@ import com.android.server.task.controllers.TaskStatus;
* - This class is <strong>not</strong> thread-safe.
*/
public class TaskStore {
-
- /**
- * Master list, indexed by {@link com.android.server.task.controllers.TaskStatus#hashCode()}.
- */
- final SparseArray<TaskStatus> mTasks;
+ private static final String TAG = "TaskManagerStore";
+ /** Threshold to adjust how often we want to write to the db. */
+ private static final int MAX_OPS_BEFORE_WRITE = 1;
+ final ArraySet<TaskStatus> mTasks;
final Context mContext;
+ private int mDirtyOperations;
+
TaskStore(Context context) {
- mTasks = intialiseTaskMapFromDisk();
+ mTasks = intialiseTasksFromDisk();
mContext = context;
+ mDirtyOperations = 0;
}
/**
- * Add a task to the master list, persisting it if necessary.
- * Will first check to see if the task already exists. If so, it will replace it.
- * {@link android.content.pm.PackageManager} is queried to see if the calling package has
- * permission to
- * @param task Task to add.
- * @return The initialised TaskStatus object if this operation was successful, null if it
- * failed.
+ * Add a task to the master list, persisting it if necessary. If the TaskStatus already exists,
+ * it will be replaced.
+ * @param taskStatus Task to add.
+ * @return true if the operation succeeded.
*/
- public TaskStatus addNewTaskForUser(Task task, int userId, int uId,
- boolean canPersistTask) {
- TaskStatus taskStatus = TaskStatus.getForTaskAndUser(task, userId, uId);
- if (canPersistTask && task.isPeriodic()) {
- if (writeStatusToDisk()) {
- mTasks.put(taskStatus.hashCode(), taskStatus);
+ public boolean add(TaskStatus taskStatus) {
+ if (taskStatus.isPersisted()) {
+ if (!maybeWriteStatusToDisk()) {
+ return false;
}
}
- return taskStatus;
+ mTasks.remove(taskStatus);
+ mTasks.add(taskStatus);
+ return true;
}
- /**
- * Remove the provided task. Will also delete the task if it was persisted. Note that this
- * function does not return the validity of the operation, as we assume a delete will always
- * succeed.
- * @param task Task to remove.
- */
- public void remove(Task task) {
-
+ public int size() {
+ return mTasks.size();
}
/**
- * Every time the state changes we write all the tasks in one swathe, instead of trying to
- * track incremental changes.
+ * Remove the provided task. Will also delete the task if it was persisted.
+ * @return The TaskStatus that was removed, or null if an invalid token was provided.
*/
- private boolean writeStatusToDisk() {
+ public boolean remove(TaskStatus taskStatus) {
+ boolean removed = mTasks.remove(taskStatus);
+ if (!removed) {
+ Slog.e(TAG, "Error removing task: " + taskStatus);
+ return false;
+ } else {
+ maybeWriteStatusToDisk();
+ }
return true;
}
/**
- *
- * @return
+ * Removes all TaskStatus objects for a given uid from the master list. Note that it is
+ * possible to remove a task that is pending/active. This operation will succeed, and the
+ * removal will take effect when the task has completed executing.
+ * @param uid Uid of the requesting app.
+ * @return True if at least one task was removed, false if nothing matching the provided uId
+ * was found.
*/
- // TODO: Implement this.
- private SparseArray<TaskStatus> intialiseTaskMapFromDisk() {
- return new SparseArray<TaskStatus>();
+ public boolean removeAllByUid(int uid) {
+ Iterator<TaskStatus> it = mTasks.iterator();
+ boolean removed = false;
+ while (it.hasNext()) {
+ TaskStatus ts = it.next();
+ if (ts.getUid() == uid) {
+ it.remove();
+ removed = true;
+ }
+ }
+ if (removed) {
+ maybeWriteStatusToDisk();
+ }
+ return removed;
}
/**
- * @return the number of tasks in the store
+ * Remove the TaskStatus that matches the provided uId and taskId. Note that it is possible
+ * to remove a task that is pending/active. This operation will succeed, and the removal will
+ * take effect when the task has completed executing.
+ * @param uid Uid of the requesting app.
+ * @param taskId Task id, specified at schedule-time.
+ * @return true if a removal occurred, false if the provided parameters didn't match anything.
*/
- public int size() {
- return mTasks.size();
+ public boolean remove(int uid, int taskId) {
+ Iterator<TaskStatus> it = mTasks.iterator();
+ while (it.hasNext()) {
+ TaskStatus ts = it.next();
+ if (ts.getUid() == uid && ts.getTaskId() == taskId) {
+ it.remove();
+ maybeWriteStatusToDisk();
+ return true;
+ }
+ }
+ return false;
}
/**
* @return The live array of TaskStatus objects.
*/
- public SparseArray<TaskStatus> getTasks() {
+ public Set<TaskStatus> getTasks() {
return mTasks;
}
+
+ /**
+ * Every time the state changes we write all the tasks in one swathe, instead of trying to
+ * track incremental changes.
+ * @return Whether the operation was successful. This will only fail for e.g. if the system is
+ * low on storage. If this happens, we continue as normal
+ */
+ private boolean maybeWriteStatusToDisk() {
+ mDirtyOperations++;
+ if (mDirtyOperations > MAX_OPS_BEFORE_WRITE) {
+ for (TaskStatus ts : mTasks) {
+ //
+ }
+ mDirtyOperations = 0;
+ }
+ return true;
+ }
+
+ /**
+ *
+ * @return
+ */
+ // TODO: Implement this.
+ private ArraySet<TaskStatus> intialiseTasksFromDisk() {
+ return new ArraySet<TaskStatus>();
+ }
}
diff --git a/services/core/java/com/android/server/task/controllers/ConnectivityController.java b/services/core/java/com/android/server/task/controllers/ConnectivityController.java
index a0038c5..474af8f 100644
--- a/services/core/java/com/android/server/task/controllers/ConnectivityController.java
+++ b/services/core/java/com/android/server/task/controllers/ConnectivityController.java
@@ -25,6 +25,7 @@ import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.UserHandle;
import android.util.Log;
+import android.util.Slog;
import com.android.server.task.TaskManagerService;
@@ -32,21 +33,33 @@ import java.util.LinkedList;
import java.util.List;
/**
- *
+ * Handles changes in connectivity.
+ * We are only interested in metered vs. unmetered networks, and we're interested in them on a
+ * per-user basis.
*/
public class ConnectivityController extends StateController {
private static final String TAG = "TaskManager.Connectivity";
+ private static final boolean DEBUG = true;
private final List<TaskStatus> mTrackedTasks = new LinkedList<TaskStatus>();
private final BroadcastReceiver mConnectivityChangedReceiver =
new ConnectivityChangedReceiver();
+ /** Singleton. */
+ private static ConnectivityController mSingleton;
/** Track whether the latest active network is metered. */
private boolean mMetered;
/** Track whether the latest active network is connected. */
private boolean mConnectivity;
- public ConnectivityController(TaskManagerService service) {
+ public static synchronized ConnectivityController get(TaskManagerService taskManager) {
+ if (mSingleton == null) {
+ mSingleton = new ConnectivityController(taskManager);
+ }
+ return mSingleton;
+ }
+
+ private ConnectivityController(TaskManagerService service) {
super(service);
// Register connectivity changed BR.
IntentFilter intentFilter = new IntentFilter();
@@ -56,7 +69,7 @@ public class ConnectivityController extends StateController {
}
@Override
- public void maybeTrackTaskState(TaskStatus taskStatus) {
+ public synchronized void maybeStartTrackingTask(TaskStatus taskStatus) {
if (taskStatus.hasConnectivityConstraint() || taskStatus.hasMeteredConstraint()) {
taskStatus.connectivityConstraintSatisfied.set(mConnectivity);
taskStatus.meteredConstraintSatisfied.set(mMetered);
@@ -65,21 +78,28 @@ public class ConnectivityController extends StateController {
}
@Override
- public void removeTaskStateIfTracked(TaskStatus taskStatus) {
+ public synchronized void maybeStopTrackingTask(TaskStatus taskStatus) {
mTrackedTasks.remove(taskStatus);
}
/**
- *
+ * @param userId Id of the user for whom we are updating the connectivity state.
*/
- private void updateTrackedTasks() {
+ private void updateTrackedTasks(int userId) {
+ boolean changed = false;
for (TaskStatus ts : mTrackedTasks) {
+ if (ts.getUserId() != userId) {
+ continue;
+ }
boolean prevIsConnected = ts.connectivityConstraintSatisfied.getAndSet(mConnectivity);
boolean prevIsMetered = ts.meteredConstraintSatisfied.getAndSet(mMetered);
if (prevIsConnected != mConnectivity || prevIsMetered != mMetered) {
- mStateChangedListener.onTaskStateChanged(ts);
+ changed = true;
}
}
+ if (changed) {
+ mStateChangedListener.onControllerStateChanged();
+ }
}
class ConnectivityChangedReceiver extends BroadcastReceiver {
@@ -103,17 +123,20 @@ public class ConnectivityController extends StateController {
context.getSystemService(Context.CONNECTIVITY_SERVICE);
final NetworkInfo activeNetwork = connManager.getActiveNetworkInfo();
// This broadcast gets sent a lot, only update if the active network has changed.
- if (activeNetwork.getType() == networkType) {
+ if (activeNetwork != null && activeNetwork.getType() == networkType) {
+ final int userid = context.getUserId();
mMetered = false;
mConnectivity =
!intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
if (mConnectivity) { // No point making the call if we know there's no conn.
mMetered = connManager.isActiveNetworkMetered();
}
- updateTrackedTasks();
+ updateTrackedTasks(userid);
}
} else {
- Log.w(TAG, "Unrecognised action in intent: " + action);
+ if (DEBUG) {
+ Slog.d(TAG, "Unrecognised action in intent: " + action);
+ }
}
}
};
diff --git a/services/core/java/com/android/server/task/controllers/IdleController.java b/services/core/java/com/android/server/task/controllers/IdleController.java
index a319a31..9489644 100644
--- a/services/core/java/com/android/server/task/controllers/IdleController.java
+++ b/services/core/java/com/android/server/task/controllers/IdleController.java
@@ -49,7 +49,7 @@ public class IdleController extends StateController {
private static Object sCreationLock = new Object();
private static volatile IdleController sController;
- public IdleController getController(TaskManagerService service) {
+ public static IdleController get(TaskManagerService service) {
synchronized (sCreationLock) {
if (sController == null) {
sController = new IdleController(service);
@@ -67,7 +67,7 @@ public class IdleController extends StateController {
* StateController interface
*/
@Override
- public void maybeTrackTaskState(TaskStatus taskStatus) {
+ public void maybeStartTrackingTask(TaskStatus taskStatus) {
if (taskStatus.hasIdleConstraint()) {
synchronized (mTrackedTasks) {
mTrackedTasks.add(taskStatus);
@@ -77,7 +77,7 @@ public class IdleController extends StateController {
}
@Override
- public void removeTaskStateIfTracked(TaskStatus taskStatus) {
+ public void maybeStopTrackingTask(TaskStatus taskStatus) {
synchronized (mTrackedTasks) {
mTrackedTasks.remove(taskStatus);
}
@@ -90,9 +90,9 @@ public class IdleController extends StateController {
synchronized (mTrackedTasks) {
for (TaskStatus task : mTrackedTasks) {
task.idleConstraintSatisfied.set(isIdle);
- mStateChangedListener.onTaskStateChanged(task);
}
}
+ mStateChangedListener.onControllerStateChanged();
}
/**
diff --git a/services/core/java/com/android/server/task/controllers/StateController.java b/services/core/java/com/android/server/task/controllers/StateController.java
index e1cd662..ed31eac 100644
--- a/services/core/java/com/android/server/task/controllers/StateController.java
+++ b/services/core/java/com/android/server/task/controllers/StateController.java
@@ -42,10 +42,10 @@ public abstract class StateController {
* Also called when updating a task, so implementing controllers have to be aware of
* preexisting tasks.
*/
- public abstract void maybeTrackTaskState(TaskStatus taskStatus);
+ public abstract void maybeStartTrackingTask(TaskStatus taskStatus);
/**
* Remove task - this will happen if the task is cancelled, completed, etc.
*/
- public abstract void removeTaskStateIfTracked(TaskStatus taskStatus);
+ public abstract void maybeStopTrackingTask(TaskStatus taskStatus);
}
diff --git a/services/core/java/com/android/server/task/controllers/TaskStatus.java b/services/core/java/com/android/server/task/controllers/TaskStatus.java
index d270016..b7f84ec 100644
--- a/services/core/java/com/android/server/task/controllers/TaskStatus.java
+++ b/services/core/java/com/android/server/task/controllers/TaskStatus.java
@@ -18,7 +18,6 @@ package com.android.server.task.controllers;
import android.app.task.Task;
import android.content.ComponentName;
-import android.content.pm.PackageParser;
import android.os.Bundle;
import android.os.SystemClock;
import android.os.UserHandle;
@@ -39,65 +38,67 @@ import java.util.concurrent.atomic.AtomicBoolean;
*/
public class TaskStatus {
final Task task;
- final int taskId;
final int uId;
- final Bundle extras;
+ /** At reschedule time we need to know whether to update task on disk. */
+ final boolean persisted;
+
+ // Constraints.
final AtomicBoolean chargingConstraintSatisfied = new AtomicBoolean();
- final AtomicBoolean timeConstraintSatisfied = new AtomicBoolean();
+ final AtomicBoolean timeDelayConstraintSatisfied = new AtomicBoolean();
+ final AtomicBoolean deadlineConstraintSatisfied = new AtomicBoolean();
final AtomicBoolean idleConstraintSatisfied = new AtomicBoolean();
final AtomicBoolean meteredConstraintSatisfied = new AtomicBoolean();
final AtomicBoolean connectivityConstraintSatisfied = new AtomicBoolean();
- private final boolean hasChargingConstraint;
- private final boolean hasTimingConstraint;
- private final boolean hasIdleConstraint;
- private final boolean hasMeteredConstraint;
- private final boolean hasConnectivityConstraint;
-
+ /**
+ * Earliest point in the future at which this task will be eligible to run. A value of 0
+ * indicates there is no delay constraint. See {@link #hasTimingDelayConstraint()}.
+ */
private long earliestRunTimeElapsedMillis;
+ /**
+ * Latest point in the future at which this task must be run. A value of {@link Long#MAX_VALUE}
+ * indicates there is no deadline constraint. See {@link #hasDeadlineConstraint()}.
+ */
private long latestRunTimeElapsedMillis;
+ private final int numFailures;
+
/** Provide a handle to the service that this task will be run on. */
public int getServiceToken() {
return uId;
}
- /** Generate a TaskStatus object for a given task and uid. */
- // TODO: reimplement this to reuse these objects instead of creating a new one each time?
- public static TaskStatus getForTaskAndUser(Task task, int userId, int uId) {
- return new TaskStatus(task, userId, uId);
- }
-
- /** Set up the state of a newly scheduled task. */
- TaskStatus(Task task, int userId, int uId) {
+ /** Create a newly scheduled task. */
+ public TaskStatus(Task task, int uId, boolean persisted) {
this.task = task;
- this.taskId = task.getTaskId();
- this.extras = task.getExtras();
this.uId = uId;
+ this.numFailures = 0;
+ this.persisted = persisted;
- hasChargingConstraint = task.isRequireCharging();
- hasIdleConstraint = task.isRequireDeviceIdle();
-
+ final long elapsedNow = SystemClock.elapsedRealtime();
// Timing constraints
if (task.isPeriodic()) {
- long elapsedNow = SystemClock.elapsedRealtime();
earliestRunTimeElapsedMillis = elapsedNow;
latestRunTimeElapsedMillis = elapsedNow + task.getIntervalMillis();
- hasTimingConstraint = true;
- } else if (task.getMinLatencyMillis() != 0L || task.getMaxExecutionDelayMillis() != 0L) {
- earliestRunTimeElapsedMillis = task.getMinLatencyMillis() > 0L ?
- task.getMinLatencyMillis() : Long.MAX_VALUE;
- latestRunTimeElapsedMillis = task.getMaxExecutionDelayMillis() > 0L ?
- task.getMaxExecutionDelayMillis() : Long.MAX_VALUE;
- hasTimingConstraint = true;
} else {
- hasTimingConstraint = false;
+ earliestRunTimeElapsedMillis = task.hasEarlyConstraint() ?
+ elapsedNow + task.getMinLatencyMillis() : 0L;
+ latestRunTimeElapsedMillis = task.hasLateConstraint() ?
+ elapsedNow + task.getMaxExecutionDelayMillis() : Long.MAX_VALUE;
}
+ }
+
+ public TaskStatus(TaskStatus rescheduling, long newEarliestRuntimeElapsed,
+ long newLatestRuntimeElapsed, int backoffAttempt) {
+ this.task = rescheduling.task;
- // Networking constraints
- hasMeteredConstraint = task.getNetworkCapabilities() == Task.NetworkType.UNMETERED;
- hasConnectivityConstraint = task.getNetworkCapabilities() == Task.NetworkType.ANY;
+ this.uId = rescheduling.getUid();
+ this.persisted = rescheduling.isPersisted();
+ this.numFailures = backoffAttempt;
+
+ earliestRunTimeElapsedMillis = newEarliestRuntimeElapsed;
+ latestRunTimeElapsedMillis = newLatestRuntimeElapsed;
}
public Task getTask() {
@@ -105,7 +106,11 @@ public class TaskStatus {
}
public int getTaskId() {
- return taskId;
+ return task.getId();
+ }
+
+ public int getNumFailures() {
+ return numFailures;
}
public ComponentName getServiceComponent() {
@@ -121,52 +126,60 @@ public class TaskStatus {
}
public Bundle getExtras() {
- return extras;
+ return task.getExtras();
+ }
+
+ public boolean hasConnectivityConstraint() {
+ return task.getNetworkCapabilities() == Task.NetworkType.ANY;
}
- boolean hasConnectivityConstraint() {
- return hasConnectivityConstraint;
+ public boolean hasMeteredConstraint() {
+ return task.getNetworkCapabilities() == Task.NetworkType.UNMETERED;
}
- boolean hasMeteredConstraint() {
- return hasMeteredConstraint;
+ public boolean hasChargingConstraint() {
+ return task.isRequireCharging();
}
- boolean hasChargingConstraint() {
- return hasChargingConstraint;
+ public boolean hasTimingDelayConstraint() {
+ return earliestRunTimeElapsedMillis != 0L;
}
- boolean hasTimingConstraint() {
- return hasTimingConstraint;
+ public boolean hasDeadlineConstraint() {
+ return latestRunTimeElapsedMillis != Long.MAX_VALUE;
}
- boolean hasIdleConstraint() {
- return hasIdleConstraint;
+ public boolean hasIdleConstraint() {
+ return task.isRequireDeviceIdle();
}
- long getEarliestRunTime() {
+ public long getEarliestRunTime() {
return earliestRunTimeElapsedMillis;
}
- long getLatestRunTime() {
+ public long getLatestRunTimeElapsed() {
return latestRunTimeElapsedMillis;
}
+ public boolean isPersisted() {
+ return persisted;
+ }
/**
- * @return whether this task is ready to run, based on its requirements.
+ * @return Whether or not this task is ready to run, based on its requirements.
*/
public synchronized boolean isReady() {
- return (!hasChargingConstraint || chargingConstraintSatisfied.get())
- && (!hasTimingConstraint || timeConstraintSatisfied.get())
- && (!hasConnectivityConstraint || connectivityConstraintSatisfied.get())
- && (!hasMeteredConstraint || meteredConstraintSatisfied.get())
- && (!hasIdleConstraint || idleConstraintSatisfied.get());
+ return (!hasChargingConstraint() || chargingConstraintSatisfied.get())
+ && (!hasTimingDelayConstraint() || timeDelayConstraintSatisfied.get())
+ && (!hasConnectivityConstraint() || connectivityConstraintSatisfied.get())
+ && (!hasMeteredConstraint() || meteredConstraintSatisfied.get())
+ && (!hasIdleConstraint() || idleConstraintSatisfied.get())
+ && (!hasDeadlineConstraint() || deadlineConstraintSatisfied.get());
}
@Override
public int hashCode() {
int result = getServiceComponent().hashCode();
- result = 31 * result + taskId;
+ result = 31 * result + task.getId();
result = 31 * result + uId;
return result;
}
@@ -177,14 +190,14 @@ public class TaskStatus {
if (!(o instanceof TaskStatus)) return false;
TaskStatus that = (TaskStatus) o;
- return ((taskId == that.taskId)
+ return ((task.getId() == that.task.getId())
&& (uId == that.uId)
&& (getServiceComponent().equals(that.getServiceComponent())));
}
// Dumpsys infrastructure
public void dump(PrintWriter pw, String prefix) {
- pw.print(prefix); pw.print("Task "); pw.println(taskId);
+ pw.print(prefix); pw.print("Task "); pw.println(task.getId());
pw.print(prefix); pw.print("uid="); pw.println(uId);
pw.print(prefix); pw.print("component="); pw.println(task.getService());
}
diff --git a/services/core/java/com/android/server/task/controllers/TimeController.java b/services/core/java/com/android/server/task/controllers/TimeController.java
index 6d97a53..72f312c 100644
--- a/services/core/java/com/android/server/task/controllers/TimeController.java
+++ b/services/core/java/com/android/server/task/controllers/TimeController.java
@@ -23,7 +23,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.SystemClock;
-import android.util.Log;
import com.android.server.task.TaskManagerService;
@@ -54,8 +53,17 @@ public class TimeController extends StateController {
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 TimeController(TaskManagerService service) {
+ 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 */,
@@ -75,8 +83,8 @@ public class TimeController extends StateController {
* list.
*/
@Override
- public synchronized void maybeTrackTaskState(TaskStatus task) {
- if (task.hasTimingConstraint()) {
+ public synchronized void maybeStartTrackingTask(TaskStatus task) {
+ if (task.hasTimingDelayConstraint()) {
ListIterator<TaskStatus> it = mTrackedTasks.listIterator(mTrackedTasks.size());
while (it.hasPrevious()) {
TaskStatus ts = it.previous();
@@ -85,13 +93,13 @@ public class TimeController extends StateController {
it.remove();
it.add(task);
break;
- } else if (ts.getLatestRunTime() < task.getLatestRunTime()) {
+ } else if (ts.getLatestRunTimeElapsed() < task.getLatestRunTimeElapsed()) {
// Insert
it.add(task);
break;
}
}
- maybeUpdateAlarms(task.getEarliestRunTime(), task.getLatestRunTime());
+ maybeUpdateAlarms(task.getEarliestRunTime(), task.getLatestRunTimeElapsed());
}
}
@@ -100,12 +108,12 @@ public class TimeController extends StateController {
* so, update them.
*/
@Override
- public synchronized void removeTaskStateIfTracked(TaskStatus taskStatus) {
+ public synchronized void maybeStopTrackingTask(TaskStatus taskStatus) {
if (mTrackedTasks.remove(taskStatus)) {
if (mNextDelayExpiredElapsedMillis <= taskStatus.getEarliestRunTime()) {
handleTaskDelayExpired();
}
- if (mNextTaskExpiredElapsedMillis <= taskStatus.getLatestRunTime()) {
+ if (mNextTaskExpiredElapsedMillis <= taskStatus.getLatestRunTimeElapsed()) {
handleTaskDeadlineExpired();
}
}
@@ -140,10 +148,10 @@ public class TimeController extends StateController {
* back and forth.
*/
private boolean canStopTrackingTask(TaskStatus taskStatus) {
- final long elapsedNowMillis = SystemClock.elapsedRealtime();
- return taskStatus.timeConstraintSatisfied.get() &&
- (taskStatus.getLatestRunTime() == Long.MAX_VALUE ||
- taskStatus.getLatestRunTime() < elapsedNowMillis);
+ return (!taskStatus.hasTimingDelayConstraint() ||
+ taskStatus.timeDelayConstraintSatisfied.get()) &&
+ (!taskStatus.hasDeadlineConstraint() ||
+ taskStatus.deadlineConstraintSatisfied.get());
}
private void maybeUpdateAlarms(long delayExpiredElapsed, long deadlineExpiredElapsed) {
@@ -174,10 +182,10 @@ public class TimeController extends StateController {
Iterator<TaskStatus> it = mTrackedTasks.iterator();
while (it.hasNext()) {
TaskStatus ts = it.next();
- final long taskDeadline = ts.getLatestRunTime();
+ final long taskDeadline = ts.getLatestRunTimeElapsed();
if (taskDeadline <= nowElapsedMillis) {
- ts.timeConstraintSatisfied.set(true);
+ ts.deadlineConstraintSatisfied.set(true);
mStateChangedListener.onTaskDeadlineExpired(ts);
it.remove();
} else { // Sorted by expiry time, so take the next one and stop.
@@ -199,10 +207,12 @@ public class TimeController extends StateController {
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.timeConstraintSatisfied.set(true);
- mStateChangedListener.onTaskStateChanged(ts);
+ ts.timeDelayConstraintSatisfied.set(true);
if (canStopTrackingTask(ts)) {
it.remove();
}
@@ -212,6 +222,7 @@ public class TimeController extends StateController {
}
}
}
+ mStateChangedListener.onControllerStateChanged();
maybeUpdateAlarms(nextDelayTime, Long.MAX_VALUE);
}