Add reporting of how jobs complete.

In both battery stats and job scheduler statistics, keep track of
the reason jobs are ending.  This can help to identify apps that
are having bad behavior with jobs (in particular, regularly timing
out their job instead of finishing it when done).

Test: manual
Change-Id: Icc12d5e0dc0df1906716e7d995c56d50a9fa148a
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index cf4fd99..a34668c 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -25,6 +25,7 @@
 import java.util.List;
 import java.util.Map;
 
+import android.app.job.JobParameters;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.telephony.SignalStrength;
@@ -238,6 +239,7 @@
     private static final String AGGREGATED_WAKELOCK_DATA = "awl";
     private static final String SYNC_DATA = "sy";
     private static final String JOB_DATA = "jb";
+    private static final String JOB_COMPLETION_DATA = "jbc";
     private static final String KERNEL_WAKELOCK_DATA = "kwl";
     private static final String WAKEUP_REASON_DATA = "wr";
     private static final String NETWORK_DATA = "nt";
@@ -498,6 +500,13 @@
         public abstract ArrayMap<String, ? extends Timer> getJobStats();
 
         /**
+         * Returns statistics about how jobs have completed.
+         *
+         * @return A Map of String job names to completion type -> count mapping.
+         */
+        public abstract ArrayMap<String, SparseIntArray> getJobCompletionStats();
+
+        /**
          * The statistics associated with a particular wake lock.
          */
         public static abstract class Wakelock {
@@ -3557,6 +3566,20 @@
                 }
             }
 
+            final ArrayMap<String, SparseIntArray> completions = u.getJobCompletionStats();
+            for (int ic=completions.size()-1; ic>=0; ic--) {
+                SparseIntArray types = completions.valueAt(ic);
+                if (types != null) {
+                    dumpLine(pw, uid, category, JOB_COMPLETION_DATA,
+                            "\"" + completions.keyAt(ic) + "\"",
+                            types.get(JobParameters.REASON_CANCELED, 0),
+                            types.get(JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED, 0),
+                            types.get(JobParameters.REASON_PREEMPT, 0),
+                            types.get(JobParameters.REASON_TIMEOUT, 0),
+                            types.get(JobParameters.REASON_DEVICE_IDLE, 0));
+                }
+            }
+
             dumpTimer(pw, uid, category, FLASHLIGHT_DATA, u.getFlashlightTurnedOnTimer(),
                     rawRealtime, which);
             dumpTimer(pw, uid, category, CAMERA_DATA, u.getCameraTurnedOnTimer(),
@@ -4979,6 +5002,25 @@
                 uidActivity = true;
             }
 
+            final ArrayMap<String, SparseIntArray> completions = u.getJobCompletionStats();
+            for (int ic=completions.size()-1; ic>=0; ic--) {
+                SparseIntArray types = completions.valueAt(ic);
+                if (types != null) {
+                    pw.print(prefix);
+                    pw.print("    Job Completions ");
+                    pw.print(completions.keyAt(ic));
+                    pw.print(":");
+                    for (int it=0; it<types.size(); it++) {
+                        pw.print(" ");
+                        pw.print(JobParameters.getReasonName(types.keyAt(it)));
+                        pw.print("(");
+                        pw.print(types.valueAt(it));
+                        pw.print("x)");
+                    }
+                    pw.println();
+                }
+            }
+
             uidActivity |= printTimer(pw, sb, u.getFlashlightTurnedOnTimer(), rawRealtime, which,
                     prefix, "Flashlight");
             uidActivity |= printTimer(pw, sb, u.getCameraTurnedOnTimer(), rawRealtime, which,