diff options
author | Matthew Williams <mjwilliams@google.com> | 2014-06-10 14:55:46 +0000 |
---|---|---|
committer | Android Git Automerger <android-git-automerger@android.com> | 2014-06-10 14:55:46 +0000 |
commit | f53c295ae3ccabf1cf5a31c03f64233526e683eb (patch) | |
tree | fb59f194508169e78b3c3197c1c463b193ffea8b | |
parent | 1a2f7a1e29e59e0c9140ac5d3f465dc110c70b48 (diff) | |
parent | 5553aeb2aeee88929b11ee3bbf8e02da1e9368bd (diff) | |
download | frameworks_base-f53c295ae3ccabf1cf5a31c03f64233526e683eb.zip frameworks_base-f53c295ae3ccabf1cf5a31c03f64233526e683eb.tar.gz frameworks_base-f53c295ae3ccabf1cf5a31c03f64233526e683eb.tar.bz2 |
am 3b471117: Merge "Add OnNetworkActive to TaskManager and simplify locking." into lmp-preview-dev
* commit '3b4711176e77640d697e94137e65fa93c8363f5c':
Add OnNetworkActive to TaskManager and simplify locking.
13 files changed, 594 insertions, 337 deletions
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 @@ 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<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 87d57fb..0e660b3 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 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 @@ 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 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 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 <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 @@ public class TaskManagerService extends com.android.server.SystemService 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(); - } - } - } - // Cancel if running. - synchronized (mActiveServices) { - for (TaskServiceContext tsc : mActiveServices) { - if (tsc.getRunningTask().getUid() == uid) { - tsc.cancelExecutingTask(); + List<TaskStatus> 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<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; - } + 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. * <p> @@ -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<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 @@ public class TaskManagerService extends com.android.server.SystemService 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 @@ 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<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 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<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); + } + + /** + * 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); + } - synchronized (mActiveServices) { - for (TaskServiceContext tsc : mActiveServices) { - if (tsc.isAvailable()) { - if (tsc.executeRunnableTask(nextPending)) { - return; + /** + * 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 @@ 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 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.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); + mRunningTask = ts; + mParams = new TaskParams(ts.getTaskId(), ts.getExtras(), this); + mExecutionStartTimeElapsed = SystemClock.elapsedRealtime(); - 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); - - mWakeLock = null; - mRunningTask = null; - mParams = null; - mVerb = -1; - mCancelled.set(false); - service = null; - synchronized (mAvailableLock) { + 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; 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 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.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<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 @@ 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<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 @@ public class TaskStore { } } - 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 @@ 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 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.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 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.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<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 @@ public class ConnectivityController extends StateController { intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); mContext.registerReceiverAsUser( mConnectivityChangedReceiver, UserHandle.ALL, intentFilter, null, null); + 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 maybeStartTrackingTask(TaskStatus taskStatus) { - if (taskStatus.hasConnectivityConstraint() || taskStatus.hasMeteredConstraint()) { - taskStatus.connectivityConstraintSatisfied.set(mConnectivity); - taskStatus.meteredConstraintSatisfied.set(mMetered); - mTrackedTasks.add(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 synchronized void maybeStopTrackingTask(TaskStatus taskStatus) { - mTrackedTasks.remove(taskStatus); + 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 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 @@ 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 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 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 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 @@ 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 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.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,64 +91,37 @@ public class TimeController extends StateController { */ @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 @@ 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<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 @@ 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 <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 @@ 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 |