am 3b471117: Merge "Add OnNetworkActive to TaskManager and simplify locking." into lmp-preview-dev

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