Fix issue #15681802: Missing RESET:TIME in complete battery histories

But wait, there's more!

- Keep track of sync durations in the aggregated stats.
- Add events for users that are running and in the foreground.
- Rework the activity manager's tracking of stuff using
  battery in the background to be based on proc stats, which
  allows it to be better about determing when it should reset
  its tracking of background work.
- Also add tracking of scheduled job execution, like we are
  doing for syncs.
- And once I started hooking battery stats in to
  JobSchedulerService, I found a few things I couldn't stop myself
  from changing: (1) make it very explicit that it doesn't start
  scheduling jobs until we have reached the point in system boot
  where third party apps are allowed to run, and (2) adjust
  the various for loops to not use iterators.

Change-Id: I69d812e27bcfee9e58a614f0f6b1c7545d7530b1
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 8ef6dd6..79cb60e 100755
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1811,10 +1811,18 @@
                 break;
             }
             case SYSTEM_USER_START_MSG: {
+                mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_USER_RUNNING_START,
+                        Integer.toString(msg.arg1), msg.arg1);
                 mSystemServiceManager.startUser(msg.arg1);
                 break;
             }
             case SYSTEM_USER_CURRENT_MSG: {
+                mBatteryStatsService.noteEvent(
+                        BatteryStats.HistoryItem.EVENT_USER_FOREGROUND_FINISH,
+                        Integer.toString(msg.arg2), msg.arg2);
+                mBatteryStatsService.noteEvent(
+                        BatteryStats.HistoryItem.EVENT_USER_FOREGROUND_START,
+                        Integer.toString(msg.arg1), msg.arg1);
                 mSystemServiceManager.switchUser(msg.arg1);
                 break;
             }
@@ -10168,7 +10176,11 @@
         }
 
         if (goingCallback != null) goingCallback.run();
-        
+
+        mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_USER_RUNNING_START,
+                Integer.toString(mCurrentUserId), mCurrentUserId);
+        mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_USER_FOREGROUND_START,
+                Integer.toString(mCurrentUserId), mCurrentUserId);
         mSystemServiceManager.startUser(mCurrentUserId);
 
         synchronized (this) {
@@ -12441,12 +12453,11 @@
                 pw.print(" lastCachedPss="); pw.println(r.lastCachedPss);
                 pw.print(prefix);
                 pw.print("    ");
-                pw.print("keeping="); pw.print(r.keeping);
-                pw.print(" cached="); pw.print(r.cached);
+                pw.print("cached="); pw.print(r.cached);
                 pw.print(" empty="); pw.print(r.empty);
                 pw.print(" hasAboveClient="); pw.println(r.hasAboveClient);
 
-                if (!r.keeping) {
+                if (r.setProcState >= ActivityManager.PROCESS_STATE_SERVICE) {
                     if (r.lastWakeTime != 0) {
                         long wtime;
                         BatteryStatsImpl stats = service.mBatteryStatsService.getActiveStatistics();
@@ -15200,7 +15211,6 @@
             app.adjSeq = mAdjSeq;
             app.curRawAdj = app.maxAdj;
             app.foregroundActivities = false;
-            app.keeping = true;
             app.curSchedGroup = Process.THREAD_GROUP_DEFAULT;
             app.curProcState = ActivityManager.PROCESS_STATE_PERSISTENT;
             // System processes can do UI, and when they do we want to have
@@ -15224,7 +15234,6 @@
             return (app.curAdj=app.maxAdj);
         }
 
-        app.keeping = false;
         app.systemNoUi = false;
 
         // Determine the importance of the process, starting with most
@@ -15469,9 +15478,6 @@
                         app.adjType = "cch-started-services";
                     }
                 }
-                // Don't kill this process because it is doing work; it
-                // has said it is doing work.
-                app.keeping = true;
             }
             for (int conni = s.connections.size()-1;
                     conni >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
@@ -15561,9 +15567,6 @@
                                 if (!client.cached) {
                                     app.cached = false;
                                 }
-                                if (client.keeping) {
-                                    app.keeping = true;
-                                }
                                 adjType = "service";
                             }
                         }
@@ -15675,7 +15678,6 @@
                         app.adjType = "provider";
                     }
                     app.cached &= client.cached;
-                    app.keeping |= client.keeping;
                     app.adjTypeCode = ActivityManager.RunningAppProcessInfo
                             .REASON_PROVIDER_IN_USE;
                     app.adjSource = client;
@@ -15719,7 +15721,6 @@
                     adj = ProcessList.FOREGROUND_APP_ADJ;
                     schedGroup = Process.THREAD_GROUP_DEFAULT;
                     app.cached = false;
-                    app.keeping = true;
                     app.adjType = "provider";
                     app.adjTarget = cpr.name;
                 }
@@ -15802,9 +15803,6 @@
                 schedGroup = Process.THREAD_GROUP_DEFAULT;
             }
         }
-        if (adj < ProcessList.CACHED_APP_MIN_ADJ) {
-            app.keeping = true;
-        }
 
         // Do final modification to adj.  Everything we do between here and applying
         // the final setAdj must be done in this function, because we will also use
@@ -16026,7 +16024,7 @@
         while (i > 0) {
             i--;
             ProcessRecord app = mLruProcesses.get(i);
-            if (!app.keeping) {
+            if (app.setProcState >= ActivityManager.PROCESS_STATE_HOME) {
                 long wtime;
                 synchronized (stats) {
                     wtime = stats.getProcessWakeTime(app.info.uid,
@@ -16071,7 +16069,7 @@
                             + " during " + realtimeSince);
                     app.baseProcessTracker.reportExcessiveWake(app.pkgList);
                 } else if (doCpuKills && uptimeSince > 0
-                        && ((cputimeUsed*100)/uptimeSince) >= 50) {
+                        && ((cputimeUsed*100)/uptimeSince) >= 25) {
                     synchronized (stats) {
                         stats.reportExcessiveCpuLocked(app.info.uid, app.processName,
                                 uptimeSince, cputimeUsed);
@@ -16087,23 +16085,11 @@
         }
     }
 
-    private final boolean applyOomAdjLocked(ProcessRecord app, boolean wasKeeping,
+    private final boolean applyOomAdjLocked(ProcessRecord app,
             ProcessRecord TOP_APP, boolean doingAll, long now) {
         boolean success = true;
 
         if (app.curRawAdj != app.setRawAdj) {
-            if (wasKeeping && !app.keeping) {
-                // This app is no longer something we want to keep.  Note
-                // its current wake lock time to later know to kill it if
-                // it is not behaving well.
-                BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
-                synchronized (stats) {
-                    app.lastWakeTime = stats.getProcessWakeTime(app.info.uid,
-                            app.pid, SystemClock.elapsedRealtime());
-                }
-                app.lastCpuTime = app.curCpuTime;
-            }
-
             app.setRawAdj = app.curRawAdj;
         }
 
@@ -16192,6 +16178,21 @@
             if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG,
                     "Proc state change of " + app.processName
                     + " to " + app.curProcState);
+            boolean setImportant = app.setProcState < ActivityManager.PROCESS_STATE_SERVICE;
+            boolean curImportant = app.curProcState < ActivityManager.PROCESS_STATE_SERVICE;
+            if (setImportant && !curImportant) {
+                // This app is no longer something we consider important enough to allow to
+                // use arbitrary amounts of battery power.  Note
+                // its current wake lock time to later know to kill it if
+                // it is not behaving well.
+                BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
+                synchronized (stats) {
+                    app.lastWakeTime = stats.getProcessWakeTime(app.info.uid,
+                            app.pid, SystemClock.elapsedRealtime());
+                }
+                app.lastCpuTime = app.curCpuTime;
+
+            }
             app.setProcState = app.curProcState;
             if (app.setProcState >= ActivityManager.PROCESS_STATE_HOME) {
                 app.notCachedSinceIdle = false;
@@ -16268,11 +16269,9 @@
             return false;
         }
 
-        final boolean wasKeeping = app.keeping;
-
         computeOomAdjLocked(app, cachedAdj, TOP_APP, doingAll, now);
 
-        return applyOomAdjLocked(app, wasKeeping, TOP_APP, doingAll, now);
+        return applyOomAdjLocked(app, TOP_APP, doingAll, now);
     }
 
     final void updateProcessForegroundLocked(ProcessRecord proc, boolean isForeground,
@@ -16428,7 +16427,6 @@
             ProcessRecord app = mLruProcesses.get(i);
             if (!app.killedByAm && app.thread != null) {
                 app.procStateChanged = false;
-                final boolean wasKeeping = app.keeping;
                 computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now);
 
                 // If we haven't yet assigned the final cached adj
@@ -16483,7 +16481,7 @@
                     }
                 }
 
-                applyOomAdjLocked(app, wasKeeping, TOP_APP, true, now);
+                applyOomAdjLocked(app, TOP_APP, true, now);
 
                 // Count the number of process types.
                 switch (app.curProcState) {
@@ -17127,7 +17125,8 @@
                 }
 
                 if (foreground) {
-                    mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_CURRENT_MSG, userId));
+                    mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_CURRENT_MSG, userId,
+                            oldUserId));
                     mHandler.removeMessages(REPORT_USER_SWITCH_MSG);
                     mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
                     mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_MSG,
@@ -17502,6 +17501,9 @@
                             }
                             uss.mState = UserStartedState.STATE_SHUTDOWN;
                         }
+                        mBatteryStatsService.noteEvent(
+                                BatteryStats.HistoryItem.EVENT_USER_RUNNING_FINISH,
+                                Integer.toString(userId), userId);
                         mSystemServiceManager.stopUser(userId);
                         broadcastIntentLocked(null, null, shutdownIntent,
                                 null, shutdownReceiver, 0, null, null, null, AppOpsManager.OP_NONE,
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index da444f9..ac19bde 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -186,6 +186,34 @@
         }
     }
 
+    public void noteSyncStart(String name, int uid) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteSyncStartLocked(name, uid);
+        }
+    }
+
+    public void noteSyncFinish(String name, int uid) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteSyncFinishLocked(name, uid);
+        }
+    }
+
+    public void noteJobStart(String name, int uid) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteJobStartLocked(name, uid);
+        }
+    }
+
+    public void noteJobFinish(String name, int uid) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteJobFinishLocked(name, uid);
+        }
+    }
+
     public void noteStartWakelock(int uid, int pid, String name, String historyName, int type,
             boolean unimportantForLogging) {
         enforceCallingPermission();
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 2f25bd4..a20be73 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -83,7 +83,6 @@
     int pssProcState = -1;      // The proc state we are currently requesting pss for
     boolean serviceb;           // Process currently is on the service B list
     boolean serviceHighRam;     // We are forcing to service B list due to its RAM use
-    boolean keeping;            // Actively running code so don't kill due to that?
     boolean setIsForeground;    // Running foreground UI when last set?
     boolean notCachedSinceIdle; // Has this process not been in a cached state since last idle?
     boolean hasClientActivities;  // Are there any client services with activities?
@@ -225,8 +224,7 @@
                 pw.print(" lruSeq="); pw.print(lruSeq);
                 pw.print(" lastPss="); pw.print(lastPss);
                 pw.print(" lastCachedPss="); pw.println(lastCachedPss);
-        pw.print(prefix); pw.print("keeping="); pw.print(keeping);
-                pw.print(" cached="); pw.print(cached);
+        pw.print(prefix); pw.print("cached="); pw.print(cached);
                 pw.print(" empty="); pw.println(empty);
         if (serviceb) {
             pw.print(prefix); pw.print("serviceb="); pw.print(serviceb);
@@ -275,16 +273,15 @@
         if (hasStartedServices) {
             pw.print(prefix); pw.print("hasStartedServices="); pw.println(hasStartedServices);
         }
-        if (!keeping) {
+        if (setProcState >= ActivityManager.PROCESS_STATE_SERVICE) {
             long wtime;
             synchronized (mBatteryStats) {
                 wtime = mBatteryStats.getProcessWakeTime(info.uid,
                         pid, SystemClock.elapsedRealtime());
             }
-            long timeUsed = wtime - lastWakeTime;
             pw.print(prefix); pw.print("lastWakeTime="); pw.print(lastWakeTime);
                     pw.print(" timeUsed=");
-                    TimeUtils.formatDuration(timeUsed, pw); pw.println("");
+                    TimeUtils.formatDuration(wtime-lastWakeTime, pw); pw.println("");
             pw.print(prefix); pw.print("lastCpuTime="); pw.print(lastCpuTime);
                     pw.print(" timeUsed=");
                     TimeUtils.formatDuration(curCpuTime-lastCpuTime, pw); pw.println("");
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 1b40cdf..08d6fc9 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -1212,8 +1212,7 @@
             } else {
                 try {
                     mEventName = mSyncOperation.wakeLockName();
-                    mBatteryStats.noteEvent(BatteryStats.HistoryItem.EVENT_SYNC_START,
-                            mEventName, mSyncAdapterUid);
+                    mBatteryStats.noteSyncStart(mEventName, mSyncAdapterUid);
                 } catch (RemoteException e) {
                 }
             }
@@ -1232,8 +1231,7 @@
                 mBound = false;
                 mContext.unbindService(this);
                 try {
-                    mBatteryStats.noteEvent(BatteryStats.HistoryItem.EVENT_SYNC_FINISH,
-                            mEventName, mSyncAdapterUid);
+                    mBatteryStats.noteSyncFinish(mEventName, mSyncAdapterUid);
                 } catch (RemoteException e) {
                 }
             }
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 7f8b232..caae9f5 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -35,16 +35,20 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
+import android.os.BatteryStats;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.util.ArraySet;
 import android.util.Slog;
 import android.util.SparseArray;
 
+import com.android.internal.app.IBatteryStats;
 import com.android.server.job.controllers.BatteryController;
 import com.android.server.job.controllers.ConnectivityController;
 import com.android.server.job.controllers.IdleController;
@@ -52,8 +56,6 @@
 import com.android.server.job.controllers.StateController;
 import com.android.server.job.controllers.TimeController;
 
-import java.util.LinkedList;
-
 /**
  * Responsible for taking jobs representing work to be performed by a client app, and determining
  * based on the criteria specified when that job should be run against the client application's
@@ -74,7 +76,7 @@
     private static final int MAX_JOB_CONTEXTS_COUNT = 3;
     static final String TAG = "JobManagerService";
     /** Master list of jobs. */
-    private final JobStore mJobs;
+    final JobStore mJobs;
 
     static final int MSG_JOB_EXPIRED = 0;
     static final int MSG_CHECK_JOB = 1;
@@ -84,33 +86,41 @@
      * Minimum # of idle jobs that must be ready in order to force the JMS to schedule things
      * early.
      */
-    private static final int MIN_IDLE_COUNT = 1;
+    static final int MIN_IDLE_COUNT = 1;
     /**
      * Minimum # of connectivity jobs that must be ready in order to force the JMS to schedule
      * things early.
      */
-    private static final int MIN_CONNECTIVITY_COUNT = 2;
+    static final int MIN_CONNECTIVITY_COUNT = 2;
     /**
      * Minimum # of jobs (with no particular constraints) for which the JMS will be happy running
      * some work early.
      */
-    private static final int MIN_READY_JOBS_COUNT = 4;
+    static final int MIN_READY_JOBS_COUNT = 4;
 
     /**
      * Track Services that have currently active or pending jobs. The index is provided by
      * {@link JobStatus#getServiceToken()}
      */
-    private final List<JobServiceContext> mActiveServices = new LinkedList<JobServiceContext>();
+    final List<JobServiceContext> mActiveServices = new ArrayList<JobServiceContext>();
     /** List of controllers that will notify this service of updates to jobs. */
-    private List<StateController> mControllers;
+    List<StateController> mControllers;
     /**
      * Queue of pending jobs. The JobServiceContext class will receive jobs from this list
      * when ready to execute them.
      */
-    private final LinkedList<JobStatus> mPendingJobs = new LinkedList<JobStatus>();
+    final ArrayList<JobStatus> mPendingJobs = new ArrayList<JobStatus>();
 
-    private final JobHandler mHandler;
-    private final JobSchedulerStub mJobSchedulerStub;
+    final JobHandler mHandler;
+    final JobSchedulerStub mJobSchedulerStub;
+
+    IBatteryStats mBatteryStats;
+
+    /**
+     * Set to true once we are allowed to run third party apps.
+     */
+    boolean mReadyToRock;
+
     /**
      * 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.
@@ -152,7 +162,9 @@
     public List<JobInfo> getPendingJobs(int uid) {
         ArrayList<JobInfo> outList = new ArrayList<JobInfo>();
         synchronized (mJobs) {
-            for (JobStatus job : mJobs.getJobs()) {
+            ArraySet<JobStatus> jobs = mJobs.getJobs();
+            for (int i=0; i<jobs.size(); i++) {
+                JobStatus job = jobs.valueAt(i);
                 if (job.getUid() == uid) {
                     outList.add(job.getJob());
                 }
@@ -164,7 +176,8 @@
     private void cancelJobsForUser(int userHandle) {
         synchronized (mJobs) {
             List<JobStatus> jobsForUser = mJobs.getJobsByUser(userHandle);
-            for (JobStatus toRemove : jobsForUser) {
+            for (int i=0; i<jobsForUser.size(); i++) {
+                JobStatus toRemove = jobsForUser.get(i);
                 if (DEBUG) {
                     Slog.d(TAG, "Cancelling: " + toRemove);
                 }
@@ -183,7 +196,8 @@
         // Remove from master list.
         synchronized (mJobs) {
             List<JobStatus> jobsForUid = mJobs.getJobsByUid(uid);
-            for (JobStatus toRemove : jobsForUid) {
+            for (int i=0; i<jobsForUid.size(); i++) {
+                JobStatus toRemove = jobsForUid.get(i);
                 if (DEBUG) {
                     Slog.d(TAG, "Cancelling: " + toRemove);
                 }
@@ -230,7 +244,7 @@
     public JobSchedulerService(Context context) {
         super(context);
         // Create the controllers.
-        mControllers = new LinkedList<StateController>();
+        mControllers = new ArrayList<StateController>();
         mControllers.add(ConnectivityController.get(this));
         mControllers.add(TimeController.get(this));
         mControllers.add(IdleController.get(this));
@@ -238,11 +252,6 @@
 
         mHandler = new JobHandler(context.getMainLooper());
         mJobSchedulerStub = new JobSchedulerStub();
-        // Create the "runners".
-        for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
-            mActiveServices.add(
-                    new JobServiceContext(this, context.getMainLooper()));
-        }
         mJobs = JobStore.initAndGet(this);
     }
 
@@ -262,6 +271,29 @@
             final IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_REMOVED);
             getContext().registerReceiverAsUser(
                     mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
+        } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
+            synchronized (mJobs) {
+                // Let's go!
+                mReadyToRock = true;
+                mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService(
+                        BatteryStats.SERVICE_NAME));
+                // Create the "runners".
+                for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
+                    mActiveServices.add(
+                            new JobServiceContext(this, mBatteryStats,
+                                    getContext().getMainLooper()));
+                }
+                // Attach jobs to their controllers.
+                ArraySet<JobStatus> jobs = mJobs.getJobs();
+                for (int i=0; i<jobs.size(); i++) {
+                    JobStatus job = jobs.valueAt(i);
+                    for (int j=0; j<mControllers.size(); j++) {
+                        mControllers.get(i).maybeStartTrackingJob(job);
+                    }
+                }
+                // GO GO GO!
+                mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
+            }
         }
     }
 
@@ -272,14 +304,19 @@
      */
     private void startTrackingJob(JobStatus jobStatus) {
         boolean update;
+        boolean rocking;
         synchronized (mJobs) {
             update = mJobs.add(jobStatus);
+            rocking = mReadyToRock;
         }
-        for (StateController controller : mControllers) {
-            if (update) {
-                controller.maybeStopTrackingJob(jobStatus);
+        if (rocking) {
+            for (int i=0; i<mControllers.size(); i++) {
+                StateController controller = mControllers.get(i);
+                if (update) {
+                    controller.maybeStopTrackingJob(jobStatus);
+                }
+                controller.maybeStartTrackingJob(jobStatus);
             }
-            controller.maybeStartTrackingJob(jobStatus);
         }
     }
 
@@ -289,12 +326,15 @@
      */
     private boolean stopTrackingJob(JobStatus jobStatus) {
         boolean removed;
+        boolean rocking;
         synchronized (mJobs) {
             // Remove from store as well as controllers.
             removed = mJobs.remove(jobStatus);
+            rocking = mReadyToRock;
         }
-        if (removed) {
-            for (StateController controller : mControllers) {
+        if (removed && rocking) {
+            for (int i=0; i<mControllers.size(); i++) {
+                StateController controller = mControllers.get(i);
                 controller.maybeStopTrackingJob(jobStatus);
             }
         }
@@ -302,7 +342,8 @@
     }
 
     private boolean stopJobOnServiceContextLocked(JobStatus job) {
-        for (JobServiceContext jsc : mActiveServices) {
+        for (int i=0; i<mActiveServices.size(); i++) {
+            JobServiceContext jsc = mActiveServices.get(i);
             final JobStatus executing = jsc.getRunningJob();
             if (executing != null && executing.matches(job.getUid(), job.getJobId())) {
                 jsc.cancelExecutingJob();
@@ -318,7 +359,8 @@
      * is pending.
      */
     private boolean isCurrentlyActiveLocked(JobStatus job) {
-        for (JobServiceContext serviceContext : mActiveServices) {
+        for (int i=0; i<mActiveServices.size(); i++) {
+            JobServiceContext serviceContext = mActiveServices.get(i);
             final JobStatus running = serviceContext.getRunningJob();
             if (running != null && running.matches(job.getUid(), job.getJobId())) {
                 return true;
@@ -431,8 +473,13 @@
      */
     @Override
     public void onControllerStateChanged() {
-        // Post a message to to run through the list of jobs and start/stop any that are eligible.
-        mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
+        synchronized (mJobs) {
+            if (mReadyToRock) {
+                // Post a message to to run through the list of jobs and start/stop any that
+                // are eligible.
+                mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
+            }
+        }
     }
 
     @Override
@@ -449,7 +496,8 @@
     @Override
     public void onJobMapReadFinished(List<JobStatus> jobs) {
         synchronized (mJobs) {
-            for (JobStatus js : jobs) {
+            for (int i=0; i<jobs.size(); i++) {
+                JobStatus js = jobs.get(i);
                 if (mJobs.containsJobIdForUid(js.getJobId(), js.getUid())) {
                     // An app with BOOT_COMPLETED *might* have decided to reschedule their job, in
                     // the same amount of time it took us to read it from disk. If this is the case
@@ -495,7 +543,9 @@
          */
         private void queueReadyJobsForExecutionH() {
             synchronized (mJobs) {
-                for (JobStatus job : mJobs.getJobs()) {
+                ArraySet<JobStatus> jobs = mJobs.getJobs();
+                for (int i=0; i<jobs.size(); i++) {
+                    JobStatus job = jobs.valueAt(i);
                     if (isReadyToBeExecutedLocked(job)) {
                         mPendingJobs.add(job);
                     } else if (isReadyToBeCancelledLocked(job)) {
@@ -520,7 +570,9 @@
                 int backoffCount = 0;
                 int connectivityCount = 0;
                 List<JobStatus> runnableJobs = new ArrayList<JobStatus>();
-                for (JobStatus job : mJobs.getJobs()) {
+                ArraySet<JobStatus> jobs = mJobs.getJobs();
+                for (int i=0; i<jobs.size(); i++) {
+                    JobStatus job = jobs.valueAt(i);
                     if (isReadyToBeExecutedLocked(job)) {
                         if (job.getNumFailures() > 0) {
                             backoffCount++;
@@ -539,8 +591,8 @@
                 if (backoffCount > 0 || idleCount >= MIN_IDLE_COUNT ||
                         connectivityCount >= MIN_CONNECTIVITY_COUNT ||
                         runnableJobs.size() >= MIN_READY_JOBS_COUNT) {
-                    for (JobStatus job : runnableJobs) {
-                        mPendingJobs.add(job);
+                    for (int i=0; i<runnableJobs.size(); i++) {
+                        mPendingJobs.add(runnableJobs.get(i));
                     }
                 }
             }
@@ -576,7 +628,8 @@
                 while (it.hasNext()) {
                     JobStatus nextPending = it.next();
                     JobServiceContext availableContext = null;
-                    for (JobServiceContext jsc : mActiveServices) {
+                    for (int i=0; i<mActiveServices.size(); i++) {
+                        JobServiceContext jsc = mActiveServices.get(i);
                         final JobStatus running = jsc.getRunningJob();
                         if (running != null && running.matches(nextPending.getUid(),
                                 nextPending.getJobId())) {
@@ -737,25 +790,28 @@
         synchronized (mJobs) {
             pw.println("Registered jobs:");
             if (mJobs.size() > 0) {
-                for (JobStatus job : mJobs.getJobs()) {
+                ArraySet<JobStatus> jobs = mJobs.getJobs();
+                for (int i=0; i<jobs.size(); i++) {
+                    JobStatus job = jobs.valueAt(i);
                     job.dump(pw, "  ");
                 }
             } else {
                 pw.println();
                 pw.println("No jobs scheduled.");
             }
-            for (StateController controller : mControllers) {
+            for (int i=0; i<mControllers.size(); i++) {
                 pw.println();
-                controller.dumpControllerState(pw);
+                mControllers.get(i).dumpControllerState(pw);
             }
             pw.println();
             pw.println("Pending");
-            for (JobStatus jobStatus : mPendingJobs) {
-                pw.println(jobStatus.hashCode());
+            for (int i=0; i<mPendingJobs.size(); i++) {
+                pw.println(mPendingJobs.get(i).hashCode());
             }
             pw.println();
             pw.println("Active jobs:");
-            for (JobServiceContext jsc : mActiveServices) {
+            for (int i=0; i<mActiveServices.size(); i++) {
+                JobServiceContext jsc = mActiveServices.get(i);
                 if (jsc.isAvailable()) {
                     continue;
                 } else {
@@ -765,6 +821,8 @@
                             "timeout: " + jsc.getTimeoutElapsed());
                 }
             }
+            pw.println();
+            pw.print("mReadyToRock="); pw.println(mReadyToRock);
         }
         pw.println();
     }
diff --git a/services/core/java/com/android/server/job/JobServiceContext.java b/services/core/java/com/android/server/job/JobServiceContext.java
index 534faba3..eaf5480 100644
--- a/services/core/java/com/android/server/job/JobServiceContext.java
+++ b/services/core/java/com/android/server/job/JobServiceContext.java
@@ -39,6 +39,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.IBatteryStats;
 import com.android.server.job.controllers.JobStatus;
 
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -57,8 +58,6 @@
     private static final long EXECUTING_TIMESLICE_MILLIS = 60 * 1000;
     /** Amount of time the JobScheduler 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. */
-    private static final String JS_WAKELOCK_PREFIX = "*job*/";
 
     private static final String[] VERB_STRINGS = {
             "VERB_STARTING", "VERB_EXECUTING", "VERB_STOPPING", "VERB_PENDING"
@@ -87,6 +86,7 @@
     private final JobCompletedListener mCompletedListener;
     /** Used for service binding, etc. */
     private final Context mContext;
+    private final IBatteryStats mBatteryStats;
     private PowerManager.WakeLock mWakeLock;
 
     // Execution state.
@@ -109,13 +109,15 @@
     /** Track when job will timeout. */
     private long mTimeoutElapsed;
 
-    JobServiceContext(JobSchedulerService service, Looper looper) {
-        this(service.getContext(), service, looper);
+    JobServiceContext(JobSchedulerService service, IBatteryStats batteryStats, Looper looper) {
+        this(service.getContext(), batteryStats, service, looper);
     }
 
     @VisibleForTesting
-    JobServiceContext(Context context, JobCompletedListener completedListener, Looper looper) {
+    JobServiceContext(Context context, IBatteryStats batteryStats,
+            JobCompletedListener completedListener, Looper looper) {
         mContext = context;
+        mBatteryStats = batteryStats;
         mCallbackHandler = new JobServiceHandler(looper);
         mCompletedListener = completedListener;
         mAvailable = true;
@@ -152,6 +154,11 @@
                 mExecutionStartTimeElapsed = 0L;
                 return false;
             }
+            try {
+                mBatteryStats.noteJobStart(job.getName(), job.getUid());
+            } catch (RemoteException e) {
+                // Whatever.
+            }
             mAvailable = false;
             return true;
         }
@@ -228,8 +235,7 @@
         mCallbackHandler.removeMessages(MSG_TIMEOUT);
         final PowerManager pm =
                 (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
-        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
-                JS_WAKELOCK_PREFIX + mRunningJob.getServiceComponent().getPackageName());
+        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, mRunningJob.getTag());
         mWakeLock.setWorkSource(new WorkSource(mRunningJob.getUid()));
         mWakeLock.setReferenceCounted(false);
         mWakeLock.acquire();
@@ -483,6 +489,11 @@
             removeMessages(MSG_TIMEOUT);
             mCompletedListener.onJobCompleted(mRunningJob, reschedule);
             synchronized (mLock) {
+                try {
+                    mBatteryStats.noteJobFinish(mRunningJob.getName(), mRunningJob.getUid());
+                } catch (RemoteException e) {
+                    // Whatever.
+                }
                 mWakeLock.release();
                 mContext.unbindService(JobServiceContext.this);
                 mWakeLock = null;
diff --git a/services/core/java/com/android/server/job/JobStore.java b/services/core/java/com/android/server/job/JobStore.java
index 8736980..48312b0 100644
--- a/services/core/java/com/android/server/job/JobStore.java
+++ b/services/core/java/com/android/server/job/JobStore.java
@@ -136,7 +136,8 @@
      * Whether this jobStatus object already exists in the JobStore.
      */
     public boolean containsJobIdForUid(int jobId, int uId) {
-        for (JobStatus ts : mJobSet) {
+        for (int i=mJobSet.size()-1; i>=0; i--) {
+            JobStatus ts = mJobSet.valueAt(i);
             if (ts.getUid() == uId && ts.getJobId() == jobId) {
                 return true;
             }
@@ -267,7 +268,8 @@
             List<JobStatus> mStoreCopy = new ArrayList<JobStatus>();
             synchronized (JobStore.this) {
                 // Copy over the jobs so we can release the lock before writing.
-                for (JobStatus jobStatus : mJobSet) {
+                for (int i=0; i<mJobSet.size(); i++) {
+                    JobStatus jobStatus = mJobSet.valueAt(i);
                     JobStatus copy = new JobStatus(jobStatus.getJob(), jobStatus.getUid(),
                             jobStatus.getEarliestRunTime(), jobStatus.getLatestRunTimeElapsed());
                     mStoreCopy.add(copy);
@@ -290,7 +292,8 @@
 
                 out.startTag(null, "job-info");
                 out.attribute(null, "version", Integer.toString(JOBS_FILE_VERSION));
-                for (JobStatus jobStatus : jobList) {
+                for (int i=0; i<jobList.size(); i++) {
+                    JobStatus jobStatus = jobList.get(i);
                     if (DEBUG) {
                         Slog.d(TAG, "Saving job " + jobStatus.getJobId());
                     }
diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java
index 9ee2869..652d8f8 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -42,6 +42,8 @@
 
     final JobInfo job;
     final int uId;
+    final String name;
+    final String tag;
 
     // Constraints.
     final AtomicBoolean chargingConstraintSatisfied = new AtomicBoolean();
@@ -72,6 +74,8 @@
     private JobStatus(JobInfo job, int uId, int numFailures) {
         this.job = job;
         this.uId = uId;
+        this.name = job.getService().flattenToShortString();
+        this.tag = "*job*/" + this.name;
         this.numFailures = numFailures;
     }
 
@@ -140,6 +144,14 @@
         return uId;
     }
 
+    public String getName() {
+        return name;
+    }
+
+    public String getTag() {
+        return tag;
+    }
+
     public PersistableBundle getExtras() {
         return job.getExtras();
     }