From effacfa75bd9c2ebc889a7bc4f002c07f82f4c31 Mon Sep 17 00:00:00 2001 From: Matthew Williams Date: Thu, 5 Jun 2014 20:56:40 -0700 Subject: [PATCH] Add OnNetworkActive to TaskManager and simplify locking. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug: 14993295 Hooks up a ConnectivityManager.NetworkActiveListener.      Previously was using 3 locks where 1 would suffice, negligible to no performance issues b/c most of the previous locks were nested. Added OnNetworkActiveListener so that the ConnectivityController can kick of tasks when it knows that the network is active. Fixed some bugs that stopped timecontroller from executing tasks. Requesting a retry will not add a deadline to that task. However backed-off tasks are run as soon as they are ready, subject to their constraints. Added receiver to listen for package removal and user removal, which will delete any outstanding jobs for that uid. Change-Id: I4c4523af558b927f19b8a215878fcc7198abb93f --- core/java/android/app/TaskManagerImpl.java | 24 +- core/java/android/app/task/Task.java | 11 +- .../server/task/StateChangedListener.java | 2 +- .../server/task/TaskManagerService.java | 345 +++++++++++------- .../server/task/TaskServiceContext.java | 102 +++--- .../com/android/server/task/TaskStore.java | 78 ++-- .../task/controllers/BatteryController.java | 6 + .../controllers/ConnectivityController.java | 133 +++++-- .../task/controllers/IdleController.java | 6 + .../task/controllers/StateController.java | 4 + .../server/task/controllers/TaskStatus.java | 41 ++- .../task/controllers/TimeController.java | 179 +++++---- .../controllers/BatteryControllerTest.java | 4 +- 13 files changed, 596 insertions(+), 339 deletions(-) diff --git a/core/java/android/app/TaskManagerImpl.java b/core/java/android/app/TaskManagerImpl.java index f42839e36793e..fe29fb791bb0b 100644 --- a/core/java/android/app/TaskManagerImpl.java +++ b/core/java/android/app/TaskManagerImpl.java @@ -20,6 +20,7 @@ package android.app; 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 @@ public class TaskManagerImpl extends TaskManager { @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 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 87d57fb413192..0e660b337ca14 100644 --- a/core/java/android/app/task/Task.java +++ b/core/java/android/app/task/Task.java @@ -48,6 +48,11 @@ public class Task implements Parcelable { * @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 @@ public class Task implements Parcelable { 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 @@ public class Task implements Parcelable { 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 class Task implements Parcelable { 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 b1a4636c831eb..ab5cc7c0f8822 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 @@ public interface StateChangedListener { * 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 27af0ed7c9193..a6b68d921d102 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 java.util.List; 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.Looper; 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 @@ import java.util.LinkedList; * 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 @@ public class TaskManagerService extends com.android.server.SystemService /** 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 @@ public class TaskManagerService extends com.android.server.SystemService 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 TaskManager#RESULT_* 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 @@ public class TaskManagerService extends com.android.server.SystemService return outList; } + private void cancelTasksForUser(int userHandle) { + synchronized (mTasks) { + List 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 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(); + List tasksForUid = mTasks.getTasksByUid(uid); + for (TaskStatus toRemove : tasksForUid) { + if (DEBUG) { + Slog.d(TAG, "Cancelling: " + toRemove); } + cancelTaskLocked(toRemove); } } } @@ -179,34 +197,24 @@ public class TaskManagerService extends com.android.server.SystemService * @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; - } - } - synchronized (mPendingTasks) { - Iterator 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; - } + toCancel = mTasks.getTaskByUidAndTaskId(uid, taskId); + if (toCancel != null) { + cancelTaskLocked(toCancel); } } } + private void cancelTaskLocked(TaskStatus cancelled) { + // Remove from store. + stopTrackingTask(cancelled); + // Remove from pending queue. + mPendingTasks.remove(cancelled); + // Cancel if running. + stopTaskOnServiceContextLocked(cancelled); + } + /** * Initializes the system service. *

@@ -218,7 +226,13 @@ public class TaskManagerService extends com.android.server.SystemService */ public TaskManagerService(Context context) { super(context); - mTasks = TaskStore.initAndGet(this); + // Create the controllers. + mControllers = new LinkedList(); + 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 @@ public class TaskManagerService extends com.android.server.SystemService mActiveServices.add( new TaskServiceContext(this, context.getMainLooper())); } - // Create the controllers. - mControllers = new LinkedList(); - 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 @@ public class TaskManagerService extends com.android.server.SystemService 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 @@ public class TaskManagerService extends com.android.server.SystemService 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 @@ public class TaskManagerService extends com.android.server.SystemService * @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 @@ public class TaskManagerService extends com.android.server.SystemService 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 @@ public class TaskManagerService extends com.android.server.SystemService */ @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 @@ public class TaskManagerService extends com.android.server.SystemService } @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 class TaskManagerService extends com.android.server.SystemService public void onTaskMapReadFinished(List 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 class TaskManagerService extends com.android.server.SystemService 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 @@ public class TaskManagerService extends com.android.server.SystemService 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 @@ public class TaskManagerService extends com.android.server.SystemService 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 @@ public class TaskManagerService extends com.android.server.SystemService * 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 runnableTasks = new ArrayList(); 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 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 @@ public class TaskManagerService extends com.android.server.SystemService 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 @@ public class TaskManagerService extends com.android.server.SystemService // 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 @@ public class TaskManagerService extends com.android.server.SystemService long ident = Binder.clearCallingIdentity(); try { - TaskManagerService.this.cancelTaskForUid(uid); + TaskManagerService.this.cancelTasksForUid(uid); } finally { Binder.restoreCallingIdentity(ident); } @@ -639,16 +702,36 @@ public class TaskManagerService extends com.android.server.SystemService 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 686ca38e064fb..a21de88c6917e 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.Looper; 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 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon 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 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon /** 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 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon 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 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon * @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 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon 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 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon 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 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon break; case MSG_SHUTDOWN_EXECUTION: closeAndCleanupTaskH(true /* needsReschedule */); + break; default: Log.e(TAG, "Unrecognised message: " + message); } @@ -423,7 +440,7 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon 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 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon service.stopTask(mParams); } catch (RemoteException e) { Log.e(TAG, "Error sending onStopTask to client.", e); - closeAndCleanupTaskH(false); + closeAndCleanupTaskH(false /* reschedule */); } } @@ -464,17 +481,17 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon */ 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 @@ public class TaskServiceContext extends ITaskCallback.Stub implements ServiceCon } 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 6bb00b1fac42b..9e095e785a0fc 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.Environment; 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 @@ import org.xmlpull.v1.XmlSerializer; * - 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 @@ public class TaskStore { @VisibleForTesting public static TaskStore initAndGetForTesting(Context context, File dataDir, - TaskMapReadFinishedListener callback) { + TaskMapReadFinishedListener callback) { return new TaskStore(context, dataDir, callback); } @@ -126,14 +126,22 @@ public class TaskStore { 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 @@ public class TaskStore { maybeWriteStatusToDiskAsync(); } + public List getTasksByUser(int userHandle) { + List matchingTasks = new ArrayList(); + Iterator 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 getTasksByUid(int uid) { + List matchingTasks = new ArrayList(); Iterator 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 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 @@ public class TaskStore { */ 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 class TaskStore { public void run() { try { List 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 @@ public class TaskStore { } } - private List readTaskMapImpl() throws XmlPullParserException, IOException { - FileInputStream fis = mTasksFile.openRead(); + private List readTaskMapImpl(FileInputStream fis) throws XmlPullParserException, IOException { XmlPullParser parser = Xml.newPullParser(); parser.setInput(fis, null); @@ -537,10 +545,10 @@ public class TaskStore { } } 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 @@ public class TaskStore { 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 @@ public class TaskStore { 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 4727e9a6b8317..443527f47ceee 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.BatteryService; 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 @@ public class BatteryController extends StateController { } } } + + @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 48194604d2cde..c1ab0f091bfff 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.Intent; 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 @@ import java.util.List; * 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 mTrackedTasks = new LinkedList(); 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 @@ public class ConnectivityController extends StateController { 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 @@ public class ConnectivityController extends StateController { // 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 @@ public class ConnectivityController extends StateController { 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 @@ public class ConnectivityController extends StateController { } } }; -} + + @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 c47facae2df56..e749b0022198f 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 @@ public class IdleController extends StateController { } } } + + @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 cbe6ff8d9cdd2..a7f52f577ab55 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 android.content.Context; 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 class StateController { */ 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 33670a1220268..a2867379a6dbf 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 @@ import java.util.concurrent.atomic.AtomicBoolean; * @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 @@ public class TaskStatus { 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 @@ public class TaskStatus { 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 @@ public class TaskStatus { return task.getNetworkCapabilities() == Task.NetworkType.ANY; } - public boolean hasMeteredConstraint() { + public boolean hasUnmeteredConstraint() { return task.getNetworkCapabilities() == Task.NetworkType.UNMETERED; } @@ -161,11 +161,11 @@ public class TaskStatus { } 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 @@ public class TaskStatus { 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 @@ public class TaskStatus { 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 8c6dd2715ce8f..b75036ce6331b 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.Context; 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 @@ import java.util.ListIterator; 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 @@ public class TimeController extends StateController { 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,63 +91,36 @@ public class TimeController extends StateController { */ @Override public synchronized void maybeStartTrackingTask(TaskStatus task) { - if (task.hasTimingDelayConstraint()) { + if (task.hasTimingDelayConstraint() || task.hasDeadlineConstraint()) { + maybeStopTrackingTask(task); ListIterator 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 will not 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 will 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 @@ -155,17 +134,6 @@ public class TimeController extends StateController { 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 @@ public class TimeController extends StateController { } /** - * 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 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 it = mTrackedTasks.iterator(); while (it.hasNext()) { final TaskStatus ts = it.next(); @@ -212,31 +183,107 @@ public class TimeController extends StateController { 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 will not 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 will 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 e617caf0cfa10..6617a050d321b 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 @@ public class BatteryControllerTest extends AndroidTestCase { } @Override - public void onTaskDeadlineExpired(TaskStatus taskStatus) { + public void onRunTaskNow(TaskStatus taskStatus) { } }; @@ -63,4 +63,4 @@ public class BatteryControllerTest extends AndroidTestCase { assertTrue(mTrackerUnderTest.isOnStablePower()); } -} +} \ No newline at end of file