Mechanical refactoring to improve job dumping.

First, JobStatusFunctor was really Consumer<JobStatus> before we
had the java.util.function APIs, so switch everyone over.  Replace
most usages with lambdas; no additional runtime cost, since existing
code was already creating classes.

Move dump() to accept Predicate<JobStatus> for their filtering
behavior, enabling more future advanced filtering using any
attributes of JobStatus.  Also move dump() to IndentingPrintWriter
to avoid passing around tedious prefix information.  Makes it much
easier to print sane-looking output.

Add IndentingPrintWriter support for initial prefix values.

Test: manual dumpsys output looks sane
Bug: 73019091
Change-Id: I4c2398443b42dfb48135ab900d4331ff6d2bb5c4
diff --git a/core/java/com/android/internal/util/IndentingPrintWriter.java b/core/java/com/android/internal/util/IndentingPrintWriter.java
index 696667c..e453866 100644
--- a/core/java/com/android/internal/util/IndentingPrintWriter.java
+++ b/core/java/com/android/internal/util/IndentingPrintWriter.java
@@ -57,26 +57,46 @@
         mWrapLength = wrapLength;
     }
 
-    public void increaseIndent() {
+    public IndentingPrintWriter setIndent(String indent) {
+        mIndentBuilder.setLength(0);
+        mIndentBuilder.append(indent);
+        mCurrentIndent = null;
+        return this;
+    }
+
+    public IndentingPrintWriter setIndent(int indent) {
+        mIndentBuilder.setLength(0);
+        for (int i = 0; i < indent; i++) {
+            increaseIndent();
+        }
+        return this;
+    }
+
+    public IndentingPrintWriter increaseIndent() {
         mIndentBuilder.append(mSingleIndent);
         mCurrentIndent = null;
+        return this;
     }
 
-    public void decreaseIndent() {
+    public IndentingPrintWriter decreaseIndent() {
         mIndentBuilder.delete(0, mSingleIndent.length());
         mCurrentIndent = null;
+        return this;
     }
 
-    public void printPair(String key, Object value) {
+    public IndentingPrintWriter printPair(String key, Object value) {
         print(key + "=" + String.valueOf(value) + " ");
+        return this;
     }
 
-    public void printPair(String key, Object[] value) {
+    public IndentingPrintWriter printPair(String key, Object[] value) {
         print(key + "=" + Arrays.toString(value) + " ");
+        return this;
     }
 
-    public void printHexPair(String key, int value) {
+    public IndentingPrintWriter printHexPair(String key, int value) {
         print(key + "=0x" + Integer.toHexString(value) + " ");
+        return this;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/AppStateTracker.java b/services/core/java/com/android/server/AppStateTracker.java
index 9b29b32..fc4d463 100644
--- a/services/core/java/com/android/server/AppStateTracker.java
+++ b/services/core/java/com/android/server/AppStateTracker.java
@@ -53,6 +53,7 @@
 import com.android.internal.app.IAppOpsCallback;
 import com.android.internal.app.IAppOpsService;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.server.ForceAppStandbyTrackerProto.ExemptedPackage;
 import com.android.server.ForceAppStandbyTrackerProto.RunAnyInBackgroundRestrictedPackages;
@@ -1182,72 +1183,67 @@
         }
     }
 
-    public void dump(PrintWriter pw, String indent) {
+    @Deprecated
+    public void dump(PrintWriter pw, String prefix) {
+        dump(new IndentingPrintWriter(pw, "  ").setIndent(prefix));
+    }
+
+    public void dump(IndentingPrintWriter pw) {
         synchronized (mLock) {
-            pw.print(indent);
             pw.println("Forced App Standby Feature enabled: " + mForcedAppStandbyEnabled);
 
-            pw.print(indent);
             pw.print("Force all apps standby: ");
             pw.println(isForceAllAppsStandbyEnabled());
 
-            pw.print(indent);
             pw.print("Small Battery Device: ");
             pw.println(isSmallBatteryDevice());
 
-            pw.print(indent);
             pw.print("Force all apps standby for small battery device: ");
             pw.println(mForceAllAppStandbyForSmallBattery);
 
-            pw.print(indent);
             pw.print("Plugged In: ");
             pw.println(mIsPluggedIn);
 
-            pw.print(indent);
             pw.print("Active uids: ");
             dumpUids(pw, mActiveUids);
 
-            pw.print(indent);
             pw.print("Foreground uids: ");
             dumpUids(pw, mForegroundUids);
 
-            pw.print(indent);
             pw.print("Whitelist appids: ");
             pw.println(Arrays.toString(mPowerWhitelistedAllAppIds));
 
-            pw.print(indent);
             pw.print("Temp whitelist appids: ");
             pw.println(Arrays.toString(mTempWhitelistedAppIds));
 
-            pw.print(indent);
             pw.println("Exempted packages:");
+            pw.increaseIndent();
             for (int i = 0; i < mExemptedPackages.size(); i++) {
-                pw.print(indent);
-                pw.print("  User ");
+                pw.print("User ");
                 pw.print(mExemptedPackages.keyAt(i));
                 pw.println();
 
+                pw.increaseIndent();
                 for (int j = 0; j < mExemptedPackages.sizeAt(i); j++) {
-                    pw.print(indent);
-                    pw.print("    ");
                     pw.print(mExemptedPackages.valueAt(i, j));
                     pw.println();
                 }
+                pw.decreaseIndent();
             }
+            pw.decreaseIndent();
             pw.println();
 
-            pw.print(indent);
             pw.println("Restricted packages:");
+            pw.increaseIndent();
             for (Pair<Integer, String> uidAndPackage : mRunAnyRestrictedPackages) {
-                pw.print(indent);
-                pw.print("  ");
                 pw.print(UserHandle.formatUid(uidAndPackage.first));
                 pw.print(" ");
                 pw.print(uidAndPackage.second);
                 pw.println();
             }
+            pw.decreaseIndent();
 
-            mStatLogger.dump(pw, indent);
+            mStatLogger.dump(pw);
         }
     }
 
diff --git a/services/core/java/com/android/server/StatLogger.java b/services/core/java/com/android/server/StatLogger.java
index 0e6f5e2..d85810d 100644
--- a/services/core/java/com/android/server/StatLogger.java
+++ b/services/core/java/com/android/server/StatLogger.java
@@ -21,6 +21,7 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.StatLoggerProto.Event;
 
 import java.io.PrintWriter;
@@ -78,19 +79,23 @@
         }
     }
 
+    @Deprecated
     public void dump(PrintWriter pw, String prefix) {
+        dump(new IndentingPrintWriter(pw, "  ").setIndent(prefix));
+    }
+
+    public void dump(IndentingPrintWriter pw) {
         synchronized (mLock) {
-            pw.print(prefix);
             pw.println("Stats:");
+            pw.increaseIndent();
             for (int i = 0; i < SIZE; i++) {
-                pw.print(prefix);
-                pw.print("  ");
                 final int count = mCountStats[i];
                 final double durationMs = mDurationStats[i] / 1000.0;
                 pw.println(String.format("%s: count=%d, total=%.1fms, avg=%.3fms",
                         mLabels[i], count, durationMs,
                         (count == 0 ? 0 : ((double) durationMs) / count)));
             }
+            pw.decreaseIndent();
         }
     }
 
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index f3f4dfd..ff4e1e2 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -79,6 +79,7 @@
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.server.AppStateTracker;
 import com.android.server.DeviceIdleController;
@@ -86,7 +87,6 @@
 import com.android.server.LocalServices;
 import com.android.server.job.JobSchedulerServiceDumpProto.ActiveJob;
 import com.android.server.job.JobSchedulerServiceDumpProto.PendingJob;
-import com.android.server.job.JobStore.JobStatusFunctor;
 import com.android.server.job.controllers.AppIdleController;
 import com.android.server.job.controllers.BackgroundJobsController;
 import com.android.server.job.controllers.BatteryController;
@@ -110,6 +110,7 @@
 import java.util.Comparator;
 import java.util.Iterator;
 import java.util.List;
+import java.util.function.Consumer;
 import java.util.function.Predicate;
 
 /**
@@ -1227,13 +1228,10 @@
                                     getContext().getMainLooper()));
                 }
                 // Attach jobs to their controllers.
-                mJobs.forEachJob(new JobStatusFunctor() {
-                    @Override
-                    public void process(JobStatus job) {
-                        for (int controller = 0; controller < mControllers.size(); controller++) {
-                            final StateController sc = mControllers.get(controller);
-                            sc.maybeStartTrackingJobLocked(job, null);
-                        }
+                mJobs.forEachJob((job) -> {
+                    for (int controller = 0; controller < mControllers.size(); controller++) {
+                        final StateController sc = mControllers.get(controller);
+                        sc.maybeStartTrackingJobLocked(job, null);
                     }
                 });
                 // GO GO GO!
@@ -1602,11 +1600,11 @@
         }
     }
 
-    final class ReadyJobQueueFunctor implements JobStatusFunctor {
+    final class ReadyJobQueueFunctor implements Consumer<JobStatus> {
         ArrayList<JobStatus> newReadyJobs;
 
         @Override
-        public void process(JobStatus job) {
+        public void accept(JobStatus job) {
             if (isReadyToBeExecutedLocked(job)) {
                 if (DEBUG) {
                     Slog.d(TAG, "    queued " + job.toShortString());
@@ -1640,7 +1638,7 @@
      * If more than 4 jobs total are ready we send them all off.
      * TODO: It would be nice to consolidate these sort of high-level policies somewhere.
      */
-    final class MaybeReadyJobQueueFunctor implements JobStatusFunctor {
+    final class MaybeReadyJobQueueFunctor implements Consumer<JobStatus> {
         int chargingCount;
         int batteryNotLowCount;
         int storageNotLowCount;
@@ -1656,7 +1654,7 @@
 
         // Functor method invoked for each job via JobStore.forEachJob()
         @Override
-        public void process(JobStatus job) {
+        public void accept(JobStatus job) {
             if (isReadyToBeExecutedLocked(job)) {
                 try {
                     if (ActivityManager.getService().isAppStartModeDisabled(job.getUid(),
@@ -2173,12 +2171,9 @@
         public List<JobInfo> getSystemScheduledPendingJobs() {
             synchronized (mLock) {
                 final List<JobInfo> pendingJobs = new ArrayList<JobInfo>();
-                mJobs.forEachJob(Process.SYSTEM_UID, new JobStatusFunctor() {
-                    @Override
-                    public void process(JobStatus job) {
-                        if (job.getJob().isPeriodic() || !isCurrentlyActiveLocked(job)) {
-                            pendingJobs.add(job.getJob());
-                        }
+                mJobs.forEachJob(Process.SYSTEM_UID, (job) -> {
+                    if (job.getJob().isPeriodic() || !isCurrentlyActiveLocked(job)) {
+                        pendingJobs.add(job.getJob());
                     }
                 });
                 return pendingJobs;
@@ -2307,7 +2302,7 @@
         }
     }
 
-    static class DeferredJobCounter implements JobStatusFunctor {
+    static class DeferredJobCounter implements Consumer<JobStatus> {
         private int mDeferred = 0;
 
         public int numDeferred() {
@@ -2315,7 +2310,7 @@
         }
 
         @Override
-        public void process(JobStatus job) {
+        public void accept(JobStatus job) {
             if (job.getWhenStandbyDeferred() > 0) {
                 mDeferred++;
             }
@@ -2596,12 +2591,13 @@
                 }
             }
 
-            long identityToken = Binder.clearCallingIdentity();
+            final long identityToken = Binder.clearCallingIdentity();
             try {
                 if (proto) {
                     JobSchedulerService.this.dumpInternalProto(fd, filterUid);
                 } else {
-                    JobSchedulerService.this.dumpInternal(pw, filterUid);
+                    JobSchedulerService.this.dumpInternal(new IndentingPrintWriter(pw, "  "),
+                            filterUid);
                 }
             } finally {
                 Binder.restoreCallingIdentity(identityToken);
@@ -2900,10 +2896,14 @@
         });
     }
 
-    void dumpInternal(final PrintWriter pw, int filterUid) {
+    void dumpInternal(final IndentingPrintWriter pw, int filterUid) {
         final int filterUidFinal = UserHandle.getAppId(filterUid);
         final long nowElapsed = sElapsedRealtimeClock.millis();
         final long nowUptime = sUptimeMillisClock.millis();
+        final Predicate<JobStatus> predicate = (js) -> {
+            return filterUidFinal == -1 || UserHandle.getAppId(js.getUid()) == filterUidFinal
+                    || UserHandle.getAppId(js.getSourceUid()) == filterUidFinal;
+        };
         synchronized (mLock) {
             mConstants.dump(pw);
             pw.println();
@@ -2919,7 +2919,7 @@
                     pw.println(job.toShortStringExceptUniqueId());
 
                     // Skip printing details if the caller requested a filter
-                    if (!job.shouldDump(filterUidFinal)) {
+                    if (!predicate.test(job)) {
                         continue;
                     }
 
@@ -2953,7 +2953,10 @@
             }
             for (int i=0; i<mControllers.size(); i++) {
                 pw.println();
-                mControllers.get(i).dumpControllerStateLocked(pw, filterUidFinal);
+                pw.println(mControllers.get(i).getClass().getSimpleName() + ":");
+                pw.increaseIndent();
+                mControllers.get(i).dumpControllerStateLocked(pw, predicate);
+                pw.decreaseIndent();
             }
             pw.println();
             pw.println("Uid priority overrides:");
@@ -3056,6 +3059,10 @@
         final int filterUidFinal = UserHandle.getAppId(filterUid);
         final long nowElapsed = sElapsedRealtimeClock.millis();
         final long nowUptime = sUptimeMillisClock.millis();
+        final Predicate<JobStatus> predicate = (js) -> {
+            return filterUidFinal == -1 || UserHandle.getAppId(js.getUid()) == filterUidFinal
+                    || UserHandle.getAppId(js.getSourceUid()) == filterUidFinal;
+        };
 
         synchronized (mLock) {
             mConstants.dump(proto, JobSchedulerServiceDumpProto.SETTINGS);
@@ -3070,7 +3077,7 @@
                     job.writeToShortProto(proto, JobSchedulerServiceDumpProto.RegisteredJob.INFO);
 
                     // Skip printing details if the caller requested a filter
-                    if (!job.shouldDump(filterUidFinal)) {
+                    if (!predicate.test(job)) {
                         continue;
                     }
 
@@ -3103,7 +3110,7 @@
             }
             for (StateController controller : mControllers) {
                 controller.dumpControllerStateLocked(
-                        proto, JobSchedulerServiceDumpProto.CONTROLLERS, filterUidFinal);
+                        proto, JobSchedulerServiceDumpProto.CONTROLLERS, predicate);
             }
             for (int i=0; i< mUidPriorityOverride.size(); i++) {
                 int uid = mUidPriorityOverride.keyAt(i);
diff --git a/services/core/java/com/android/server/job/JobStore.java b/services/core/java/com/android/server/job/JobStore.java
index cf27882..7235faa 100644
--- a/services/core/java/com/android/server/job/JobStore.java
+++ b/services/core/java/com/android/server/job/JobStore.java
@@ -19,6 +19,7 @@
 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
 import static com.android.server.job.JobSchedulerService.sSystemClock;
 
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.IActivityManager;
 import android.app.job.JobInfo;
@@ -62,6 +63,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
+import java.util.function.Consumer;
 import java.util.function.Predicate;
 
 /**
@@ -286,22 +288,23 @@
      * transient unified collections for them to iterate over and then discard, or creating
      * iterators every time a client needs to perform a sweep.
      */
-    public void forEachJob(JobStatusFunctor functor) {
-        mJobSet.forEachJob(functor);
+    public void forEachJob(Consumer<JobStatus> functor) {
+        mJobSet.forEachJob(null, functor);
     }
 
-    public void forEachJob(int uid, JobStatusFunctor functor) {
+    public void forEachJob(@Nullable Predicate<JobStatus> filterPredicate,
+            Consumer<JobStatus> functor) {
+        mJobSet.forEachJob(filterPredicate, functor);
+    }
+
+    public void forEachJob(int uid, Consumer<JobStatus> functor) {
         mJobSet.forEachJob(uid, functor);
     }
 
-    public void forEachJobForSourceUid(int sourceUid, JobStatusFunctor functor) {
+    public void forEachJobForSourceUid(int sourceUid, Consumer<JobStatus> functor) {
         mJobSet.forEachJobForSourceUid(sourceUid, functor);
     }
 
-    public interface JobStatusFunctor {
-        public void process(JobStatus jobStatus);
-    }
-
     /** Version of the db schema. */
     private static final int JOBS_FILE_VERSION = 0;
     /** Tag corresponds to constraints this job needs. */
@@ -342,12 +345,9 @@
             final List<JobStatus> storeCopy = new ArrayList<JobStatus>();
             synchronized (mLock) {
                 // Clone the jobs so we can release the lock before writing.
-                mJobSet.forEachJob(new JobStatusFunctor() {
-                    @Override
-                    public void process(JobStatus job) {
-                        if (job.isPersisted()) {
-                            storeCopy.add(new JobStatus(job));
-                        }
+                mJobSet.forEachJob(null, (job) -> {
+                    if (job.isPersisted()) {
+                        storeCopy.add(new JobStatus(job));
                     }
                 });
             }
@@ -1184,31 +1184,35 @@
             return total;
         }
 
-        public void forEachJob(JobStatusFunctor functor) {
+        public void forEachJob(@Nullable Predicate<JobStatus> filterPredicate,
+                Consumer<JobStatus> functor) {
             for (int uidIndex = mJobs.size() - 1; uidIndex >= 0; uidIndex--) {
                 ArraySet<JobStatus> jobs = mJobs.valueAt(uidIndex);
                 if (jobs != null) {
                     for (int i = jobs.size() - 1; i >= 0; i--) {
-                        functor.process(jobs.valueAt(i));
+                        final JobStatus jobStatus = jobs.valueAt(i);
+                        if ((filterPredicate == null) || filterPredicate.test(jobStatus)) {
+                            functor.accept(jobStatus);
+                        }
                     }
                 }
             }
         }
 
-        public void forEachJob(int callingUid, JobStatusFunctor functor) {
+        public void forEachJob(int callingUid, Consumer<JobStatus> functor) {
             ArraySet<JobStatus> jobs = mJobs.get(callingUid);
             if (jobs != null) {
                 for (int i = jobs.size() - 1; i >= 0; i--) {
-                    functor.process(jobs.valueAt(i));
+                    functor.accept(jobs.valueAt(i));
                 }
             }
         }
 
-        public void forEachJobForSourceUid(int sourceUid, JobStatusFunctor functor) {
+        public void forEachJobForSourceUid(int sourceUid, Consumer<JobStatus> functor) {
             final ArraySet<JobStatus> jobs = mJobsPerSourceUid.get(sourceUid);
             if (jobs != null) {
                 for (int i = jobs.size() - 1; i >= 0; i--) {
-                    functor.process(jobs.valueAt(i));
+                    functor.accept(jobs.valueAt(i));
                 }
             }
         }
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 021eccd..4b48824 100644
--- a/services/core/java/com/android/server/job/controllers/AppIdleController.java
+++ b/services/core/java/com/android/server/job/controllers/AppIdleController.java
@@ -23,12 +23,13 @@
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.LocalServices;
 import com.android.server.job.JobSchedulerService;
-import com.android.server.job.JobStore;
 import com.android.server.job.StateControllerProto;
 
-import java.io.PrintWriter;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
 
 /**
  * Controls when apps are considered idle and if jobs pertaining to those apps should
@@ -49,10 +50,11 @@
     private boolean mInitializedParoleOn;
     boolean mAppIdleParoleOn;
 
-    final class GlobalUpdateFunc implements JobStore.JobStatusFunctor {
+    final class GlobalUpdateFunc implements Consumer<JobStatus> {
         boolean mChanged;
 
-        @Override public void process(JobStatus jobStatus) {
+        @Override
+        public void accept(JobStatus jobStatus) {
             String packageName = jobStatus.getSourcePackageName();
             final boolean appIdle = !mAppIdleParoleOn && mUsageStatsInternal.isAppIdle(packageName,
                     jobStatus.getSourceUid(), jobStatus.getSourceUserId());
@@ -63,9 +65,9 @@
                 mChanged = true;
             }
         }
-    };
+    }
 
-    final static class PackageUpdateFunc implements JobStore.JobStatusFunctor {
+    final static class PackageUpdateFunc implements Consumer<JobStatus> {
         final int mUserId;
         final String mPackage;
         final boolean mIdle;
@@ -77,7 +79,8 @@
             mIdle = idle;
         }
 
-        @Override public void process(JobStatus jobStatus) {
+        @Override
+        public void accept(JobStatus jobStatus) {
             if (jobStatus.getSourcePackageName().equals(mPackage)
                     && jobStatus.getSourceUserId() == mUserId) {
                 if (jobStatus.setAppNotIdleConstraintSatisfied(!mIdle)) {
@@ -89,7 +92,7 @@
                 }
             }
         }
-    };
+    }
 
     public static AppIdleController get(JobSchedulerService service) {
         synchronized (sCreationLock) {
@@ -131,56 +134,46 @@
     }
 
     @Override
-    public void dumpControllerStateLocked(final PrintWriter pw, final int filterUid) {
-        pw.print("AppIdle: parole on = ");
-        pw.println(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("  #");
-                jobStatus.printUniqueId(pw);
-                pw.print(" from ");
-                UserHandle.formatUid(pw, jobStatus.getSourceUid());
-                pw.print(": ");
-                pw.print(jobStatus.getSourcePackageName());
-                if ((jobStatus.satisfiedConstraints&JobStatus.CONSTRAINT_APP_NOT_IDLE) != 0) {
-                    pw.println(" RUNNABLE");
-                } else {
-                    pw.println(" WAITING");
-                }
+    public void dumpControllerStateLocked(final IndentingPrintWriter pw,
+            final Predicate<JobStatus> predicate) {
+        pw.println("Parole on: " + mAppIdleParoleOn);
+        pw.println();
+
+        mJobSchedulerService.getJobStore().forEachJob(predicate, (jobStatus) -> {
+            pw.print("#");
+            jobStatus.printUniqueId(pw);
+            pw.print(" from ");
+            UserHandle.formatUid(pw, jobStatus.getSourceUid());
+            pw.print(": ");
+            pw.print(jobStatus.getSourcePackageName());
+            if ((jobStatus.satisfiedConstraints&JobStatus.CONSTRAINT_APP_NOT_IDLE) != 0) {
+                pw.println(" RUNNABLE");
+            } else {
+                pw.println(" WAITING");
             }
         });
     }
 
     @Override
-    public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId, int filterUid) {
+    public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
+            Predicate<JobStatus> predicate) {
         final long token = proto.start(fieldId);
         final long mToken = proto.start(StateControllerProto.APP_IDLE);
 
         proto.write(StateControllerProto.AppIdleController.IS_PAROLE_ON, mAppIdleParoleOn);
 
-        mJobSchedulerService.getJobStore().forEachJob(new JobStore.JobStatusFunctor() {
-            @Override public void process(JobStatus js) {
-                // Skip printing details if the caller requested a filter
-                if (!js.shouldDump(filterUid)) {
-                    return;
-                }
-
-                final long jsToken =
-                        proto.start(StateControllerProto.AppIdleController.TRACKED_JOBS);
-                js.writeToShortProto(proto, StateControllerProto.AppIdleController.TrackedJob.INFO);
-                proto.write(StateControllerProto.AppIdleController.TrackedJob.SOURCE_UID,
-                        js.getSourceUid());
-                proto.write(StateControllerProto.AppIdleController.TrackedJob.SOURCE_PACKAGE_NAME,
-                        js.getSourcePackageName());
-                proto.write(
-                        StateControllerProto.AppIdleController.TrackedJob.ARE_CONSTRAINTS_SATISFIED,
-                        (js.satisfiedConstraints & JobStatus.CONSTRAINT_APP_NOT_IDLE) != 0);
-                proto.end(jsToken);
-            }
+        mJobSchedulerService.getJobStore().forEachJob(predicate, (js) -> {
+            final long jsToken =
+                    proto.start(StateControllerProto.AppIdleController.TRACKED_JOBS);
+            js.writeToShortProto(proto, StateControllerProto.AppIdleController.TrackedJob.INFO);
+            proto.write(StateControllerProto.AppIdleController.TrackedJob.SOURCE_UID,
+                    js.getSourceUid());
+            proto.write(StateControllerProto.AppIdleController.TrackedJob.SOURCE_PACKAGE_NAME,
+                    js.getSourcePackageName());
+            proto.write(
+                    StateControllerProto.AppIdleController.TrackedJob.ARE_CONSTRAINTS_SATISFIED,
+                    (js.satisfiedConstraints & JobStatus.CONSTRAINT_APP_NOT_IDLE) != 0);
+            proto.end(jsToken);
         });
 
         proto.end(mToken);
diff --git a/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java b/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java
index 46ec5e5..7a09fc4 100644
--- a/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java
+++ b/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java
@@ -23,16 +23,17 @@
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.server.AppStateTracker;
 import com.android.server.AppStateTracker.Listener;
 import com.android.server.LocalServices;
 import com.android.server.job.JobSchedulerService;
-import com.android.server.job.JobStore;
 import com.android.server.job.StateControllerProto;
 import com.android.server.job.StateControllerProto.BackgroundJobsController.TrackedJob;
 
-import java.io.PrintWriter;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
 
 public final class BackgroundJobsController extends StateController {
     private static final String TAG = "JobScheduler.Background";
@@ -77,19 +78,15 @@
     }
 
     @Override
-    public void dumpControllerStateLocked(final PrintWriter pw, final int filterUid) {
-        pw.println("BackgroundJobsController");
+    public void dumpControllerStateLocked(final IndentingPrintWriter pw,
+            final Predicate<JobStatus> predicate) {
+        mAppStateTracker.dump(pw);
+        pw.println();
 
-        mAppStateTracker.dump(pw, "");
-
-        pw.println("Job state:");
-        mJobSchedulerService.getJobStore().forEachJob((jobStatus) -> {
-            if (!jobStatus.shouldDump(filterUid)) {
-                return;
-            }
+        mJobSchedulerService.getJobStore().forEachJob(predicate, (jobStatus) -> {
             final int uid = jobStatus.getSourceUid();
             final String sourcePkg = jobStatus.getSourcePackageName();
-            pw.print("  #");
+            pw.print("#");
             jobStatus.printUniqueId(pw);
             pw.print(" from ");
             UserHandle.formatUid(pw, uid);
@@ -115,17 +112,15 @@
     }
 
     @Override
-    public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId, int filterUid) {
+    public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
+            Predicate<JobStatus> predicate) {
         final long token = proto.start(fieldId);
         final long mToken = proto.start(StateControllerProto.BACKGROUND);
 
         mAppStateTracker.dumpProto(proto,
                 StateControllerProto.BackgroundJobsController.FORCE_APP_STANDBY_TRACKER);
 
-        mJobSchedulerService.getJobStore().forEachJob((jobStatus) -> {
-            if (!jobStatus.shouldDump(filterUid)) {
-                return;
-            }
+        mJobSchedulerService.getJobStore().forEachJob(predicate, (jobStatus) -> {
             final long jsToken =
                     proto.start(StateControllerProto.BackgroundJobsController.TRACKED_JOBS);
 
@@ -205,7 +200,7 @@
         return jobStatus.setBackgroundNotRestrictedConstraintSatisfied(canRun);
     }
 
-    private final class UpdateJobFunctor implements JobStore.JobStatusFunctor {
+    private final class UpdateJobFunctor implements Consumer<JobStatus> {
         private final int mFilterUid;
 
         boolean mChanged = false;
@@ -217,7 +212,7 @@
         }
 
         @Override
-        public void process(JobStatus jobStatus) {
+        public void accept(JobStatus jobStatus) {
             mTotalCount++;
             if ((mFilterUid > 0) && (mFilterUid != jobStatus.getSourceUid())) {
                 return;
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 263d99b..24491b0 100644
--- a/services/core/java/com/android/server/job/controllers/BatteryController.java
+++ b/services/core/java/com/android/server/job/controllers/BatteryController.java
@@ -31,12 +31,13 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.LocalServices;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.StateChangedListener;
 import com.android.server.job.StateControllerProto;
 
-import java.io.PrintWriter;
+import java.util.function.Predicate;
 
 /**
  * Simple controller that tracks whether the phone is charging or not. The phone is considered to
@@ -244,24 +245,23 @@
     }
 
     @Override
-    public void dumpControllerStateLocked(PrintWriter pw, int filterUid) {
-        pw.print("Battery: stable power = ");
-        pw.print(mChargeTracker.isOnStablePower());
-        pw.print(", not low = ");
-        pw.println(mChargeTracker.isBatteryNotLow());
+    public void dumpControllerStateLocked(IndentingPrintWriter pw,
+            Predicate<JobStatus> predicate) {
+        pw.println("Stable power: " + mChargeTracker.isOnStablePower());
+        pw.println("Not low: " + mChargeTracker.isBatteryNotLow());
+
         if (mChargeTracker.isMonitoring()) {
             pw.print("MONITORING: seq=");
             pw.println(mChargeTracker.getSeq());
         }
-        pw.print("Tracking ");
-        pw.print(mTrackedTasks.size());
-        pw.println(":");
+        pw.println();
+
         for (int i = 0; i < mTrackedTasks.size(); i++) {
             final JobStatus js = mTrackedTasks.valueAt(i);
-            if (!js.shouldDump(filterUid)) {
+            if (!predicate.test(js)) {
                 continue;
             }
-            pw.print("  #");
+            pw.print("#");
             js.printUniqueId(pw);
             pw.print(" from ");
             UserHandle.formatUid(pw, js.getSourceUid());
@@ -270,7 +270,8 @@
     }
 
     @Override
-    public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId, int filterUid) {
+    public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
+            Predicate<JobStatus> predicate) {
         final long token = proto.start(fieldId);
         final long mToken = proto.start(StateControllerProto.BATTERY);
 
@@ -286,7 +287,7 @@
 
         for (int i = 0; i < mTrackedTasks.size(); i++) {
             final JobStatus js = mTrackedTasks.valueAt(i);
-            if (!js.shouldDump(filterUid)) {
+            if (!predicate.test(js)) {
                 continue;
             }
             final long jsToken = proto.start(StateControllerProto.BatteryController.TRACKED_JOBS);
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 d7ef124..dd07bc6 100644
--- a/services/core/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
@@ -41,12 +41,13 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.JobServiceContext;
 import com.android.server.job.StateChangedListener;
 import com.android.server.job.StateControllerProto;
 
-import java.io.PrintWriter;
+import java.util.function.Predicate;
 
 /**
  * Handles changes in connectivity.
@@ -331,18 +332,15 @@
 
     @GuardedBy("mLock")
     @Override
-    public void dumpControllerStateLocked(PrintWriter pw, int filterUid) {
-        pw.print("Connectivity: connected=");
-        pw.println(mConnected);
-
-        pw.print("Tracking ");
-        pw.print(mTrackedJobs.size());
-        pw.println(" jobs");
+    public void dumpControllerStateLocked(IndentingPrintWriter pw,
+            Predicate<JobStatus> predicate) {
+        pw.println("System connected: " + mConnected);
+        pw.println();
 
         for (int i = 0; i < mTrackedJobs.size(); i++) {
             final JobStatus js = mTrackedJobs.valueAt(i);
-            if (js.shouldDump(filterUid)) {
-                pw.print("  #");
+            if (predicate.test(js)) {
+                pw.print("#");
                 js.printUniqueId(pw);
                 pw.print(" from ");
                 UserHandle.formatUid(pw, js.getSourceUid());
@@ -355,7 +353,8 @@
 
     @GuardedBy("mLock")
     @Override
-    public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId, int filterUid) {
+    public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
+            Predicate<JobStatus> predicate) {
         final long token = proto.start(fieldId);
         final long mToken = proto.start(StateControllerProto.CONNECTIVITY);
 
@@ -363,7 +362,7 @@
 
         for (int i = 0; i < mTrackedJobs.size(); i++) {
             final JobStatus js = mTrackedJobs.valueAt(i);
-            if (!js.shouldDump(filterUid)) {
+            if (!predicate.test(js)) {
                 continue;
             }
             final long jsToken = proto.start(StateControllerProto.ConnectivityController.TRACKED_JOBS);
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 90edde9..27f6c1e 100644
--- a/services/core/java/com/android/server/job/controllers/ContentObserverController.java
+++ b/services/core/java/com/android/server/job/controllers/ContentObserverController.java
@@ -32,13 +32,14 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.StateChangedListener;
 import com.android.server.job.StateControllerProto;
 import com.android.server.job.StateControllerProto.ContentObserverController.Observer.TriggerContentData;
 
-import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.function.Predicate;
 
 /**
  * Controller for monitoring changes to content URIs through a ContentObserver.
@@ -375,22 +376,25 @@
     }
 
     @Override
-    public void dumpControllerStateLocked(PrintWriter pw, int filterUid) {
-        pw.println("Content:");
+    public void dumpControllerStateLocked(IndentingPrintWriter pw,
+            Predicate<JobStatus> predicate) {
         for (int i = 0; i < mTrackedTasks.size(); i++) {
             JobStatus js = mTrackedTasks.valueAt(i);
-            if (!js.shouldDump(filterUid)) {
+            if (!predicate.test(js)) {
                 continue;
             }
-            pw.print("  #");
+            pw.print("#");
             js.printUniqueId(pw);
             pw.print(" from ");
             UserHandle.formatUid(pw, js.getSourceUid());
             pw.println();
         }
+        pw.println();
+
         int N = mObservers.size();
         if (N > 0) {
-            pw.println("  Observers:");
+            pw.println("Observers:");
+            pw.increaseIndent();
             for (int userIdx = 0; userIdx < N; userIdx++) {
                 final int userId = mObservers.keyAt(userIdx);
                 ArrayMap<JobInfo.TriggerContentUri, ObserverInstance> observersOfUser =
@@ -402,7 +406,7 @@
                     boolean shouldDump = false;
                     for (int j = 0; j < M; j++) {
                         JobInstance inst = obs.mJobs.valueAt(j);
-                        if (inst.mJobStatus.shouldDump(filterUid)) {
+                        if (predicate.test(inst.mJobStatus)) {
                             shouldDump = true;
                             break;
                         }
@@ -410,7 +414,6 @@
                     if (!shouldDump) {
                         continue;
                     }
-                    pw.print("    ");
                     JobInfo.TriggerContentUri trigger = observersOfUser.keyAt(observerIdx);
                     pw.print(trigger.getUri());
                     pw.print(" 0x");
@@ -418,17 +421,20 @@
                     pw.print(" (");
                     pw.print(System.identityHashCode(obs));
                     pw.println("):");
-                    pw.println("      Jobs:");
+                    pw.increaseIndent();
+                    pw.println("Jobs:");
+                    pw.increaseIndent();
                     for (int j = 0; j < M; j++) {
                         JobInstance inst = obs.mJobs.valueAt(j);
-                        pw.print("        #");
+                        pw.print("#");
                         inst.mJobStatus.printUniqueId(pw);
                         pw.print(" from ");
                         UserHandle.formatUid(pw, inst.mJobStatus.getSourceUid());
                         if (inst.mChangedAuthorities != null) {
                             pw.println(":");
+                            pw.increaseIndent();
                             if (inst.mTriggerPending) {
-                                pw.print("          Trigger pending: update=");
+                                pw.print("Trigger pending: update=");
                                 TimeUtils.formatDuration(
                                         inst.mJobStatus.getTriggerContentUpdateDelay(), pw);
                                 pw.print(", max=");
@@ -436,35 +442,38 @@
                                         inst.mJobStatus.getTriggerContentMaxDelay(), pw);
                                 pw.println();
                             }
-                            pw.println("          Changed Authorities:");
+                            pw.println("Changed Authorities:");
                             for (int k = 0; k < inst.mChangedAuthorities.size(); k++) {
-                                pw.print("          ");
                                 pw.println(inst.mChangedAuthorities.valueAt(k));
                             }
                             if (inst.mChangedUris != null) {
                                 pw.println("          Changed URIs:");
                                 for (int k = 0; k < inst.mChangedUris.size(); k++) {
-                                    pw.print("          ");
                                     pw.println(inst.mChangedUris.valueAt(k));
                                 }
                             }
+                            pw.decreaseIndent();
                         } else {
                             pw.println();
                         }
                     }
+                    pw.decreaseIndent();
+                    pw.decreaseIndent();
                 }
             }
+            pw.decreaseIndent();
         }
     }
 
     @Override
-    public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId, int filterUid) {
+    public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
+            Predicate<JobStatus> predicate) {
         final long token = proto.start(fieldId);
         final long mToken = proto.start(StateControllerProto.CONTENT_OBSERVER);
 
         for (int i = 0; i < mTrackedTasks.size(); i++) {
             JobStatus js = mTrackedTasks.valueAt(i);
-            if (!js.shouldDump(filterUid)) {
+            if (!predicate.test(js)) {
                 continue;
             }
             final long jsToken =
@@ -493,7 +502,7 @@
                 boolean shouldDump = false;
                 for (int j = 0; j < m; j++) {
                     JobInstance inst = obs.mJobs.valueAt(j);
-                    if (inst.mJobStatus.shouldDump(filterUid)) {
+                    if (predicate.test(inst.mJobStatus)) {
                         shouldDump = true;
                         break;
                     }
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 323a126..aa60350 100644
--- a/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
+++ b/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
@@ -33,15 +33,16 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.DeviceIdleController;
 import com.android.server.LocalServices;
 import com.android.server.job.JobSchedulerService;
-import com.android.server.job.JobStore;
 import com.android.server.job.StateControllerProto;
 import com.android.server.job.StateControllerProto.DeviceIdleJobsController.TrackedJob;
 
-import java.io.PrintWriter;
 import java.util.Arrays;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
 
 /**
  * When device is dozing, set constraint for all jobs, except whitelisted apps, as not satisfied.
@@ -247,71 +248,64 @@
     }
 
     @Override
-    public void dumpControllerStateLocked(final PrintWriter pw, final int filterUid) {
-        pw.println("DeviceIdleJobsController");
-        pw.println("mDeviceIdleMode=" + mDeviceIdleMode);
-        mJobSchedulerService.getJobStore().forEachJob(new JobStore.JobStatusFunctor() {
-            @Override public void process(JobStatus jobStatus) {
-                if (!jobStatus.shouldDump(filterUid)) {
-                    return;
-                }
-                pw.print("  #");
-                jobStatus.printUniqueId(pw);
-                pw.print(" from ");
-                UserHandle.formatUid(pw, jobStatus.getSourceUid());
-                pw.print(": ");
-                pw.print(jobStatus.getSourcePackageName());
-                pw.print((jobStatus.satisfiedConstraints
-                        & JobStatus.CONSTRAINT_DEVICE_NOT_DOZING) != 0
-                                ? " RUNNABLE" : " WAITING");
-                if (jobStatus.dozeWhitelisted) {
-                    pw.print(" WHITELISTED");
-                }
-                if (mAllowInIdleJobs.contains(jobStatus)) {
-                    pw.print(" ALLOWED_IN_DOZE");
-                }
-                pw.println();
+    public void dumpControllerStateLocked(final IndentingPrintWriter pw,
+            final Predicate<JobStatus> predicate) {
+        pw.println("Idle mode: " + mDeviceIdleMode);
+        pw.println();
+
+        mJobSchedulerService.getJobStore().forEachJob(predicate, (jobStatus) -> {
+            pw.print("#");
+            jobStatus.printUniqueId(pw);
+            pw.print(" from ");
+            UserHandle.formatUid(pw, jobStatus.getSourceUid());
+            pw.print(": ");
+            pw.print(jobStatus.getSourcePackageName());
+            pw.print((jobStatus.satisfiedConstraints
+                    & JobStatus.CONSTRAINT_DEVICE_NOT_DOZING) != 0
+                            ? " RUNNABLE" : " WAITING");
+            if (jobStatus.dozeWhitelisted) {
+                pw.print(" WHITELISTED");
             }
+            if (mAllowInIdleJobs.contains(jobStatus)) {
+                pw.print(" ALLOWED_IN_DOZE");
+            }
+            pw.println();
         });
     }
 
     @Override
-    public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId, int filterUid) {
+    public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
+            Predicate<JobStatus> predicate) {
         final long token = proto.start(fieldId);
         final long mToken = proto.start(StateControllerProto.DEVICE_IDLE);
 
         proto.write(StateControllerProto.DeviceIdleJobsController.IS_DEVICE_IDLE_MODE,
                 mDeviceIdleMode);
-        mJobSchedulerService.getJobStore().forEachJob(new JobStore.JobStatusFunctor() {
-            @Override public void process(JobStatus jobStatus) {
-                if (!jobStatus.shouldDump(filterUid)) {
-                    return;
-                }
-                final long jsToken =
-                        proto.start(StateControllerProto.DeviceIdleJobsController.TRACKED_JOBS);
+        mJobSchedulerService.getJobStore().forEachJob(predicate, (jobStatus) -> {
+            final long jsToken =
+                    proto.start(StateControllerProto.DeviceIdleJobsController.TRACKED_JOBS);
 
-                jobStatus.writeToShortProto(proto, TrackedJob.INFO);
-                proto.write(TrackedJob.SOURCE_UID, jobStatus.getSourceUid());
-                proto.write(TrackedJob.SOURCE_PACKAGE_NAME, jobStatus.getSourcePackageName());
-                proto.write(TrackedJob.ARE_CONSTRAINTS_SATISFIED,
-                        (jobStatus.satisfiedConstraints &
-                            JobStatus.CONSTRAINT_DEVICE_NOT_DOZING) != 0);
-                proto.write(TrackedJob.IS_DOZE_WHITELISTED, jobStatus.dozeWhitelisted);
-                proto.write(TrackedJob.IS_ALLOWED_IN_DOZE, mAllowInIdleJobs.contains(jobStatus));
+            jobStatus.writeToShortProto(proto, TrackedJob.INFO);
+            proto.write(TrackedJob.SOURCE_UID, jobStatus.getSourceUid());
+            proto.write(TrackedJob.SOURCE_PACKAGE_NAME, jobStatus.getSourcePackageName());
+            proto.write(TrackedJob.ARE_CONSTRAINTS_SATISFIED,
+                    (jobStatus.satisfiedConstraints &
+                        JobStatus.CONSTRAINT_DEVICE_NOT_DOZING) != 0);
+            proto.write(TrackedJob.IS_DOZE_WHITELISTED, jobStatus.dozeWhitelisted);
+            proto.write(TrackedJob.IS_ALLOWED_IN_DOZE, mAllowInIdleJobs.contains(jobStatus));
 
-                proto.end(jsToken);
-            }
+            proto.end(jsToken);
         });
 
         proto.end(mToken);
         proto.end(token);
     }
 
-    final class DeviceIdleUpdateFunctor implements JobStore.JobStatusFunctor {
+    final class DeviceIdleUpdateFunctor implements Consumer<JobStatus> {
         boolean mChanged;
 
         @Override
-        public void process(JobStatus jobStatus) {
+        public void accept(JobStatus jobStatus) {
             mChanged |= updateTaskStateLocked(jobStatus);
         }
     }
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 78284e5..2cfa2c5 100644
--- a/services/core/java/com/android/server/job/controllers/IdleController.java
+++ b/services/core/java/com/android/server/job/controllers/IdleController.java
@@ -30,12 +30,13 @@
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.am.ActivityManagerService;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.StateChangedListener;
 import com.android.server.job.StateControllerProto;
 
-import java.io.PrintWriter;
+import java.util.function.Predicate;
 
 public final class IdleController extends StateController {
     private static final String TAG = "JobScheduler.Idle";
@@ -203,18 +204,17 @@
     }
 
     @Override
-    public void dumpControllerStateLocked(PrintWriter pw, int filterUid) {
-        pw.print("Idle: ");
-        pw.println(mIdleTracker.isIdle());
-        pw.print("Tracking ");
-        pw.print(mTrackedTasks.size());
-        pw.println(":");
+    public void dumpControllerStateLocked(IndentingPrintWriter pw,
+            Predicate<JobStatus> predicate) {
+        pw.println("Currently idle: " + mIdleTracker.isIdle());
+        pw.println();
+
         for (int i = 0; i < mTrackedTasks.size(); i++) {
             final JobStatus js = mTrackedTasks.valueAt(i);
-            if (!js.shouldDump(filterUid)) {
+            if (!predicate.test(js)) {
                 continue;
             }
-            pw.print("  #");
+            pw.print("#");
             js.printUniqueId(pw);
             pw.print(" from ");
             UserHandle.formatUid(pw, js.getSourceUid());
@@ -223,7 +223,8 @@
     }
 
     @Override
-    public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId, int filterUid) {
+    public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
+            Predicate<JobStatus> predicate) {
         final long token = proto.start(fieldId);
         final long mToken = proto.start(StateControllerProto.IDLE);
 
@@ -231,7 +232,7 @@
 
         for (int i = 0; i < mTrackedTasks.size(); i++) {
             final JobStatus js = mTrackedTasks.valueAt(i);
-            if (!js.shouldDump(filterUid)) {
+            if (!predicate.test(js)) {
                 continue;
             }
             final long jsToken = proto.start(StateControllerProto.IdleController.TRACKED_JOBS);
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 3867306..9333e22 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -882,11 +882,6 @@
         return mLastFailedRunTime;
     }
 
-    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 88d6bea..2b97bca 100644
--- a/services/core/java/com/android/server/job/controllers/StateController.java
+++ b/services/core/java/com/android/server/job/controllers/StateController.java
@@ -19,10 +19,10 @@
 import android.content.Context;
 import android.util.proto.ProtoOutputStream;
 
-import com.android.server.job.JobSchedulerService;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.job.StateChangedListener;
 
-import java.io.PrintWriter;
+import java.util.function.Predicate;
 
 /**
  * Incorporates shared controller logic between the various controllers of the JobManager.
@@ -64,7 +64,8 @@
     public void rescheduleForFailureLocked(JobStatus newJob, JobStatus failureToReschedule) {
     }
 
-    public abstract void dumpControllerStateLocked(PrintWriter pw, int filterUid);
+    public abstract void dumpControllerStateLocked(IndentingPrintWriter pw,
+            Predicate<JobStatus> predicate);
     public abstract void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
-            int filterUid);
+            Predicate<JobStatus> predicate);
 }
diff --git a/services/core/java/com/android/server/job/controllers/StorageController.java b/services/core/java/com/android/server/job/controllers/StorageController.java
index 5b79f39..e36f02d 100644
--- a/services/core/java/com/android/server/job/controllers/StorageController.java
+++ b/services/core/java/com/android/server/job/controllers/StorageController.java
@@ -29,12 +29,13 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.StateChangedListener;
 import com.android.server.job.StateControllerProto;
 import com.android.server.storage.DeviceStorageMonitorService;
 
-import java.io.PrintWriter;
+import java.util.function.Predicate;
 
 /**
  * Simple controller that tracks the status of the device's storage.
@@ -175,20 +176,18 @@
     }
 
     @Override
-    public void dumpControllerStateLocked(PrintWriter pw, int filterUid) {
-        pw.print("Storage: not low = ");
-        pw.print(mStorageTracker.isStorageNotLow());
-        pw.print(", seq=");
-        pw.println(mStorageTracker.getSeq());
-        pw.print("Tracking ");
-        pw.print(mTrackedTasks.size());
-        pw.println(":");
+    public void dumpControllerStateLocked(IndentingPrintWriter pw,
+            Predicate<JobStatus> predicate) {
+        pw.println("Not low: " + mStorageTracker.isStorageNotLow());
+        pw.println("Sequence: " + mStorageTracker.getSeq());
+        pw.println();
+
         for (int i = 0; i < mTrackedTasks.size(); i++) {
             final JobStatus js = mTrackedTasks.valueAt(i);
-            if (!js.shouldDump(filterUid)) {
+            if (!predicate.test(js)) {
                 continue;
             }
-            pw.print("  #");
+            pw.print("#");
             js.printUniqueId(pw);
             pw.print(" from ");
             UserHandle.formatUid(pw, js.getSourceUid());
@@ -197,7 +196,8 @@
     }
 
     @Override
-    public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId, int filterUid) {
+    public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
+            Predicate<JobStatus> predicate) {
         final long token = proto.start(fieldId);
         final long mToken = proto.start(StateControllerProto.STORAGE);
 
@@ -208,7 +208,7 @@
 
         for (int i = 0; i < mTrackedTasks.size(); i++) {
             final JobStatus js = mTrackedTasks.valueAt(i);
-            if (!js.shouldDump(filterUid)) {
+            if (!predicate.test(js)) {
                 continue;
             }
             final long jsToken = proto.start(StateControllerProto.StorageController.TRACKED_JOBS);
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 cdafc3b..1b2292db 100644
--- a/services/core/java/com/android/server/job/controllers/TimeController.java
+++ b/services/core/java/com/android/server/job/controllers/TimeController.java
@@ -30,15 +30,16 @@
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.StateChangedListener;
 import com.android.server.job.StateControllerProto;
 
-import java.io.PrintWriter;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.ListIterator;
+import java.util.function.Predicate;
 
 /**
  * This class sets an alarm for the next expiring job, and determines whether a job's minimum
@@ -348,25 +349,24 @@
     };
 
     @Override
-    public void dumpControllerStateLocked(PrintWriter pw, int filterUid) {
+    public void dumpControllerStateLocked(IndentingPrintWriter pw,
+            Predicate<JobStatus> predicate) {
         final long nowElapsed = sElapsedRealtimeClock.millis();
-        pw.print("Alarms: now=");
-        pw.print(nowElapsed);
-        pw.println();
+        pw.println("Elapsed clock: " + nowElapsed);
+
         pw.print("Next delay alarm in ");
         TimeUtils.formatDuration(mNextDelayExpiredElapsedMillis, nowElapsed, pw);
         pw.println();
         pw.print("Next deadline alarm in ");
         TimeUtils.formatDuration(mNextJobExpiredElapsedMillis, nowElapsed, pw);
         pw.println();
-        pw.print("Tracking ");
-        pw.print(mTrackedJobs.size());
-        pw.println(":");
+        pw.println();
+
         for (JobStatus ts : mTrackedJobs) {
-            if (!ts.shouldDump(filterUid)) {
+            if (!predicate.test(ts)) {
                 continue;
             }
-            pw.print("  #");
+            pw.print("#");
             ts.printUniqueId(pw);
             pw.print(" from ");
             UserHandle.formatUid(pw, ts.getSourceUid());
@@ -387,7 +387,8 @@
     }
 
     @Override
-    public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId, int filterUid) {
+    public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
+            Predicate<JobStatus> predicate) {
         final long token = proto.start(fieldId);
         final long mToken = proto.start(StateControllerProto.TIME);
 
@@ -399,7 +400,7 @@
                 mNextJobExpiredElapsedMillis - nowElapsed);
 
         for (JobStatus ts : mTrackedJobs) {
-            if (!ts.shouldDump(filterUid)) {
+            if (!predicate.test(ts)) {
                 continue;
             }
             final long tsToken = proto.start(StateControllerProto.TimeController.TRACKED_JOBS);