am 3b471117: Merge "Add OnNetworkActive to TaskManager and simplify locking." into lmp-preview-dev
* commit '3b4711176e77640d697e94137e65fa93c8363f5c':
Add OnNetworkActive to TaskManager and simplify locking.
diff --git a/core/java/android/app/TaskManagerImpl.java b/core/java/android/app/TaskManagerImpl.java
index f42839e..fe29fb7 100644
--- a/core/java/android/app/TaskManagerImpl.java
+++ b/core/java/android/app/TaskManagerImpl.java
@@ -20,6 +20,7 @@
import android.app.task.ITaskManager;
import android.app.task.Task;
import android.app.task.TaskManager;
+import android.os.RemoteException;
import java.util.List;
@@ -37,26 +38,35 @@
@Override
public int schedule(Task task) {
- // TODO Auto-generated method stub
- return 0;
+ try {
+ return mBinder.schedule(task);
+ } catch (RemoteException e) {
+ return TaskManager.RESULT_FAILURE;
+ }
}
@Override
public void cancel(int taskId) {
- // TODO Auto-generated method stub
+ try {
+ mBinder.cancel(taskId);
+ } catch (RemoteException e) {}
}
@Override
public void cancelAll() {
- // TODO Auto-generated method stub
+ try {
+ mBinder.cancelAll();
+ } catch (RemoteException e) {}
}
@Override
public List<Task> getAllPendingTasks() {
- // TODO Auto-generated method stub
- return null;
+ try {
+ return mBinder.getAllPendingTasks();
+ } catch (RemoteException e) {
+ return null;
+ }
}
-
}
diff --git a/core/java/android/app/task/Task.java b/core/java/android/app/task/Task.java
index 87d57fb4..0e660b3 100644
--- a/core/java/android/app/task/Task.java
+++ b/core/java/android/app/task/Task.java
@@ -48,6 +48,11 @@
* @hide
*/
public static final int DEFAULT_BACKOFF_POLICY = BackoffPolicy.EXPONENTIAL;
+ /**
+ * Maximum backoff we allow for a job, in milliseconds.
+ * @hide
+ */
+ public static final long MAX_BACKOFF_DELAY_MILLIS = 24 * 60 * 60 * 1000; // 24 hours.
/**
* Linear: retry_time(failure_time, t) = failure_time + initial_retry_delay * t, t >= 1
@@ -185,7 +190,7 @@
private Task(Parcel in) {
taskId = in.readInt();
extras = in.readPersistableBundle();
- service = ComponentName.readFromParcel(in);
+ service = in.readParcelable(null);
requireCharging = in.readInt() == 1;
requireDeviceIdle = in.readInt() == 1;
networkCapabilities = in.readInt();
@@ -201,7 +206,7 @@
private Task(Task.Builder b) {
taskId = b.mTaskId;
- extras = new PersistableBundle(b.mExtras);
+ extras = b.mExtras;
service = b.mTaskService;
requireCharging = b.mRequiresCharging;
requireDeviceIdle = b.mRequiresDeviceIdle;
@@ -225,7 +230,7 @@
public void writeToParcel(Parcel out, int flags) {
out.writeInt(taskId);
out.writePersistableBundle(extras);
- ComponentName.writeToParcel(service, out);
+ out.writeParcelable(service, flags);
out.writeInt(requireCharging ? 1 : 0);
out.writeInt(requireDeviceIdle ? 1 : 0);
out.writeInt(networkCapabilities);
diff --git a/services/core/java/com/android/server/task/StateChangedListener.java b/services/core/java/com/android/server/task/StateChangedListener.java
index b1a4636..ab5cc7c 100644
--- a/services/core/java/com/android/server/task/StateChangedListener.java
+++ b/services/core/java/com/android/server/task/StateChangedListener.java
@@ -35,5 +35,5 @@
* it must be run immediately.
* @param taskStatus The state of the task which is to be run immediately.
*/
- public void onTaskDeadlineExpired(TaskStatus taskStatus);
+ public void onRunTaskNow(TaskStatus taskStatus);
}
diff --git a/services/core/java/com/android/server/task/TaskManagerService.java b/services/core/java/com/android/server/task/TaskManagerService.java
index 27af0ed..a6b68d9 100644
--- a/services/core/java/com/android/server/task/TaskManagerService.java
+++ b/services/core/java/com/android/server/task/TaskManagerService.java
@@ -25,7 +25,10 @@
import android.app.task.ITaskManager;
import android.app.task.Task;
import android.app.task.TaskManager;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.Handler;
@@ -33,6 +36,7 @@
import android.os.Message;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.os.UserHandle;
import android.util.Slog;
import android.util.SparseArray;
@@ -53,9 +57,8 @@
* about constraints, or the state of active tasks. It receives callbacks from the various
* controllers and completed tasks and operates accordingly.
*
- * Note on locking: Any operations that manipulate {@link #mTasks} need to lock on that object, and
- * similarly for {@link #mActiveServices}. If both locks need to be held take mTasksSet first and then
- * mActiveService afterwards.
+ * Note on locking: Any operations that manipulate {@link #mTasks} need to lock on that object.
+ * Any function with the suffix 'Locked' also needs to lock on {@link #mTasks}.
* @hide
*/
public class TaskManagerService extends com.android.server.SystemService
@@ -65,12 +68,6 @@
/** 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;
@@ -109,18 +106,42 @@
private final TaskHandler mHandler;
private final TaskManagerStub mTaskManagerStub;
+ /**
+ * Cleans up outstanding jobs when a package is removed. Even if it's being replaced later we
+ * still clean up. On reinstall the package will have a new uid.
+ */
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Slog.d(TAG, "Receieved: " + intent.getAction());
+ if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
+ int uidRemoved = intent.getIntExtra(Intent.EXTRA_UID, -1);
+ if (DEBUG) {
+ Slog.d(TAG, "Removing jobs for uid: " + uidRemoved);
+ }
+ cancelTasksForUid(uidRemoved);
+ } else if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) {
+ final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
+ if (DEBUG) {
+ Slog.d(TAG, "Removing jobs for user: " + userId);
+ }
+ cancelTasksForUser(userId);
+ }
+ }
+ };
/**
* Entry point from client to schedule the provided task.
- * This will add the task to the
+ * This cancels the task if it's already been scheduled, and replaces it with the one provided.
* @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.
+ * @param canPersistTask Whether or not the client has the appropriate permissions for
+ * persisting 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);
+ cancelTask(uId, task.getId());
startTrackingTask(taskStatus);
return TaskManager.RESULT_SUCCESS;
}
@@ -137,36 +158,33 @@
return outList;
}
+ private void cancelTasksForUser(int userHandle) {
+ synchronized (mTasks) {
+ List<TaskStatus> tasksForUser = mTasks.getTasksByUser(userHandle);
+ for (TaskStatus toRemove : tasksForUser) {
+ if (DEBUG) {
+ Slog.d(TAG, "Cancelling: " + toRemove);
+ }
+ cancelTaskLocked(toRemove);
+ }
+ }
+ }
+
/**
* 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) {
+ public void cancelTasksForUid(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();
+ List<TaskStatus> tasksForUid = mTasks.getTasksByUid(uid);
+ for (TaskStatus toRemove : tasksForUid) {
+ if (DEBUG) {
+ Slog.d(TAG, "Cancelling: " + toRemove);
}
- }
- }
- // Cancel if running.
- synchronized (mActiveServices) {
- for (TaskServiceContext tsc : mActiveServices) {
- if (tsc.getRunningTask().getUid() == uid) {
- tsc.cancelExecutingTask();
- }
+ cancelTaskLocked(toRemove);
}
}
}
@@ -179,32 +197,22 @@
* @param taskId Id of the task, provided at schedule-time.
*/
public void cancelTask(int uid, int taskId) {
+ TaskStatus toCancel;
synchronized (mTasks) {
- if (!mTasks.remove(uid, taskId)) {
- // If it's not in the master list, it's nowhere.
- return;
+ toCancel = mTasks.getTaskByUidAndTaskId(uid, taskId);
+ if (toCancel != null) {
+ cancelTaskLocked(toCancel);
}
}
- 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;
- }
- }
- }
+ }
+
+ private void cancelTaskLocked(TaskStatus cancelled) {
+ // Remove from store.
+ stopTrackingTask(cancelled);
+ // Remove from pending queue.
+ mPendingTasks.remove(cancelled);
+ // Cancel if running.
+ stopTaskOnServiceContextLocked(cancelled);
}
/**
@@ -218,7 +226,13 @@
*/
public TaskManagerService(Context context) {
super(context);
- mTasks = TaskStore.initAndGet(this);
+ // Create the controllers.
+ mControllers = new LinkedList<StateController>();
+ mControllers.add(ConnectivityController.get(this));
+ mControllers.add(TimeController.get(this));
+ mControllers.add(IdleController.get(this));
+ mControllers.add(BatteryController.get(this));
+
mHandler = new TaskHandler(context.getMainLooper());
mTaskManagerStub = new TaskManagerStub();
// Create the "runners".
@@ -226,12 +240,7 @@
mActiveServices.add(
new TaskServiceContext(this, context.getMainLooper()));
}
- // Create the controllers.
- mControllers = new LinkedList<StateController>();
- mControllers.add(ConnectivityController.get(this));
- mControllers.add(TimeController.get(this));
- mControllers.add(IdleController.get(this));
- mControllers.add(BatteryController.get(this));
+ mTasks = TaskStore.initAndGet(this);
}
@Override
@@ -239,18 +248,35 @@
publishBinderService(Context.TASK_SERVICE, mTaskManagerStub);
}
+ @Override
+ public void onBootPhase(int phase) {
+ if (PHASE_SYSTEM_SERVICES_READY == phase) {
+ // Register br for package removals and user removals.
+ final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addDataScheme("package");
+ getContext().registerReceiverAsUser(
+ mBroadcastReceiver, UserHandle.ALL, filter, null, null);
+ final IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_REMOVED);
+ getContext().registerReceiverAsUser(
+ mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
+ }
+ }
+
/**
* 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.
*/
private void startTrackingTask(TaskStatus taskStatus) {
+ boolean update;
synchronized (mTasks) {
- mTasks.add(taskStatus);
+ update = mTasks.add(taskStatus);
}
for (StateController controller : mControllers) {
+ if (update) {
+ controller.maybeStopTrackingTask(taskStatus);
+ }
controller.maybeStartTrackingTask(taskStatus);
-
}
}
@@ -272,16 +298,15 @@
return removed;
}
- private boolean cancelTaskOnServiceContext(TaskStatus ts) {
- synchronized (mActiveServices) {
- for (TaskServiceContext tsc : mActiveServices) {
- if (tsc.getRunningTask() == ts) {
- tsc.cancelExecutingTask();
- return true;
- }
+ private boolean stopTaskOnServiceContextLocked(TaskStatus ts) {
+ for (TaskServiceContext tsc : mActiveServices) {
+ final TaskStatus executing = tsc.getRunningTask();
+ if (executing != null && executing.matches(ts.getUid(), ts.getTaskId())) {
+ tsc.cancelExecutingTask();
+ return true;
}
- return false;
}
+ return false;
}
/**
@@ -289,15 +314,14 @@
* @return Whether or not the task represented by the status object is currently being run or
* is pending.
*/
- private boolean isCurrentlyActive(TaskStatus ts) {
- synchronized (mActiveServices) {
- for (TaskServiceContext serviceContext : mActiveServices) {
- if (serviceContext.getRunningTask() == ts) {
- return true;
- }
+ private boolean isCurrentlyActiveLocked(TaskStatus ts) {
+ for (TaskServiceContext serviceContext : mActiveServices) {
+ final TaskStatus running = serviceContext.getRunningTask();
+ if (running != null && running.matches(ts.getUid(), ts.getTaskId())) {
+ return true;
}
- return false;
}
+ return false;
}
/**
@@ -326,13 +350,14 @@
Slog.v(TAG, "Unrecognised back-off policy, defaulting to exponential.");
}
case Task.BackoffPolicy.EXPONENTIAL:
- newEarliestRuntimeElapsed += Math.pow(initialBackoffMillis, backoffAttempt);
+ newEarliestRuntimeElapsed +=
+ Math.pow(initialBackoffMillis * 0.001, backoffAttempt) * 1000;
break;
}
- long newLatestRuntimeElapsed = failureToReschedule.hasIdleConstraint() ? Long.MAX_VALUE
- : newEarliestRuntimeElapsed + RESCHEDULE_WINDOW_SLOP_MILLIS;
+ newEarliestRuntimeElapsed =
+ Math.min(newEarliestRuntimeElapsed, Task.MAX_BACKOFF_DELAY_MILLIS);
return new TaskStatus(failureToReschedule, newEarliestRuntimeElapsed,
- newLatestRuntimeElapsed, backoffAttempt);
+ TaskStatus.NO_LATEST_RUNTIME, backoffAttempt);
}
/**
@@ -372,6 +397,9 @@
*/
@Override
public void onTaskCompleted(TaskStatus taskStatus, boolean needsReschedule) {
+ if (DEBUG) {
+ Slog.d(TAG, "Completed " + taskStatus + ", reschedule=" + needsReschedule);
+ }
if (!stopTrackingTask(taskStatus)) {
if (DEBUG) {
Slog.e(TAG, "Error removing task: could not find task to remove. Was task " +
@@ -405,8 +433,8 @@
}
@Override
- public void onTaskDeadlineExpired(TaskStatus taskStatus) {
- mHandler.obtainMessage(MSG_TASK_EXPIRED, taskStatus);
+ public void onRunTaskNow(TaskStatus taskStatus) {
+ mHandler.obtainMessage(MSG_TASK_EXPIRED, taskStatus).sendToTarget();
}
/**
@@ -419,7 +447,7 @@
public void onTaskMapReadFinished(List<TaskStatus> tasks) {
synchronized (mTasks) {
for (TaskStatus ts : tasks) {
- if (mTasks.contains(ts)) {
+ if (mTasks.containsTaskIdForUid(ts.getTaskId(), ts.getUid())) {
// An app with BOOT_COMPLETED *might* have decided to reschedule their task, in
// the same amount of time it took us to read it from disk. If this is the case
// we leave it be.
@@ -440,7 +468,12 @@
public void handleMessage(Message message) {
switch (message.what) {
case MSG_TASK_EXPIRED:
- final TaskStatus expired = (TaskStatus) message.obj; // Unused for now.
+ synchronized (mTasks) {
+ TaskStatus runNow = (TaskStatus) message.obj;
+ if (!mPendingTasks.contains(runNow)) {
+ mPendingTasks.add(runNow);
+ }
+ }
queueReadyTasksForExecutionH();
break;
case MSG_CHECK_TASKS:
@@ -448,7 +481,7 @@
maybeQueueReadyTasksForExecutionH();
break;
}
- maybeRunNextPendingTaskH();
+ maybeRunPendingTasksH();
// Don't remove TASK_EXPIRED in case one came along while processing the queue.
removeMessages(MSG_CHECK_TASKS);
}
@@ -460,14 +493,10 @@
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);
+ if (isReadyToBeExecutedLocked(ts)) {
+ mPendingTasks.add(ts);
+ } else if (isReadyToBeCancelledLocked(ts)) {
+ stopTaskOnServiceContextLocked(ts);
}
}
}
@@ -477,62 +506,93 @@
* 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.
+ * 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 backoffCount = 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 (isReadyToBeExecutedLocked(ts)) {
+ if (ts.getNumFailures() > 0) {
+ backoffCount++;
+ }
if (ts.hasIdleConstraint()) {
idleCount++;
}
- if (ts.hasConnectivityConstraint() || ts.hasMeteredConstraint()) {
+ if (ts.hasConnectivityConstraint() || ts.hasUnmeteredConstraint()) {
connectivityCount++;
}
runnableTasks.add(ts);
- } else if (!criteriaSatisfied && isRunning) {
- cancelTaskOnServiceContext(ts);
+ } else if (isReadyToBeCancelledLocked(ts)) {
+ stopTaskOnServiceContextLocked(ts);
}
}
- if (idleCount >= MIN_IDLE_COUNT || connectivityCount >= MIN_CONNECTIVITY_COUNT ||
+ if (backoffCount > 0 || idleCount >= MIN_IDLE_COUNT ||
+ connectivityCount >= MIN_CONNECTIVITY_COUNT ||
runnableTasks.size() >= MIN_READY_TASKS_COUNT) {
for (TaskStatus ts : runnableTasks) {
- synchronized (mPendingTasks) {
- mPendingTasks.add(ts);
- }
+ 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}.
+ * Criteria for moving a job into the pending queue:
+ * - It's ready.
+ * - It's not pending.
+ * - It's not already running on a TSC.
*/
- private void maybeRunNextPendingTaskH() {
- TaskStatus nextPending;
- synchronized (mPendingTasks) {
- nextPending = mPendingTasks.poll();
- }
- if (nextPending == null) {
- return;
- }
+ private boolean isReadyToBeExecutedLocked(TaskStatus ts) {
+ return ts.isReady() && !mPendingTasks.contains(ts) && !isCurrentlyActiveLocked(ts);
+ }
- synchronized (mActiveServices) {
- for (TaskServiceContext tsc : mActiveServices) {
- if (tsc.isAvailable()) {
- if (tsc.executeRunnableTask(nextPending)) {
- return;
+ /**
+ * Criteria for cancelling an active job:
+ * - It's not ready
+ * - It's running on a TSC.
+ */
+ private boolean isReadyToBeCancelledLocked(TaskStatus ts) {
+ return !ts.isReady() && isCurrentlyActiveLocked(ts);
+ }
+
+ /**
+ * Reconcile jobs in the pending queue against available execution contexts.
+ * A controller can force a task into the pending queue even if it's already running, but
+ * here is where we decide whether to actually execute it.
+ */
+ private void maybeRunPendingTasksH() {
+ synchronized (mTasks) {
+ Iterator<TaskStatus> it = mPendingTasks.iterator();
+ while (it.hasNext()) {
+ TaskStatus nextPending = it.next();
+ TaskServiceContext availableContext = null;
+ for (TaskServiceContext tsc : mActiveServices) {
+ final TaskStatus running = tsc.getRunningTask();
+ if (running != null && running.matches(nextPending.getUid(),
+ nextPending.getTaskId())) {
+ // Already running this tId for this uId, skip.
+ availableContext = null;
+ break;
}
+ if (tsc.isAvailable()) {
+ availableContext = tsc;
+ }
+ }
+ if (availableContext != null) {
+ if (!availableContext.executeRunnableTask(nextPending)) {
+ if (DEBUG) {
+ Slog.d(TAG, "Error executing " + nextPending);
+ }
+ mTasks.remove(nextPending);
+ }
+ it.remove();
}
}
}
@@ -556,7 +616,7 @@
final int callingUid = Binder.getCallingUid();
synchronized (mPersistCache) {
Boolean cached = mPersistCache.get(callingUid);
- if (cached) {
+ if (cached != null) {
canPersist = cached.booleanValue();
} else {
// Persisting tasks is tantamount to running at boot, so we permit
@@ -574,6 +634,9 @@
// ITaskManager implementation
@Override
public int schedule(Task task) throws RemoteException {
+ if (DEBUG) {
+ Slog.d(TAG, "Scheduling task: " + task);
+ }
final boolean canPersist = canCallerPersistTasks();
final int uid = Binder.getCallingUid();
@@ -603,7 +666,7 @@
long ident = Binder.clearCallingIdentity();
try {
- TaskManagerService.this.cancelTaskForUid(uid);
+ TaskManagerService.this.cancelTasksForUid(uid);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -639,16 +702,36 @@
void dumpInternal(PrintWriter pw) {
synchronized (mTasks) {
- pw.print("Registered tasks:");
+ pw.println("Registered tasks:");
if (mTasks.size() > 0) {
for (TaskStatus ts : mTasks.getTasks()) {
- pw.println();
ts.dump(pw, " ");
}
} else {
pw.println();
pw.println("No tasks scheduled.");
}
+ for (StateController controller : mControllers) {
+ pw.println();
+ controller.dumpControllerState(pw);
+ }
+ pw.println();
+ pw.println("Pending");
+ for (TaskStatus taskStatus : mPendingTasks) {
+ pw.println(taskStatus.hashCode());
+ }
+ pw.println();
+ pw.println("Active jobs:");
+ for (TaskServiceContext tsc : mActiveServices) {
+ if (tsc.isAvailable()) {
+ continue;
+ } else {
+ pw.println(tsc.getRunningTask().hashCode() + " for: " +
+ (SystemClock.elapsedRealtime()
+ - tsc.getExecutionStartTimeElapsed())/1000 + "s " +
+ "timeout: " + tsc.getTimeoutElapsed());
+ }
+ }
}
pw.println();
}
diff --git a/services/core/java/com/android/server/task/TaskServiceContext.java b/services/core/java/com/android/server/task/TaskServiceContext.java
index 686ca38..a21de88 100644
--- a/services/core/java/com/android/server/task/TaskServiceContext.java
+++ b/services/core/java/com/android/server/task/TaskServiceContext.java
@@ -31,11 +31,11 @@
import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.os.WorkSource;
import android.util.Log;
import android.util.Slog;
-import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -54,7 +54,7 @@
private static final int defaultMaxActiveTasksPerService =
ActivityManager.isLowRamDeviceStatic() ? 1 : 3;
/** Amount of time a task is allowed to execute for before being considered timed-out. */
- private static final long EXECUTING_TIMESLICE_MILLIS = 5 * 60 * 1000;
+ private static final long EXECUTING_TIMESLICE_MILLIS = 60 * 1000;
/** Amount of time the TaskManager will wait for a response from an app for a message. */
private static final long OP_TIMEOUT_MILLIS = 8 * 1000;
/** String prefix for all wakelock names. */
@@ -100,10 +100,14 @@
/** Binder to the client service. */
ITaskService service;
- private final Object mAvailableLock = new Object();
+ private final Object mLock = new Object();
/** Whether this context is free. */
- @GuardedBy("mAvailableLock")
+ @GuardedBy("mLock")
private boolean mAvailable;
+ /** Track start time. */
+ private long mExecutionStartTimeElapsed;
+ /** Track when job will timeout. */
+ private long mTimeoutElapsed;
TaskServiceContext(TaskManagerService service, Looper looper) {
this(service.getContext(), service, looper);
@@ -114,46 +118,43 @@
mContext = context;
mCallbackHandler = new TaskServiceHandler(looper);
mCompletedListener = completedListener;
+ mAvailable = true;
}
/**
* 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.
+ * @return True if the task is valid and is running. False if the task cannot be executed.
*/
boolean executeRunnableTask(TaskStatus ts) {
- synchronized (mAvailableLock) {
+ synchronized (mLock) {
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);
+ mExecutionStartTimeElapsed = SystemClock.elapsedRealtime();
- 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.");
+ 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.");
+ }
+ mRunningTask = null;
+ mParams = null;
+ mExecutionStartTimeElapsed = 0L;
+ return false;
}
- return false;
+ mAvailable = false;
+ return true;
}
-
- return true;
}
/** Used externally to query the running task. Will return null if there is no task running. */
@@ -170,11 +171,19 @@
* @return Whether this context is available to handle incoming work.
*/
boolean isAvailable() {
- synchronized (mAvailableLock) {
+ synchronized (mLock) {
return mAvailable;
}
}
+ long getExecutionStartTimeElapsed() {
+ return mExecutionStartTimeElapsed;
+ }
+
+ long getTimeoutElapsed() {
+ return mTimeoutElapsed;
+ }
+
@Override
public void taskFinished(int taskId, boolean reschedule) {
if (!verifyCallingUid()) {
@@ -217,6 +226,12 @@
this.service = ITaskService.Stub.asInterface(service);
// Remove all timeouts.
mCallbackHandler.removeMessages(MSG_TIMEOUT);
+ final PowerManager pm =
+ (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+ TM_WAKELOCK_PREFIX + mRunningTask.getServiceComponent().getPackageName());
+ mWakeLock.setWorkSource(new WorkSource(mRunningTask.getUid()));
+ mWakeLock.setReferenceCounted(false);
mWakeLock.acquire();
mCallbackHandler.obtainMessage(MSG_SERVICE_BOUND).sendToTarget();
}
@@ -263,7 +278,8 @@
break;
case MSG_CALLBACK:
if (DEBUG) {
- Slog.d(TAG, "MSG_CALLBACK of : " + mRunningTask);
+ Slog.d(TAG, "MSG_CALLBACK of : " + mRunningTask + " v:" +
+ VERB_STRINGS[mVerb]);
}
removeMessages(MSG_TIMEOUT);
@@ -288,6 +304,7 @@
break;
case MSG_SHUTDOWN_EXECUTION:
closeAndCleanupTaskH(true /* needsReschedule */);
+ break;
default:
Log.e(TAG, "Unrecognised message: " + message);
}
@@ -423,7 +440,7 @@
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. " +
+ " sending onStop. " +
mRunningTask.getServiceComponent().getShortClassName() + "' tId: "
+ taskId);
sendStopMessageH();
@@ -452,7 +469,7 @@
service.stopTask(mParams);
} catch (RemoteException e) {
Log.e(TAG, "Error sending onStopTask to client.", e);
- closeAndCleanupTaskH(false);
+ closeAndCleanupTaskH(false /* reschedule */);
}
}
@@ -464,17 +481,17 @@
*/
private void closeAndCleanupTaskH(boolean reschedule) {
removeMessages(MSG_TIMEOUT);
- mWakeLock.release();
- mContext.unbindService(TaskServiceContext.this);
- mCompletedListener.onTaskCompleted(mRunningTask, reschedule);
+ synchronized (mLock) {
+ mWakeLock.release();
+ mContext.unbindService(TaskServiceContext.this);
+ mCompletedListener.onTaskCompleted(mRunningTask, reschedule);
- mWakeLock = null;
- mRunningTask = null;
- mParams = null;
- mVerb = -1;
- mCancelled.set(false);
- service = null;
- synchronized (mAvailableLock) {
+ mWakeLock = null;
+ mRunningTask = null;
+ mParams = null;
+ mVerb = -1;
+ mCancelled.set(false);
+ service = null;
mAvailable = true;
}
}
@@ -496,6 +513,7 @@
}
Message m = mCallbackHandler.obtainMessage(MSG_TIMEOUT);
mCallbackHandler.sendMessageDelayed(m, timeoutMillis);
+ mTimeoutElapsed = SystemClock.elapsedRealtime() + timeoutMillis;
}
}
}
diff --git a/services/core/java/com/android/server/task/TaskStore.java b/services/core/java/com/android/server/task/TaskStore.java
index 6bb00b1..9e095e7 100644
--- a/services/core/java/com/android/server/task/TaskStore.java
+++ b/services/core/java/com/android/server/task/TaskStore.java
@@ -23,6 +23,7 @@
import android.os.Handler;
import android.os.PersistableBundle;
import android.os.SystemClock;
+import android.os.UserHandle;
import android.util.AtomicFile;
import android.util.ArraySet;
import android.util.Pair;
@@ -54,7 +55,6 @@
* - When a task is added, it will determine if the task requirements have changed (update) and
* whether the controllers need to be updated.
* - Persists Tasks, figures out when to to rewrite the Task to disk.
- * - Is threadsafe.
* - Handles rescheduling of tasks.
* - When a periodic task is executed and must be re-added.
* - When a task fails and the client requests that it be retried with backoff.
@@ -96,7 +96,7 @@
@VisibleForTesting
public static TaskStore initAndGetForTesting(Context context, File dataDir,
- TaskMapReadFinishedListener callback) {
+ TaskMapReadFinishedListener callback) {
return new TaskStore(context, dataDir, callback);
}
@@ -126,14 +126,22 @@
if (taskStatus.isPersisted()) {
maybeWriteStatusToDiskAsync();
}
+ if (DEBUG) {
+ Slog.d(TAG, "Added task status to store: " + taskStatus);
+ }
return replaced;
}
/**
* Whether this taskStatus object already exists in the TaskStore.
*/
- public boolean contains(TaskStatus taskStatus) {
- return mTasksSet.contains(taskStatus);
+ public boolean containsTaskIdForUid(int taskId, int uId) {
+ for (TaskStatus ts : mTasksSet) {
+ if (ts.getUid() == uId && ts.getTaskId() == taskId) {
+ return true;
+ }
+ }
+ return false;
}
public int size() {
@@ -162,49 +170,48 @@
maybeWriteStatusToDiskAsync();
}
+ public List<TaskStatus> getTasksByUser(int userHandle) {
+ List<TaskStatus> matchingTasks = new ArrayList<TaskStatus>();
+ Iterator<TaskStatus> it = mTasksSet.iterator();
+ while (it.hasNext()) {
+ TaskStatus ts = it.next();
+ if (UserHandle.getUserId(ts.getUid()) == userHandle) {
+ matchingTasks.add(ts);
+ }
+ }
+ return matchingTasks;
+ }
+
/**
- * 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.
+ * @return All TaskStatus objects for a given uid from the master list.
*/
- public boolean removeAllByUid(int uid) {
+ public List<TaskStatus> getTasksByUid(int uid) {
+ List<TaskStatus> matchingTasks = new ArrayList<TaskStatus>();
Iterator<TaskStatus> it = mTasksSet.iterator();
while (it.hasNext()) {
TaskStatus ts = it.next();
if (ts.getUid() == uid) {
- it.remove();
- maybeWriteStatusToDiskAsync();
- return true;
+ matchingTasks.add(ts);
}
}
- return false;
+ return matchingTasks;
}
/**
- * 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.
+ * @return the TaskStatus that matches the provided uId and taskId, or null if none found.
*/
- public boolean remove(int uid, int taskId) {
- boolean changed = false;
+ public TaskStatus getTaskByUidAndTaskId(int uid, int taskId) {
Iterator<TaskStatus> it = mTasksSet.iterator();
while (it.hasNext()) {
TaskStatus ts = it.next();
if (ts.getUid() == uid && ts.getTaskId() == taskId) {
- it.remove();
- changed = true;
+ return ts;
}
}
- if (changed) {
- maybeWriteStatusToDiskAsync();
- }
- return changed;
+ return null;
}
/**
@@ -326,7 +333,7 @@
*/
private void writeConstraintsToXml(XmlSerializer out, TaskStatus taskStatus) throws IOException {
out.startTag(null, XML_TAG_PARAMS_CONSTRAINTS);
- if (taskStatus.hasMeteredConstraint()) {
+ if (taskStatus.hasUnmeteredConstraint()) {
out.attribute(null, "unmetered", Boolean.toString(true));
}
if (taskStatus.hasConnectivityConstraint()) {
@@ -393,9 +400,11 @@
public void run() {
try {
List<TaskStatus> tasks;
+ FileInputStream fis = mTasksFile.openRead();
synchronized (TaskStore.this) {
- tasks = readTaskMapImpl();
+ tasks = readTaskMapImpl(fis);
}
+ fis.close();
if (tasks != null) {
mCallback.onTaskMapReadFinished(tasks);
}
@@ -414,8 +423,7 @@
}
}
- private List<TaskStatus> readTaskMapImpl() throws XmlPullParserException, IOException {
- FileInputStream fis = mTasksFile.openRead();
+ private List<TaskStatus> readTaskMapImpl(FileInputStream fis) throws XmlPullParserException, IOException {
XmlPullParser parser = Xml.newPullParser();
parser.setInput(fis, null);
@@ -537,10 +545,10 @@
}
} else if (XML_TAG_ONEOFF.equals(parser.getName())) {
try {
- if (runtimes.first != TaskStatus.DEFAULT_EARLIEST_RUNTIME) {
+ if (runtimes.first != TaskStatus.NO_EARLIEST_RUNTIME) {
taskBuilder.setMinimumLatency(runtimes.first - SystemClock.elapsedRealtime());
}
- if (runtimes.second != TaskStatus.DEFAULT_LATEST_RUNTIME) {
+ if (runtimes.second != TaskStatus.NO_LATEST_RUNTIME) {
taskBuilder.setOverrideDeadline(
runtimes.second - SystemClock.elapsedRealtime());
}
@@ -632,8 +640,8 @@
final long nowWallclock = System.currentTimeMillis();
final long nowElapsed = SystemClock.elapsedRealtime();
- long earliestRunTimeElapsed = TaskStatus.DEFAULT_EARLIEST_RUNTIME;
- long latestRunTimeElapsed = TaskStatus.DEFAULT_LATEST_RUNTIME;
+ long earliestRunTimeElapsed = TaskStatus.NO_EARLIEST_RUNTIME;
+ long latestRunTimeElapsed = TaskStatus.NO_LATEST_RUNTIME;
String val = parser.getAttributeValue(null, "deadline");
if (val != null) {
long latestRuntimeWallclock = Long.valueOf(val);
@@ -652,4 +660,4 @@
return Pair.create(earliestRunTimeElapsed, latestRunTimeElapsed);
}
}
-}
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/task/controllers/BatteryController.java b/services/core/java/com/android/server/task/controllers/BatteryController.java
index 4727e9a..443527f 100644
--- a/services/core/java/com/android/server/task/controllers/BatteryController.java
+++ b/services/core/java/com/android/server/task/controllers/BatteryController.java
@@ -35,6 +35,7 @@
import com.android.server.task.StateChangedListener;
import com.android.server.task.TaskManagerService;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -210,4 +211,9 @@
}
}
}
+
+ @Override
+ public void dumpControllerState(PrintWriter pw) {
+
+ }
}
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 4819460..c1ab0f0 100644
--- a/services/core/java/com/android/server/task/controllers/ConnectivityController.java
+++ b/services/core/java/com/android/server/task/controllers/ConnectivityController.java
@@ -23,13 +23,15 @@
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
+import android.os.ServiceManager;
import android.os.UserHandle;
-import android.util.Log;
import android.util.Slog;
+import com.android.server.ConnectivityService;
import com.android.server.task.StateChangedListener;
import com.android.server.task.TaskManagerService;
+import java.io.PrintWriter;
import java.util.LinkedList;
import java.util.List;
@@ -38,25 +40,28 @@
* 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";
+public class ConnectivityController extends StateController implements
+ ConnectivityManager.OnNetworkActiveListener {
+ private static final String TAG = "TaskManager.Conn";
private final List<TaskStatus> mTrackedTasks = new LinkedList<TaskStatus>();
private final BroadcastReceiver mConnectivityChangedReceiver =
new ConnectivityChangedReceiver();
/** Singleton. */
private static ConnectivityController mSingleton;
-
+ private static Object sCreationLock = new Object();
/** Track whether the latest active network is metered. */
- private boolean mMetered;
+ private boolean mNetworkUnmetered;
/** Track whether the latest active network is connected. */
- private boolean mConnectivity;
+ private boolean mNetworkConnected;
- public static synchronized ConnectivityController get(TaskManagerService taskManager) {
- if (mSingleton == null) {
- mSingleton = new ConnectivityController(taskManager, taskManager.getContext());
+ public static ConnectivityController get(TaskManagerService taskManager) {
+ synchronized (sCreationLock) {
+ if (mSingleton == null) {
+ mSingleton = new ConnectivityController(taskManager, taskManager.getContext());
+ }
+ return mSingleton;
}
- return mSingleton;
}
private ConnectivityController(StateChangedListener stateChangedListener, Context context) {
@@ -66,39 +71,72 @@
intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
mContext.registerReceiverAsUser(
mConnectivityChangedReceiver, UserHandle.ALL, intentFilter, null, null);
- }
-
- @Override
- public synchronized void maybeStartTrackingTask(TaskStatus taskStatus) {
- if (taskStatus.hasConnectivityConstraint() || taskStatus.hasMeteredConstraint()) {
- taskStatus.connectivityConstraintSatisfied.set(mConnectivity);
- taskStatus.meteredConstraintSatisfied.set(mMetered);
- mTrackedTasks.add(taskStatus);
+ ConnectivityService cs =
+ (ConnectivityService)ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
+ if (cs != null) {
+ if (cs.getActiveNetworkInfo() != null) {
+ mNetworkConnected = cs.getActiveNetworkInfo().isConnected();
+ }
+ mNetworkUnmetered = mNetworkConnected && !cs.isActiveNetworkMetered();
}
}
@Override
- public synchronized void maybeStopTrackingTask(TaskStatus taskStatus) {
- mTrackedTasks.remove(taskStatus);
+ public void maybeStartTrackingTask(TaskStatus taskStatus) {
+ if (taskStatus.hasConnectivityConstraint() || taskStatus.hasUnmeteredConstraint()) {
+ synchronized (mTrackedTasks) {
+ taskStatus.connectivityConstraintSatisfied.set(mNetworkConnected);
+ taskStatus.unmeteredConstraintSatisfied.set(mNetworkUnmetered);
+ mTrackedTasks.add(taskStatus);
+ }
+ }
+ }
+
+ @Override
+ public void maybeStopTrackingTask(TaskStatus taskStatus) {
+ if (taskStatus.hasConnectivityConstraint() || taskStatus.hasUnmeteredConstraint()) {
+ synchronized (mTrackedTasks) {
+ mTrackedTasks.remove(taskStatus);
+ }
+ }
}
/**
* @param userId Id of the user for whom we are updating the connectivity state.
*/
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) {
+ synchronized (mTrackedTasks) {
+ boolean changed = false;
+ for (TaskStatus ts : mTrackedTasks) {
+ if (ts.getUserId() != userId) {
+ continue;
+ }
+ boolean prevIsConnected =
+ ts.connectivityConstraintSatisfied.getAndSet(mNetworkConnected);
+ boolean prevIsMetered = ts.unmeteredConstraintSatisfied.getAndSet(mNetworkUnmetered);
+ if (prevIsConnected != mNetworkConnected || prevIsMetered != mNetworkUnmetered) {
changed = true;
+ }
+ }
+ if (changed) {
+ mStateChangedListener.onControllerStateChanged();
}
}
- if (changed) {
- mStateChangedListener.onControllerStateChanged();
+ }
+
+ /**
+ * We know the network has just come up. We want to run any tasks that are ready.
+ */
+ public synchronized void onNetworkActive() {
+ synchronized (mTrackedTasks) {
+ for (TaskStatus ts : mTrackedTasks) {
+ if (ts.isReady()) {
+ if (DEBUG) {
+ Slog.d(TAG, "Running " + ts + " due to network activity.");
+ }
+ mStateChangedListener.onRunTaskNow(ts);
+ }
+ }
}
}
@@ -113,6 +151,10 @@
// TODO: Test whether this will be called twice for each user.
@Override
public void onReceive(Context context, Intent intent) {
+ if (DEBUG) {
+ Slog.d(TAG, "Received connectivity event: " + intent.getAction() + " u"
+ + context.getUserId());
+ }
final String action = intent.getAction();
if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
final int networkType =
@@ -122,14 +164,18 @@
final ConnectivityManager connManager = (ConnectivityManager)
context.getSystemService(Context.CONNECTIVITY_SERVICE);
final NetworkInfo activeNetwork = connManager.getActiveNetworkInfo();
+ final int userid = context.getUserId();
// This broadcast gets sent a lot, only update if the active network has changed.
- 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();
+ if (activeNetwork == null) {
+ mNetworkUnmetered = false;
+ mNetworkConnected = false;
+ updateTrackedTasks(userid);
+ } else if (activeNetwork.getType() == networkType) {
+ mNetworkUnmetered = false;
+ mNetworkConnected = !intent.getBooleanExtra(
+ ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
+ if (mNetworkConnected) { // No point making the call if we know there's no conn.
+ mNetworkUnmetered = !connManager.isActiveNetworkMetered();
}
updateTrackedTasks(userid);
}
@@ -140,4 +186,15 @@
}
}
};
-}
+
+ @Override
+ public void dumpControllerState(PrintWriter pw) {
+ pw.println("Conn.");
+ pw.println("connected: " + mNetworkConnected + " unmetered: " + mNetworkUnmetered);
+ for (TaskStatus ts: mTrackedTasks) {
+ pw.println(String.valueOf(ts.hashCode()).substring(0, 3) + ".."
+ + ": C=" + ts.hasConnectivityConstraint()
+ + ", UM=" + ts.hasUnmeteredConstraint());
+ }
+ }
+}
\ No newline at end of file
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 c47faca..e749b00 100644
--- a/services/core/java/com/android/server/task/controllers/IdleController.java
+++ b/services/core/java/com/android/server/task/controllers/IdleController.java
@@ -16,6 +16,7 @@
package com.android.server.task.controllers;
+import java.io.PrintWriter;
import java.util.ArrayList;
import android.app.AlarmManager;
@@ -177,4 +178,9 @@
}
}
}
+
+ @Override
+ public void dumpControllerState(PrintWriter pw) {
+
+ }
}
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 cbe6ff8..a7f52f5 100644
--- a/services/core/java/com/android/server/task/controllers/StateController.java
+++ b/services/core/java/com/android/server/task/controllers/StateController.java
@@ -21,6 +21,8 @@
import com.android.server.task.StateChangedListener;
import com.android.server.task.TaskManagerService;
+import java.io.PrintWriter;
+
/**
* Incorporates shared controller logic between the various controllers of the TaskManager.
* These are solely responsible for tracking a list of tasks, and notifying the TM when these
@@ -48,4 +50,6 @@
*/
public abstract void maybeStopTrackingTask(TaskStatus taskStatus);
+ public abstract void dumpControllerState(PrintWriter pw);
+
}
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 33670a1..a286737 100644
--- a/services/core/java/com/android/server/task/controllers/TaskStatus.java
+++ b/services/core/java/com/android/server/task/controllers/TaskStatus.java
@@ -37,8 +37,8 @@
* @hide
*/
public class TaskStatus {
- public static final long DEFAULT_LATEST_RUNTIME = Long.MAX_VALUE;
- public static final long DEFAULT_EARLIEST_RUNTIME = 0L;
+ public static final long NO_LATEST_RUNTIME = Long.MAX_VALUE;
+ public static final long NO_EARLIEST_RUNTIME = 0L;
final Task task;
final int uId;
@@ -51,7 +51,7 @@
final AtomicBoolean timeDelayConstraintSatisfied = new AtomicBoolean();
final AtomicBoolean deadlineConstraintSatisfied = new AtomicBoolean();
final AtomicBoolean idleConstraintSatisfied = new AtomicBoolean();
- final AtomicBoolean meteredConstraintSatisfied = new AtomicBoolean();
+ final AtomicBoolean unmeteredConstraintSatisfied = new AtomicBoolean();
final AtomicBoolean connectivityConstraintSatisfied = new AtomicBoolean();
/**
@@ -90,9 +90,9 @@
latestRunTimeElapsedMillis = elapsedNow + task.getIntervalMillis();
} else {
earliestRunTimeElapsedMillis = task.hasEarlyConstraint() ?
- elapsedNow + task.getMinLatencyMillis() : DEFAULT_EARLIEST_RUNTIME;
+ elapsedNow + task.getMinLatencyMillis() : NO_EARLIEST_RUNTIME;
latestRunTimeElapsedMillis = task.hasLateConstraint() ?
- elapsedNow + task.getMaxExecutionDelayMillis() : DEFAULT_LATEST_RUNTIME;
+ elapsedNow + task.getMaxExecutionDelayMillis() : NO_LATEST_RUNTIME;
}
}
@@ -152,7 +152,7 @@
return task.getNetworkCapabilities() == Task.NetworkType.ANY;
}
- public boolean hasMeteredConstraint() {
+ public boolean hasUnmeteredConstraint() {
return task.getNetworkCapabilities() == Task.NetworkType.UNMETERED;
}
@@ -161,11 +161,11 @@
}
public boolean hasTimingDelayConstraint() {
- return earliestRunTimeElapsedMillis != DEFAULT_EARLIEST_RUNTIME;
+ return earliestRunTimeElapsedMillis != NO_EARLIEST_RUNTIME;
}
public boolean hasDeadlineConstraint() {
- return latestRunTimeElapsedMillis != DEFAULT_LATEST_RUNTIME;
+ return latestRunTimeElapsedMillis != NO_LATEST_RUNTIME;
}
public boolean hasIdleConstraint() {
@@ -190,12 +190,13 @@
return (!hasChargingConstraint() || chargingConstraintSatisfied.get())
&& (!hasTimingDelayConstraint() || timeDelayConstraintSatisfied.get())
&& (!hasConnectivityConstraint() || connectivityConstraintSatisfied.get())
- && (!hasMeteredConstraint() || meteredConstraintSatisfied.get())
+ && (!hasUnmeteredConstraint() || unmeteredConstraintSatisfied.get())
&& (!hasIdleConstraint() || idleConstraintSatisfied.get())
- && (!hasDeadlineConstraint() || deadlineConstraintSatisfied.get());
+ // Also ready if the deadline has expired - special case.
+ || (hasDeadlineConstraint() && deadlineConstraintSatisfied.get());
}
- @Override
+ /*@Override
public int hashCode() {
int result = getServiceComponent().hashCode();
result = 31 * result + task.getId();
@@ -212,12 +213,24 @@
return ((task.getId() == that.task.getId())
&& (uId == that.uId)
&& (getServiceComponent().equals(that.getServiceComponent())));
+ }*/
+
+ public boolean matches(int uid, int taskId) {
+ return this.task.getId() == taskId && this.uId == uid;
}
+ @Override
+ public String toString() {
+ return String.valueOf(hashCode()).substring(0, 3) + ".."
+ + ":[" + task.getService().getPackageName() + ",tId=" + task.getId()
+ + ",R=(" + earliestRunTimeElapsedMillis + "," + latestRunTimeElapsedMillis + ")"
+ + ",N=" + task.getNetworkCapabilities() + ",C=" + task.isRequireCharging()
+ + ",I=" + task.isRequireDeviceIdle() + ",F=" + numFailures
+ + (isReady() ? "(READY)" : "")
+ + "]";
+ }
// Dumpsys infrastructure
public void dump(PrintWriter pw, String prefix) {
- 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());
+ pw.println(this.toString());
}
}
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 8c6dd27..b75036c 100644
--- a/services/core/java/com/android/server/task/controllers/TimeController.java
+++ b/services/core/java/com/android/server/task/controllers/TimeController.java
@@ -23,10 +23,12 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.os.SystemClock;
+import android.util.Slog;
import com.android.server.task.StateChangedListener;
import com.android.server.task.TaskManagerService;
+import java.io.PrintWriter;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
@@ -39,14 +41,16 @@
public class TimeController extends StateController {
private static final String TAG = "TaskManager.Time";
private static final String ACTION_TASK_EXPIRED =
- "android.content.taskmanager.TASK_EXPIRED";
+ "android.content.taskmanager.TASK_DEADLINE_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;
+ private final PendingIntent mDeadlineExpiredAlarmIntent;
/** Set an alarm for the next task delay expiry. This*/
private final PendingIntent mNextDelayExpiredAlarmIntent;
+ /** Constant time determining how near in the future we'll set an alarm for. */
+ private static final long MIN_WAKEUP_INTERVAL_MILLIS = 15 * 1000;
private long mNextTaskExpiredElapsedMillis;
private long mNextDelayExpiredElapsedMillis;
@@ -66,12 +70,14 @@
private TimeController(StateChangedListener stateChangedListener, Context context) {
super(stateChangedListener, context);
- mTaskExpiredAlarmIntent =
+ mDeadlineExpiredAlarmIntent =
PendingIntent.getBroadcast(mContext, 0 /* ignored */,
new Intent(ACTION_TASK_EXPIRED), 0);
mNextDelayExpiredAlarmIntent =
PendingIntent.getBroadcast(mContext, 0 /* ignored */,
new Intent(ACTION_TASK_DELAY_EXPIRED), 0);
+ mNextTaskExpiredElapsedMillis = Long.MAX_VALUE;
+ mNextDelayExpiredElapsedMillis = Long.MAX_VALUE;
// Register BR for these intents.
IntentFilter intentFilter = new IntentFilter(ACTION_TASK_EXPIRED);
@@ -85,64 +91,37 @@
*/
@Override
public synchronized void maybeStartTrackingTask(TaskStatus task) {
- if (task.hasTimingDelayConstraint()) {
+ if (task.hasTimingDelayConstraint() || task.hasDeadlineConstraint()) {
+ maybeStopTrackingTask(task);
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()) {
+ if (ts.getLatestRunTimeElapsed() < task.getLatestRunTimeElapsed()) {
// Insert
- it.add(task);
break;
}
}
- maybeUpdateAlarms(task.getEarliestRunTime(), task.getLatestRunTimeElapsed());
+ it.add(task);
+ maybeUpdateAlarms(
+ task.hasTimingDelayConstraint() ? task.getEarliestRunTime() : Long.MAX_VALUE,
+ task.hasDeadlineConstraint() ? task.getLatestRunTimeElapsed() : Long.MAX_VALUE);
}
}
/**
- * If the task passed in is being tracked, figure out if we need to update our alarms, and if
- * so, update them.
+ * When we stop tracking a task, we only need to update our alarms if the task we're no longer
+ * tracking was the one our alarms were based off of.
+ * Really an == comparison should be enough, but why play with fate? We'll do <=.
*/
@Override
public synchronized void maybeStopTrackingTask(TaskStatus taskStatus) {
if (mTrackedTasks.remove(taskStatus)) {
- if (mNextDelayExpiredElapsedMillis <= taskStatus.getEarliestRunTime()) {
- handleTaskDelayExpired();
- }
- if (mNextTaskExpiredElapsedMillis <= taskStatus.getLatestRunTimeElapsed()) {
- handleTaskDeadlineExpired();
- }
+ checkExpiredDelaysAndResetAlarm();
+ checkExpiredDeadlinesAndResetAlarm();
}
}
/**
- * 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
@@ -155,17 +134,6 @@
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);
@@ -173,38 +141,41 @@
}
/**
- * Handles alarm that notifies that a task has expired. When this function is called at least
- * one task must be run.
+ * Checks list of tasks for ones that have an expired deadline, sending them to the TaskManager
+ * if so, removing them from this list, and updating the alarm for the next expiry time.
*/
- private synchronized void handleTaskDeadlineExpired() {
+ private synchronized void checkExpiredDeadlinesAndResetAlarm() {
long nextExpiryTime = Long.MAX_VALUE;
final long nowElapsedMillis = SystemClock.elapsedRealtime();
Iterator<TaskStatus> it = mTrackedTasks.iterator();
while (it.hasNext()) {
TaskStatus ts = it.next();
+ if (!ts.hasDeadlineConstraint()) {
+ continue;
+ }
final long taskDeadline = ts.getLatestRunTimeElapsed();
if (taskDeadline <= nowElapsedMillis) {
ts.deadlineConstraintSatisfied.set(true);
- mStateChangedListener.onTaskDeadlineExpired(ts);
+ mStateChangedListener.onRunTaskNow(ts);
it.remove();
} else { // Sorted by expiry time, so take the next one and stop.
nextExpiryTime = taskDeadline;
break;
}
}
- maybeUpdateAlarms(Long.MAX_VALUE, nextExpiryTime);
+ setDeadlineExpiredAlarm(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() {
+ private synchronized void checkExpiredDelaysAndResetAlarm() {
final long nowElapsedMillis = SystemClock.elapsedRealtime();
long nextDelayTime = Long.MAX_VALUE;
-
+ boolean ready = false;
Iterator<TaskStatus> it = mTrackedTasks.iterator();
while (it.hasNext()) {
final TaskStatus ts = it.next();
@@ -212,31 +183,107 @@
continue;
}
final long taskDelayTime = ts.getEarliestRunTime();
- if (taskDelayTime < nowElapsedMillis) {
+ if (taskDelayTime <= nowElapsedMillis) {
ts.timeDelayConstraintSatisfied.set(true);
if (canStopTrackingTask(ts)) {
it.remove();
}
+ if (ts.isReady()) {
+ ready = true;
+ }
} else { // Keep going through list to get next delay time.
if (nextDelayTime > taskDelayTime) {
nextDelayTime = taskDelayTime;
}
}
}
- mStateChangedListener.onControllerStateChanged();
- maybeUpdateAlarms(nextDelayTime, Long.MAX_VALUE);
+ if (ready) {
+ mStateChangedListener.onControllerStateChanged();
+ }
+ setDelayExpiredAlarm(nextDelayTime);
+ }
+
+ private void maybeUpdateAlarms(long delayExpiredElapsed, long deadlineExpiredElapsed) {
+ if (delayExpiredElapsed < mNextDelayExpiredElapsedMillis) {
+ setDelayExpiredAlarm(delayExpiredElapsed);
+ }
+ if (deadlineExpiredElapsed < mNextTaskExpiredElapsedMillis) {
+ setDeadlineExpiredAlarm(deadlineExpiredElapsed);
+ }
+ }
+
+ /**
+ * 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) {
+ final long earliestWakeupTimeElapsed =
+ SystemClock.elapsedRealtime() + MIN_WAKEUP_INTERVAL_MILLIS;
+ if (alarmTimeElapsedMillis < earliestWakeupTimeElapsed) {
+ alarmTimeElapsedMillis = earliestWakeupTimeElapsed;
+ }
+ mNextDelayExpiredElapsedMillis = alarmTimeElapsedMillis;
+ updateAlarmWithPendingIntent(mNextDelayExpiredAlarmIntent, mNextDelayExpiredElapsedMillis);
+ }
+
+ /**
+ * 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) {
+ final long earliestWakeupTimeElapsed =
+ SystemClock.elapsedRealtime() + MIN_WAKEUP_INTERVAL_MILLIS;
+ if (alarmTimeElapsedMillis < earliestWakeupTimeElapsed) {
+ alarmTimeElapsedMillis = earliestWakeupTimeElapsed;
+ }
+ mNextTaskExpiredElapsedMillis = alarmTimeElapsedMillis;
+ updateAlarmWithPendingIntent(mDeadlineExpiredAlarmIntent, mNextTaskExpiredElapsedMillis);
+ }
+
+ private void updateAlarmWithPendingIntent(PendingIntent pi, long alarmTimeElapsed) {
+ ensureAlarmService();
+ if (alarmTimeElapsed == Long.MAX_VALUE) {
+ mAlarmService.cancel(pi);
+ } else {
+ if (DEBUG) {
+ Slog.d(TAG, "Setting " + pi.getIntent().getAction() + " for: " + alarmTimeElapsed);
+ }
+ mAlarmService.set(AlarmManager.ELAPSED_REALTIME, alarmTimeElapsed, pi);
+ }
}
private final BroadcastReceiver mAlarmExpiredReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
+ if (DEBUG) {
+ Slog.d(TAG, "Just received alarm: " + intent.getAction());
+ }
// 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();
+ checkExpiredDeadlinesAndResetAlarm();
} else if (ACTION_TASK_DELAY_EXPIRED.equals(intent.getAction())) {
- handleTaskDelayExpired();
+ checkExpiredDelaysAndResetAlarm();
}
}
};
-}
+
+ @Override
+ public void dumpControllerState(PrintWriter pw) {
+ final long nowElapsed = SystemClock.elapsedRealtime();
+ pw.println("Alarms (" + SystemClock.elapsedRealtime() + ")");
+ pw.println(
+ "Next delay alarm in " + (mNextDelayExpiredElapsedMillis - nowElapsed)/1000 + "s");
+ pw.println("Next deadline alarm in " + (mNextTaskExpiredElapsedMillis - nowElapsed)/1000
+ + "s");
+ pw.println("Tracking:");
+ for (TaskStatus ts : mTrackedTasks) {
+ pw.println(String.valueOf(ts.hashCode()).substring(0, 3) + ".."
+ + ": (" + (ts.hasTimingDelayConstraint() ? ts.getEarliestRunTime() : "N/A")
+ + ", " + (ts.hasDeadlineConstraint() ?ts.getLatestRunTimeElapsed() : "N/A")
+ + ")");
+ }
+ }
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/task/controllers/BatteryControllerTest.java b/services/tests/servicestests/src/com/android/server/task/controllers/BatteryControllerTest.java
index e617caf..6617a05 100644
--- a/services/tests/servicestests/src/com/android/server/task/controllers/BatteryControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/task/controllers/BatteryControllerTest.java
@@ -40,7 +40,7 @@
}
@Override
- public void onTaskDeadlineExpired(TaskStatus taskStatus) {
+ public void onRunTaskNow(TaskStatus taskStatus) {
}
};
@@ -63,4 +63,4 @@
assertTrue(mTrackerUnderTest.isOnStablePower());
}
-}
+}
\ No newline at end of file