Merge "Fix issue #28477006: Add small event log to job scheduler" into nyc-dev
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index 6a08191..6b0ead4 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -309,8 +309,8 @@
     private static final int EVENT_DEEP_IDLE = 4;
     private static final int EVENT_DEEP_MAINTENANCE = 5;
 
-    private int[] mEventCmds = new int[EVENT_BUFFER_SIZE];
-    private long[] mEventTimes = new long[EVENT_BUFFER_SIZE];
+    private final int[] mEventCmds = new int[EVENT_BUFFER_SIZE];
+    private final long[] mEventTimes = new long[EVENT_BUFFER_SIZE];
 
     private void addEvent(int cmd) {
         if (mEventCmds[0] != cmd) {
diff --git a/services/core/java/com/android/server/job/JobCompletedListener.java b/services/core/java/com/android/server/job/JobCompletedListener.java
index a7af9cd..655abd7 100644
--- a/services/core/java/com/android/server/job/JobCompletedListener.java
+++ b/services/core/java/com/android/server/job/JobCompletedListener.java
@@ -23,10 +23,9 @@
  * {@link com.android.server.job.JobSchedulerService}.
  */
 public interface JobCompletedListener {
-
     /**
      * Callback for when a job is completed.
      * @param needsReschedule Whether the implementing class should reschedule this job.
      */
-    public void onJobCompleted(JobStatus jobStatus, boolean needsReschedule);
+    void onJobCompleted(JobStatus jobStatus, boolean needsReschedule);
 }
diff --git a/services/core/java/com/android/server/job/JobPackageTracker.java b/services/core/java/com/android/server/job/JobPackageTracker.java
index e5a2095..4ba9dae 100644
--- a/services/core/java/com/android/server/job/JobPackageTracker.java
+++ b/services/core/java/com/android/server/job/JobPackageTracker.java
@@ -33,6 +33,28 @@
     // Number of historical data sets we keep.
     static final int NUM_HISTORY = 5;
 
+    private static final int EVENT_BUFFER_SIZE = 50;
+
+    public static final int EVENT_NULL = 0;
+    public static final int EVENT_START_JOB = 1;
+    public static final int EVENT_STOP_JOB = 2;
+
+    private int[] mEventCmds = new int[EVENT_BUFFER_SIZE];
+    private long[] mEventTimes = new long[EVENT_BUFFER_SIZE];
+    private int[] mEventUids = new int[EVENT_BUFFER_SIZE];
+    private String[] mEventTags = new String[EVENT_BUFFER_SIZE];
+
+    public void addEvent(int cmd, int uid, String tag) {
+        System.arraycopy(mEventCmds, 0, mEventCmds, 1, EVENT_BUFFER_SIZE - 1);
+        System.arraycopy(mEventTimes, 0, mEventTimes, 1, EVENT_BUFFER_SIZE - 1);
+        System.arraycopy(mEventUids, 0, mEventUids, 1, EVENT_BUFFER_SIZE - 1);
+        System.arraycopy(mEventTags, 0, mEventTags, 1, EVENT_BUFFER_SIZE - 1);
+        mEventCmds[0] = cmd;
+        mEventTimes[0] = SystemClock.elapsedRealtime();
+        mEventUids[0] = uid;
+        mEventTags[0] = tag;
+    }
+
     DataSet mCurDataSet = new DataSet();
     DataSet[] mLastDataSets = new DataSet[NUM_HISTORY];
 
@@ -240,7 +262,8 @@
             }
         }
 
-        void dump(PrintWriter pw, String header, String prefix, long now, long nowEllapsed) {
+        void dump(PrintWriter pw, String header, String prefix, long now, long nowEllapsed,
+                int filterUid) {
             final long period = getTotalTime(now);
             pw.print(prefix); pw.print(header); pw.print(" at ");
             pw.print(DateFormat.format("yyyy-MM-dd-HH-mm-ss", mStartClockTime).toString());
@@ -251,12 +274,16 @@
             pw.println(":");
             final int NE = mEntries.size();
             for (int i = 0; i < NE; i++) {
+                int uid = mEntries.keyAt(i);
+                if (filterUid != -1 && filterUid != UserHandle.getAppId(uid)) {
+                    continue;
+                }
                 ArrayMap<String, PackageEntry> uidMap = mEntries.valueAt(i);
                 final int NP = uidMap.size();
                 for (int j = 0; j < NP; j++) {
                     PackageEntry pe = uidMap.valueAt(j);
                     pw.print(prefix); pw.print("  ");
-                    UserHandle.formatUid(pw, mEntries.keyAt(i));
+                    UserHandle.formatUid(pw, uid);
                     pw.print(" / "); pw.print(uidMap.keyAt(j));
                     pw.print(":");
                     printDuration(pw, period, pe.getPendingTime(now), "pending");
@@ -309,6 +336,7 @@
         } else {
             mCurDataSet.incActive(job.getSourceUid(), job.getSourcePackageName(), now);
         }
+        addEvent(EVENT_START_JOB, job.getSourceUid(), job.getBatteryName());
     }
 
     public void noteInactive(JobStatus job) {
@@ -319,6 +347,7 @@
             mCurDataSet.decActive(job.getSourceUid(), job.getSourcePackageName(), now);
         }
         rebatchIfNeeded(now);
+        addEvent(EVENT_STOP_JOB, job.getSourceUid(), job.getBatteryName());
     }
 
     public float getLoadFactor(JobStatus job) {
@@ -339,7 +368,7 @@
         return time / (float)period;
     }
 
-    public void dump(PrintWriter pw, String prefix) {
+    public void dump(PrintWriter pw, String prefix, int filterUid) {
         final long now = SystemClock.uptimeMillis();
         final long nowEllapsed = SystemClock.elapsedRealtime();
         final DataSet total;
@@ -352,10 +381,43 @@
         mCurDataSet.addTo(total, now);
         for (int i = 1; i < mLastDataSets.length; i++) {
             if (mLastDataSets[i] != null) {
-                mLastDataSets[i].dump(pw, "Historical stats", prefix, now, nowEllapsed);
+                mLastDataSets[i].dump(pw, "Historical stats", prefix, now, nowEllapsed, filterUid);
                 pw.println();
             }
         }
-        total.dump(pw, "Current stats", prefix, now, nowEllapsed);
+        total.dump(pw, "Current stats", prefix, now, nowEllapsed, filterUid);
+    }
+
+    public boolean dumpHistory(PrintWriter pw, String prefix, int filterUid) {
+        if (mEventCmds[0] == EVENT_NULL) {
+            return false;
+        }
+        pw.println("  Job history:");
+        long now = SystemClock.elapsedRealtime();
+        for (int i=EVENT_BUFFER_SIZE-1; i>=0; i--) {
+            int uid = mEventUids[i];
+            if (filterUid != -1 && filterUid != UserHandle.getAppId(filterUid)) {
+                continue;
+            }
+            int cmd = mEventCmds[i];
+            if (cmd == EVENT_NULL) {
+                continue;
+            }
+            String label;
+            switch (mEventCmds[i]) {
+                case EVENT_START_JOB:           label = "START"; break;
+                case EVENT_STOP_JOB:            label = " STOP"; break;
+                default:                        label = "   ??"; break;
+            }
+            pw.print(prefix);
+            TimeUtils.formatDuration(mEventTimes[i]-now, pw, TimeUtils.HUNDRED_DAY_FIELD_LEN);
+            pw.print(" ");
+            pw.print(label);
+            pw.print(": ");
+            UserHandle.formatUid(pw, uid);
+            pw.print(" ");
+            pw.println(mEventTags[i]);
+        }
+        return true;
     }
 }
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 075a88f..7e26d4b 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -1477,17 +1477,45 @@
         return s.toString();
     }
 
+    static void dumpHelp(PrintWriter pw) {
+        pw.println("Job Scheduler (jobscheduler) dump options:");
+        pw.println("  [-h] [package] ...");
+        pw.println("    -h: print this help");
+        pw.println("  [package] is an optional package name to limit the output to.");
+    }
+
     void dumpInternal(final PrintWriter pw, String[] args) {
         int filterUid = -1;
         if (!ArrayUtils.isEmpty(args)) {
-            try {
-                filterUid = getContext().getPackageManager().getPackageUid(args[0],
-                        PackageManager.MATCH_UNINSTALLED_PACKAGES);
-            } catch (NameNotFoundException ignored) {
+            int opti = 0;
+            while (opti < args.length) {
+                String arg = args[opti];
+                if ("-h".equals(arg)) {
+                    dumpHelp(pw);
+                    return;
+                } else if ("-a".equals(arg)) {
+                    // Ignore, we always dump all.
+                } else if (arg.length() > 0 && arg.charAt(0) == '-') {
+                    pw.println("Unknown option: " + arg);
+                    return;
+                } else {
+                    break;
+                }
+                opti++;
+            }
+            if (opti < args.length) {
+                String pkg = args[opti];
+                try {
+                    filterUid = getContext().getPackageManager().getPackageUid(pkg,
+                            PackageManager.MATCH_UNINSTALLED_PACKAGES);
+                } catch (NameNotFoundException ignored) {
+                    pw.println("Invalid package: " + pkg);
+                    return;
+                }
             }
         }
 
-        final int filterUidFinal = filterUid;
+        final int filterUidFinal = UserHandle.getAppId(filterUid);
         final long now = SystemClock.elapsedRealtime();
         synchronized (mLock) {
             pw.println("Started users: " + Arrays.toString(mStartedUsers));
@@ -1502,8 +1530,7 @@
                         pw.println(job.toShortString());
 
                         // Skip printing details if the caller requested a filter
-                        if (filterUidFinal != -1 && job.getUid() != filterUidFinal
-                                && job.getSourceUid() != filterUidFinal) {
+                        if (!job.shouldDump(filterUidFinal)) {
                             return;
                         }
 
@@ -1526,17 +1553,23 @@
             }
             for (int i=0; i<mControllers.size(); i++) {
                 pw.println();
-                mControllers.get(i).dumpControllerStateLocked(pw);
+                mControllers.get(i).dumpControllerStateLocked(pw, filterUidFinal);
             }
             pw.println();
             pw.println("Uid priority overrides:");
             for (int i=0; i< mUidPriorityOverride.size(); i++) {
-                pw.print("  "); pw.print(UserHandle.formatUid(mUidPriorityOverride.keyAt(i)));
-                pw.print(": "); pw.println(mUidPriorityOverride.valueAt(i));
+                int uid = mUidPriorityOverride.keyAt(i);
+                if (filterUidFinal == -1 || filterUidFinal == UserHandle.getAppId(uid)) {
+                    pw.print("  "); pw.print(UserHandle.formatUid(uid));
+                    pw.print(": "); pw.println(mUidPriorityOverride.valueAt(i));
+                }
             }
             pw.println();
-            mJobPackageTracker.dump(pw, "");
+            mJobPackageTracker.dump(pw, "", filterUidFinal);
             pw.println();
+            if (mJobPackageTracker.dumpHistory(pw, "", filterUidFinal)) {
+                pw.println();
+            }
             pw.println("Pending queue:");
             for (int i=0; i<mPendingJobs.size(); i++) {
                 JobStatus job = mPendingJobs.get(i);
@@ -1571,10 +1604,12 @@
                     }
                 }
             }
-            pw.println();
-            pw.print("mReadyToRock="); pw.println(mReadyToRock);
-            pw.print("mReportedActive="); pw.println(mReportedActive);
-            pw.print("mMaxActiveJobs="); pw.println(mMaxActiveJobs);
+            if (filterUid == -1) {
+                pw.println();
+                pw.print("mReadyToRock="); pw.println(mReadyToRock);
+                pw.print("mReportedActive="); pw.println(mReportedActive);
+                pw.print("mMaxActiveJobs="); pw.println(mMaxActiveJobs);
+            }
         }
         pw.println();
     }
diff --git a/services/core/java/com/android/server/job/controllers/AppIdleController.java b/services/core/java/com/android/server/job/controllers/AppIdleController.java
index 02bc36ca..7593035 100644
--- a/services/core/java/com/android/server/job/controllers/AppIdleController.java
+++ b/services/core/java/com/android/server/job/controllers/AppIdleController.java
@@ -23,10 +23,8 @@
 import com.android.server.LocalServices;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.JobStore;
-import com.android.server.job.StateChangedListener;
 
 import java.io.PrintWriter;
-import java.util.ArrayList;
 
 /**
  * Controls when apps are considered idle and if jobs pertaining to those apps should
@@ -123,11 +121,15 @@
     }
 
     @Override
-    public void dumpControllerStateLocked(final PrintWriter pw) {
+    public void dumpControllerStateLocked(final PrintWriter pw, final int filterUid) {
         pw.println("AppIdle");
         pw.println("Parole On: " + mAppIdleParoleOn);
         mJobSchedulerService.getJobStore().forEachJob(new JobStore.JobStatusFunctor() {
             @Override public void process(JobStatus jobStatus) {
+                // Skip printing details if the caller requested a filter
+                if (!jobStatus.shouldDump(filterUid)) {
+                    return;
+                }
                 pw.print("  ");
                 pw.print(jobStatus.getSourcePackageName());
                 pw.print(": runnable=");
diff --git a/services/core/java/com/android/server/job/controllers/BatteryController.java b/services/core/java/com/android/server/job/controllers/BatteryController.java
index 0772364..a0cb25f 100644
--- a/services/core/java/com/android/server/job/controllers/BatteryController.java
+++ b/services/core/java/com/android/server/job/controllers/BatteryController.java
@@ -194,15 +194,21 @@
     }
 
     @Override
-    public void dumpControllerStateLocked(PrintWriter pw) {
+    public void dumpControllerStateLocked(PrintWriter pw, int filterUid) {
         pw.println("Batt.");
         pw.println("Stable power: " + mChargeTracker.isOnStablePower());
         Iterator<JobStatus> it = mTrackedTasks.iterator();
         if (it.hasNext()) {
-            pw.print(String.valueOf(it.next().hashCode()));
+            JobStatus jobStatus = it.next();
+            if (jobStatus.shouldDump(filterUid)) {
+                pw.print(String.valueOf(jobStatus.hashCode()));
+            }
         }
         while (it.hasNext()) {
-            pw.print("," + String.valueOf(it.next().hashCode()));
+            JobStatus jobStatus = it.next();
+            if (jobStatus.shouldDump(filterUid)) {
+                pw.print("," + String.valueOf(jobStatus.hashCode()));
+            }
         }
         pw.println();
     }
diff --git a/services/core/java/com/android/server/job/controllers/ConnectivityController.java b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
index f5aac08..9fd22686 100644
--- a/services/core/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
@@ -181,14 +181,16 @@
     };
 
     @Override
-    public void dumpControllerStateLocked(PrintWriter pw) {
+    public void dumpControllerStateLocked(PrintWriter pw, int filterUid) {
         pw.println("Conn.");
         for (int i = 0; i < mTrackedJobs.size(); i++) {
             final JobStatus js = mTrackedJobs.get(i);
-            pw.println(String.valueOf(js.getJobId() + "," + js.getUid())
-                    + ": C=" + js.hasConnectivityConstraint()
-                    + ", UM=" + js.hasUnmeteredConstraint()
-                    + ", NR=" + js.hasNotRoamingConstraint());
+            if (js.shouldDump(filterUid)) {
+                pw.println(String.valueOf(js.getJobId() + "," + js.getUid())
+                        + ": C=" + js.hasConnectivityConstraint()
+                        + ", UM=" + js.hasUnmeteredConstraint()
+                        + ", NR=" + js.hasNotRoamingConstraint());
+            }
         }
     }
 }
diff --git a/services/core/java/com/android/server/job/controllers/ContentObserverController.java b/services/core/java/com/android/server/job/controllers/ContentObserverController.java
index c5b1a3d..6722bfb 100644
--- a/services/core/java/com/android/server/job/controllers/ContentObserverController.java
+++ b/services/core/java/com/android/server/job/controllers/ContentObserverController.java
@@ -322,11 +322,15 @@
     }
 
     @Override
-    public void dumpControllerStateLocked(PrintWriter pw) {
+    public void dumpControllerStateLocked(PrintWriter pw, int filterUid) {
         pw.println("Content.");
         boolean printed = false;
         Iterator<JobStatus> it = mTrackedTasks.iterator();
         while (it.hasNext()) {
+            JobStatus js = it.next();
+            if (!js.shouldDump(filterUid)) {
+                continue;
+            }
             if (!printed) {
                 pw.print("  ");
                 printed = true;
@@ -343,13 +347,24 @@
             pw.println("  Observers:");
             for (int i = 0; i < N; i++) {
                 ObserverInstance obs = mObservers.valueAt(i);
+                int M = obs.mJobs.size();
+                boolean shouldDump = false;
+                for (int j=0; j<M; j++) {
+                    JobInstance inst = obs.mJobs.valueAt(j);
+                    if (inst.mJobStatus.shouldDump(filterUid)) {
+                        shouldDump = true;
+                        break;
+                    }
+                }
+                if (!shouldDump) {
+                    continue;
+                }
                 pw.print("    ");
                 pw.print(mObservers.keyAt(i));
                 pw.print(" (");
                 pw.print(System.identityHashCode(obs));
                 pw.println("):");
                 pw.println("      Jobs:");
-                int M = obs.mJobs.size();
                 for (int j=0; j<M; j++) {
                     JobInstance inst = obs.mJobs.valueAt(j);
                     pw.print("        ");
diff --git a/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java b/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
index fe563d2..345a032 100644
--- a/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
+++ b/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
@@ -29,10 +29,8 @@
 import com.android.server.LocalServices;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.JobStore;
-import com.android.server.job.StateChangedListener;
 
 import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.Arrays;
 
 /**
@@ -175,10 +173,13 @@
     }
 
     @Override
-    public void dumpControllerStateLocked(final PrintWriter pw) {
+    public void dumpControllerStateLocked(final PrintWriter pw, final int filterUid) {
         pw.println("DeviceIdleJobsController");
         mJobSchedulerService.getJobStore().forEachJob(new JobStore.JobStatusFunctor() {
             @Override public void process(JobStatus jobStatus) {
+                if (!jobStatus.shouldDump(filterUid)) {
+                    return;
+                }
                 pw.print("  ");
                 pw.print(jobStatus.getSourcePackageName());
                 pw.print(": runnable=");
diff --git a/services/core/java/com/android/server/job/controllers/IdleController.java b/services/core/java/com/android/server/job/controllers/IdleController.java
index 50aa882..c7a679c 100644
--- a/services/core/java/com/android/server/job/controllers/IdleController.java
+++ b/services/core/java/com/android/server/job/controllers/IdleController.java
@@ -190,12 +190,15 @@
     }
 
     @Override
-    public void dumpControllerStateLocked(PrintWriter pw) {
+    public void dumpControllerStateLocked(PrintWriter pw, int filterUid) {
         pw.print("Idle: ");
         pw.println(mIdleTracker.isIdle() ? "true" : "false");
         pw.println(mTrackedTasks.size());
         for (int i = 0; i < mTrackedTasks.size(); i++) {
             final JobStatus js = mTrackedTasks.get(i);
+            if (!js.shouldDump(filterUid)) {
+                continue;
+            }
             pw.print("  ");
             pw.print(String.valueOf(js.getJobId() + "," + js.getUid()));
             pw.println("..");
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 19bede9..072787b 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -410,6 +410,11 @@
         return true;
     }
 
+    public boolean shouldDump(int filterUid) {
+        return filterUid == -1 || UserHandle.getAppId(getUid()) == filterUid
+                || UserHandle.getAppId(getSourceUid()) == filterUid;
+    }
+
     /**
      * @return Whether or not this job is ready to run, based on its requirements. This is true if
      * the constraints are satisfied <strong>or</strong> the deadline on the job has expired.
diff --git a/services/core/java/com/android/server/job/controllers/StateController.java b/services/core/java/com/android/server/job/controllers/StateController.java
index 0139039..1721fb9 100644
--- a/services/core/java/com/android/server/job/controllers/StateController.java
+++ b/services/core/java/com/android/server/job/controllers/StateController.java
@@ -64,5 +64,5 @@
     public void rescheduleForFailure(JobStatus newJob, JobStatus failureToReschedule) {
     }
 
-    public abstract void dumpControllerStateLocked(PrintWriter pw);
+    public abstract void dumpControllerStateLocked(PrintWriter pw, int filterUid);
 }
diff --git a/services/core/java/com/android/server/job/controllers/TimeController.java b/services/core/java/com/android/server/job/controllers/TimeController.java
index ab6768e..2f8ca7e 100644
--- a/services/core/java/com/android/server/job/controllers/TimeController.java
+++ b/services/core/java/com/android/server/job/controllers/TimeController.java
@@ -275,7 +275,7 @@
     };
 
     @Override
-    public void dumpControllerStateLocked(PrintWriter pw) {
+    public void dumpControllerStateLocked(PrintWriter pw, int filterUid) {
         final long nowElapsed = SystemClock.elapsedRealtime();
         pw.println("Alarms (" + SystemClock.elapsedRealtime() + ")");
         pw.println(
@@ -284,6 +284,9 @@
                 + "s");
         pw.println("Tracking:");
         for (JobStatus ts : mTrackedJobs) {
+            if (!ts.shouldDump(filterUid)) {
+                continue;
+            }
             pw.println(String.valueOf(ts.getJobId() + "," + ts.getUid())
                     + ": (" + (ts.hasTimingDelayConstraint() ? ts.getEarliestRunTime() : "N/A")
                     + ", " + (ts.hasDeadlineConstraint() ?ts.getLatestRunTimeElapsed() : "N/A")