Merge "In the spirit of writing stuff down"
diff --git a/api/system-current.txt b/api/system-current.txt
index 1235591..d24a313 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4292,6 +4292,7 @@
   }
 
   public class RecoveryController {
+    method public android.security.keystore.recovery.RecoverySession createRecoverySession();
     method public byte[] generateAndStoreKey(java.lang.String, byte[]) throws android.security.keystore.recovery.InternalRecoveryServiceException, android.security.keystore.recovery.LockScreenRequiredException;
     method public java.util.List<java.lang.String> getAliases(java.lang.String) throws android.security.keystore.recovery.InternalRecoveryServiceException;
     method public static android.security.keystore.recovery.RecoveryController getInstance(android.content.Context);
diff --git a/core/java/android/net/INetworkStatsService.aidl b/core/java/android/net/INetworkStatsService.aidl
index 90e3ffd..381cfb6 100644
--- a/core/java/android/net/INetworkStatsService.aidl
+++ b/core/java/android/net/INetworkStatsService.aidl
@@ -39,9 +39,6 @@
      */
     INetworkStatsSession openSessionForUsageStats(int flags, String callingPackage);
 
-    /** Return network layer usage total for traffic that matches template. */
-    long getNetworkTotalBytes(in NetworkTemplate template, long start, long end);
-
     /** Return data layer snapshot of UID network usage. */
     NetworkStats getDataLayerSnapshotForUid(int uid);
     /** Return set of any ifaces associated with mobile networks since boot. */
@@ -50,17 +47,11 @@
     /** Increment data layer count of operations performed for UID and tag. */
     void incrementOperationCount(int uid, int tag, int operationCount);
 
-    /** Mark given UID as being in foreground for stats purposes. */
-    void setUidForeground(int uid, boolean uidForeground);
-
     /** Force update of ifaces. */
     void forceUpdateIfaces(in Network[] defaultNetworks);
     /** Force update of statistics. */
     void forceUpdate();
 
-    /** Advise persistance threshold; may be overridden internally. */
-    void advisePersistThreshold(long thresholdBytes);
-
     /** Registers a callback on data usage. */
     DataUsageRequest registerUsageCallback(String callingPackage,
             in DataUsageRequest request, in Messenger messenger, in IBinder binder);
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index 01b2b39..16fb858 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -234,7 +234,7 @@
     public NetworkStats(long elapsedRealtime, int initialSize) {
         this.elapsedRealtime = elapsedRealtime;
         this.size = 0;
-        if (initialSize >= 0) {
+        if (initialSize > 0) {
             this.capacity = initialSize;
             this.iface = new String[initialSize];
             this.uid = new int[initialSize];
@@ -250,19 +250,7 @@
             this.operations = new long[initialSize];
         } else {
             // Special case for use by NetworkStatsFactory to start out *really* empty.
-            this.capacity = 0;
-            this.iface = EmptyArray.STRING;
-            this.uid = EmptyArray.INT;
-            this.set = EmptyArray.INT;
-            this.tag = EmptyArray.INT;
-            this.metered = EmptyArray.INT;
-            this.roaming = EmptyArray.INT;
-            this.defaultNetwork = EmptyArray.INT;
-            this.rxBytes = EmptyArray.LONG;
-            this.rxPackets = EmptyArray.LONG;
-            this.txBytes = EmptyArray.LONG;
-            this.txPackets = EmptyArray.LONG;
-            this.operations = EmptyArray.LONG;
+            clear();
         }
     }
 
@@ -314,6 +302,25 @@
         return clone;
     }
 
+    /**
+     * Clear all data stored in this object.
+     */
+    public void clear() {
+        this.capacity = 0;
+        this.iface = EmptyArray.STRING;
+        this.uid = EmptyArray.INT;
+        this.set = EmptyArray.INT;
+        this.tag = EmptyArray.INT;
+        this.metered = EmptyArray.INT;
+        this.roaming = EmptyArray.INT;
+        this.defaultNetwork = EmptyArray.INT;
+        this.rxBytes = EmptyArray.LONG;
+        this.rxPackets = EmptyArray.LONG;
+        this.txBytes = EmptyArray.LONG;
+        this.txPackets = EmptyArray.LONG;
+        this.operations = EmptyArray.LONG;
+    }
+
     @VisibleForTesting
     public NetworkStats addIfaceValues(
             String iface, long rxBytes, long rxPackets, long txBytes, long txPackets) {
diff --git a/core/java/android/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java
index 433f941..a13ad65 100644
--- a/core/java/android/net/NetworkStatsHistory.java
+++ b/core/java/android/net/NetworkStatsHistory.java
@@ -39,6 +39,8 @@
 
 import com.android.internal.util.IndentingPrintWriter;
 
+import libcore.util.EmptyArray;
+
 import java.io.CharArrayWriter;
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
@@ -459,6 +461,21 @@
     }
 
     /**
+     * Clear all data stored in this object.
+     */
+    public void clear() {
+        bucketStart = EmptyArray.LONG;
+        if (activeTime != null) activeTime = EmptyArray.LONG;
+        if (rxBytes != null) rxBytes = EmptyArray.LONG;
+        if (rxPackets != null) rxPackets = EmptyArray.LONG;
+        if (txBytes != null) txBytes = EmptyArray.LONG;
+        if (txPackets != null) txPackets = EmptyArray.LONG;
+        if (operations != null) operations = EmptyArray.LONG;
+        bucketCount = 0;
+        totalBytes = 0;
+    }
+
+    /**
      * Remove buckets older than requested cutoff.
      */
     @Deprecated
diff --git a/core/java/android/net/NetworkTemplate.java b/core/java/android/net/NetworkTemplate.java
index 8efd39a..74233fd 100644
--- a/core/java/android/net/NetworkTemplate.java
+++ b/core/java/android/net/NetworkTemplate.java
@@ -23,7 +23,6 @@
 import static android.net.ConnectivityManager.TYPE_WIFI;
 import static android.net.ConnectivityManager.TYPE_WIFI_P2P;
 import static android.net.ConnectivityManager.TYPE_WIMAX;
-import static android.net.NetworkIdentity.COMBINE_SUBTYPE_ENABLED;
 import static android.net.NetworkStats.DEFAULT_NETWORK_ALL;
 import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
 import static android.net.NetworkStats.DEFAULT_NETWORK_YES;
@@ -34,11 +33,6 @@
 import static android.net.NetworkStats.ROAMING_NO;
 import static android.net.NetworkStats.ROAMING_YES;
 import static android.net.wifi.WifiInfo.removeDoubleQuotes;
-import static android.telephony.TelephonyManager.NETWORK_CLASS_2_G;
-import static android.telephony.TelephonyManager.NETWORK_CLASS_3_G;
-import static android.telephony.TelephonyManager.NETWORK_CLASS_4_G;
-import static android.telephony.TelephonyManager.NETWORK_CLASS_UNKNOWN;
-import static android.telephony.TelephonyManager.getNetworkClass;
 
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -55,8 +49,8 @@
 import java.util.Objects;
 
 /**
- * Template definition used to generically match {@link NetworkIdentity},
- * usually when collecting statistics.
+ * Predicate used to match {@link NetworkIdentity}, usually when collecting
+ * statistics. (It should probably have been named {@code NetworkPredicate}.)
  *
  * @hide
  */
@@ -68,13 +62,7 @@
      */
     private static final int BACKUP_VERSION = 1;
 
-    public static final int MATCH_MOBILE_ALL = 1;
-    /** @deprecated don't use this any more */
-    @Deprecated
-    public static final int MATCH_MOBILE_3G_LOWER = 2;
-    /** @deprecated don't use this any more */
-    @Deprecated
-    public static final int MATCH_MOBILE_4G = 3;
+    public static final int MATCH_MOBILE = 1;
     public static final int MATCH_WIFI = 4;
     public static final int MATCH_ETHERNET = 5;
     public static final int MATCH_MOBILE_WILDCARD = 6;
@@ -84,9 +72,7 @@
 
     private static boolean isKnownMatchRule(final int rule) {
         switch (rule) {
-            case MATCH_MOBILE_ALL:
-            case MATCH_MOBILE_3G_LOWER:
-            case MATCH_MOBILE_4G:
+            case MATCH_MOBILE:
             case MATCH_WIFI:
             case MATCH_ETHERNET:
             case MATCH_MOBILE_WILDCARD:
@@ -111,25 +97,7 @@
      * the given IMSI.
      */
     public static NetworkTemplate buildTemplateMobileAll(String subscriberId) {
-        return new NetworkTemplate(MATCH_MOBILE_ALL, subscriberId, null);
-    }
-
-    /**
-     * Template to match {@link ConnectivityManager#TYPE_MOBILE} networks with
-     * the given IMSI that roughly meet a "3G" definition, or lower.
-     */
-    @Deprecated
-    public static NetworkTemplate buildTemplateMobile3gLower(String subscriberId) {
-        return new NetworkTemplate(MATCH_MOBILE_3G_LOWER, subscriberId, null);
-    }
-
-    /**
-     * Template to match {@link ConnectivityManager#TYPE_MOBILE} networks with
-     * the given IMSI that roughly meet a "4G" definition.
-     */
-    @Deprecated
-    public static NetworkTemplate buildTemplateMobile4g(String subscriberId) {
-        return new NetworkTemplate(MATCH_MOBILE_4G, subscriberId, null);
+        return new NetworkTemplate(MATCH_MOBILE, subscriberId, null);
     }
 
     /**
@@ -307,9 +275,7 @@
 
     public boolean isMatchRuleMobile() {
         switch (mMatchRule) {
-            case MATCH_MOBILE_3G_LOWER:
-            case MATCH_MOBILE_4G:
-            case MATCH_MOBILE_ALL:
+            case MATCH_MOBILE:
             case MATCH_MOBILE_WILDCARD:
                 return true;
             default:
@@ -348,12 +314,8 @@
         if (!matchesDefaultNetwork(ident)) return false;
 
         switch (mMatchRule) {
-            case MATCH_MOBILE_ALL:
+            case MATCH_MOBILE:
                 return matchesMobile(ident);
-            case MATCH_MOBILE_3G_LOWER:
-                return matchesMobile3gLower(ident);
-            case MATCH_MOBILE_4G:
-                return matchesMobile4g(ident);
             case MATCH_WIFI:
                 return matchesWifi(ident);
             case MATCH_ETHERNET:
@@ -410,43 +372,6 @@
     }
 
     /**
-     * Check if mobile network classified 3G or lower with matching IMSI.
-     */
-    @Deprecated
-    private boolean matchesMobile3gLower(NetworkIdentity ident) {
-        ensureSubtypeAvailable();
-        if (ident.mType == TYPE_WIMAX) {
-            return false;
-        } else if (matchesMobile(ident)) {
-            switch (getNetworkClass(ident.mSubType)) {
-                case NETWORK_CLASS_UNKNOWN:
-                case NETWORK_CLASS_2_G:
-                case NETWORK_CLASS_3_G:
-                    return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Check if mobile network classified 4G with matching IMSI.
-     */
-    @Deprecated
-    private boolean matchesMobile4g(NetworkIdentity ident) {
-        ensureSubtypeAvailable();
-        if (ident.mType == TYPE_WIMAX) {
-            // TODO: consider matching against WiMAX subscriber identity
-            return true;
-        } else if (matchesMobile(ident)) {
-            switch (getNetworkClass(ident.mSubType)) {
-                case NETWORK_CLASS_4_G:
-                    return true;
-            }
-        }
-        return false;
-    }
-
-    /**
      * Check if matches Wi-Fi network template.
      */
     private boolean matchesWifi(NetworkIdentity ident) {
@@ -506,12 +431,8 @@
 
     private static String getMatchRuleName(int matchRule) {
         switch (matchRule) {
-            case MATCH_MOBILE_3G_LOWER:
-                return "MOBILE_3G_LOWER";
-            case MATCH_MOBILE_4G:
-                return "MOBILE_4G";
-            case MATCH_MOBILE_ALL:
-                return "MOBILE_ALL";
+            case MATCH_MOBILE:
+                return "MOBILE";
             case MATCH_WIFI:
                 return "WIFI";
             case MATCH_ETHERNET:
@@ -529,13 +450,6 @@
         }
     }
 
-    private static void ensureSubtypeAvailable() {
-        if (COMBINE_SUBTYPE_ENABLED) {
-            throw new IllegalArgumentException(
-                    "Unable to enforce 3G_LOWER template on combined data.");
-        }
-    }
-
     /**
      * Examine the given template and normalize if it refers to a "merged"
      * mobile subscriber. We pick the "lowest" merged subscriber as the primary
diff --git a/core/java/android/security/keystore/recovery/RecoveryController.java b/core/java/android/security/keystore/recovery/RecoveryController.java
index d57782c..fcca5af 100644
--- a/core/java/android/security/keystore/recovery/RecoveryController.java
+++ b/core/java/android/security/keystore/recovery/RecoveryController.java
@@ -552,6 +552,15 @@
         }
     }
 
+    /**
+     * Returns a new {@link RecoverySession}.
+     *
+     * <p>A recovery session is required to restore keys from a remote store.
+     */
+    public RecoverySession createRecoverySession() {
+        return RecoverySession.newInstance(this);
+    }
+
     InternalRecoveryServiceException wrapUnexpectedServiceSpecificException(
             ServiceSpecificException e) {
         if (e.errorCode == ERROR_SERVICE_INTERNAL_ERROR) {
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/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto
index 304e63f..2d31c5a 100644
--- a/core/proto/android/server/jobscheduler.proto
+++ b/core/proto/android/server/jobscheduler.proto
@@ -201,6 +201,12 @@
     // be indices into this array, rather than the raw constants used by
     // AppIdleHistory.
     repeated int32 standby_beats = 20;
+    // The fraction of a job's running window that must pass before we
+    // consider running it when the network is congested.
+    optional double conn_congestion_delay_frac = 21;
+    // The fraction of a prefetch job's running window that must pass before
+    // we consider matching it against a metered network.
+    optional double conn_prefetch_relax_frac = 22;
 }
 
 message StateControllerProto {
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 9d7432e..0b33211 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -5905,6 +5905,9 @@
         <!-- Identifier of the image file. This attribute is mandatory.
              It must be an image file with multiple frames, e.g. gif or webp -->
         <attr name="src" />
+        <!-- Indicates if the drawable needs to be mirrored when its layout direction is
+             RTL (right-to-left). -->
+        <attr name="autoMirrored" />
     </declare-styleable>
 
     <!-- Drawable used to draw bitmaps. -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index f4be888..cadc3ff 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3772,31 +3772,23 @@
     <string name="extract_edit_menu_button">Edit</string>
 
     <!-- Notification title when data usage has exceeded warning threshold. [CHAR LIMIT=50] -->
-    <string name="data_usage_warning_title">Data usage alert</string>
+    <string name="data_usage_warning_title">Data warning</string>
     <!-- Notification body when data usage has exceeded warning threshold. [CHAR LIMIT=32] -->
-    <string name="data_usage_warning_body">Tap to view usage and settings.</string>
+    <string name="data_usage_warning_body">You've used <xliff:g id="app" example="3.8GB">%s</xliff:g> of data</string>
 
-    <!-- Notification title when 2G-3G data usage has exceeded limit threshold, and has been disabled. [CHAR LIMIT=32] -->
-    <string name="data_usage_3g_limit_title">2G-3G data limit reached</string>
-    <!-- Notification title when 4G data usage has exceeded limit threshold, and has been disabled. [CHAR LIMIT=32] -->
-    <string name="data_usage_4g_limit_title">4G data limit reached</string>
     <!-- Notification title when mobile data usage has exceeded limit threshold, and has been disabled. [CHAR LIMIT=50] -->
     <string name="data_usage_mobile_limit_title">Mobile data limit reached</string>
     <!-- Notification title when Wi-Fi data usage has exceeded limit threshold, and has been disabled. [CHAR LIMIT=32] -->
     <string name="data_usage_wifi_limit_title">Wi-Fi data limit reached</string>
     <!-- Notification body when data usage has exceeded limit threshold, and has been disabled. -->
-    <string name="data_usage_limit_body">Data paused for rest of cycle</string>
+    <string name="data_usage_limit_body">Data paused for the rest of your cycle</string>
 
-    <!-- Notification title when 2G-3G data usage has exceeded limit threshold. [CHAR LIMIT=32] -->
-    <string name="data_usage_3g_limit_snoozed_title">2G-3G data limit exceeded</string>
-    <!-- Notification title when 4G data usage has exceeded limit threshold. [CHAR LIMIT=32] -->
-    <string name="data_usage_4g_limit_snoozed_title">4G data limit exceeded</string>
     <!-- Notification title when mobile data usage has exceeded limit threshold. [CHAR LIMIT=32] -->
-    <string name="data_usage_mobile_limit_snoozed_title">Mobile data limit exceeded</string>
+    <string name="data_usage_mobile_limit_snoozed_title">Over your mobile data limit</string>
     <!-- Notification title when Wi-Fi data usage has exceeded limit threshold. [CHAR LIMIT=32] -->
-    <string name="data_usage_wifi_limit_snoozed_title">Wi-Fi data limit exceeded</string>
+    <string name="data_usage_wifi_limit_snoozed_title">Over your Wi-Fi data limit</string>
     <!-- Notification body when data usage has exceeded limit threshold. -->
-    <string name="data_usage_limit_snoozed_body"><xliff:g id="size" example="3.8GB">%s</xliff:g> over specified limit.</string>
+    <string name="data_usage_limit_snoozed_body">You've gone <xliff:g id="size" example="3.8GB">%s</xliff:g> over your set limit</string>
 
     <!-- Notification title when background data usage is limited. [CHAR LIMIT=32] -->
     <string name="data_usage_restricted_title">Background data restricted</string>
@@ -3804,9 +3796,11 @@
     <string name="data_usage_restricted_body">Tap to remove restriction.</string>
 
     <!-- Notification title when there has been recent excessive data usage. [CHAR LIMIT=32] -->
-    <string name="data_usage_rapid_title">Large data usage</string>
+    <string name="data_usage_rapid_title">High mobile data usage</string>
     <!-- Notification body when there has been recent excessive data usage. [CHAR LIMIT=128] -->
-    <string name="data_usage_rapid_body">Your data usage over the last few days is larger than normal. Tap to view usage and settings.</string>
+    <string name="data_usage_rapid_body">Your apps have used more data than usual</string>
+    <!-- Notification body when there has been recent excessive data usage by a specific app. [CHAR LIMIT=128] -->
+    <string name="data_usage_rapid_app_body"><xliff:g id="app" example="Calculator">%s</xliff:g> has used more data than usual</string>
 
     <!-- SSL Certificate dialogs -->
     <!-- Title for an SSL Certificate dialog -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index fc4d4e7..a2af57e 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1971,10 +1971,6 @@
   <java-symbol type="string" name="config_wimaxServiceClassname" />
   <java-symbol type="string" name="config_wimaxServiceJarLocation" />
   <java-symbol type="string" name="config_wimaxStateTrackerClassname" />
-  <java-symbol type="string" name="data_usage_3g_limit_snoozed_title" />
-  <java-symbol type="string" name="data_usage_3g_limit_title" />
-  <java-symbol type="string" name="data_usage_4g_limit_snoozed_title" />
-  <java-symbol type="string" name="data_usage_4g_limit_title" />
   <java-symbol type="string" name="data_usage_limit_body" />
   <java-symbol type="string" name="data_usage_limit_snoozed_body" />
   <java-symbol type="string" name="data_usage_mobile_limit_snoozed_title" />
@@ -1987,6 +1983,7 @@
   <java-symbol type="string" name="data_usage_wifi_limit_title" />
   <java-symbol type="string" name="data_usage_rapid_title" />
   <java-symbol type="string" name="data_usage_rapid_body" />
+  <java-symbol type="string" name="data_usage_rapid_app_body" />
   <java-symbol type="string" name="default_wallpaper_component" />
   <java-symbol type="string" name="device_storage_monitor_notification_channel" />
   <java-symbol type="string" name="dlg_ok" />
diff --git a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
index 598807d..796d008 100644
--- a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
@@ -72,11 +72,14 @@
             mAssetFd = afd;
         }
 
-        public  final long mNativePtr;
+        final long mNativePtr;
 
         // These just keep references so the native code can continue using them.
         private final InputStream mInputStream;
         private final AssetFileDescriptor mAssetFd;
+
+        int[] mThemeAttrs = null;
+        boolean mAutoMirrored = false;
     }
 
     private State mState;
@@ -99,7 +102,7 @@
      *  <p>By default, the loop count in the encoded data is respected.</p>
      */
     public void setLoopCount(int loopCount) {
-        if (mState == null) {
+        if (mState.mNativePtr == 0) {
             throw new IllegalStateException("called setLoopCount on empty AnimatedImageDrawable");
         }
         nSetLoopCount(mState.mNativePtr, loopCount);
@@ -109,7 +112,7 @@
      * Create an empty AnimatedImageDrawable.
      */
     public AnimatedImageDrawable() {
-        mState = null;
+        mState = new State(0, null, null);
     }
 
     @Override
@@ -123,6 +126,7 @@
 
     private void updateStateFromTypedArray(TypedArray a, int srcDensityOverride)
             throws XmlPullParserException {
+        State oldState = mState;
         final Resources r = a.getResources();
         final int srcResId = a.getResourceId(R.styleable.AnimatedImageDrawable_src, 0);
         if (srcResId != 0) {
@@ -172,6 +176,16 @@
             mIntrinsicWidth =  other.mIntrinsicWidth;
             mIntrinsicHeight = other.mIntrinsicHeight;
         }
+
+        mState.mThemeAttrs = a.extractThemeAttrs();
+        if (mState.mNativePtr == 0 && (mState.mThemeAttrs == null
+                || mState.mThemeAttrs[R.styleable.AnimatedImageDrawable_src] == 0)) {
+            throw new XmlPullParserException(a.getPositionDescription() +
+                    ": <animated-image> requires a valid 'src' attribute");
+        }
+
+        mState.mAutoMirrored = a.getBoolean(
+                R.styleable.AnimatedImageDrawable_autoMirrored, oldState.mAutoMirrored);
     }
 
     /**
@@ -225,7 +239,7 @@
 
     @Override
     public void draw(@NonNull Canvas canvas) {
-        if (mState == null) {
+        if (mState.mNativePtr == 0) {
             throw new IllegalStateException("called draw on empty AnimatedImageDrawable");
         }
 
@@ -256,7 +270,7 @@
                    + " 255! provided " + alpha);
         }
 
-        if (mState == null) {
+        if (mState.mNativePtr == 0) {
             throw new IllegalStateException("called setAlpha on empty AnimatedImageDrawable");
         }
 
@@ -266,7 +280,7 @@
 
     @Override
     public int getAlpha() {
-        if (mState == null) {
+        if (mState.mNativePtr == 0) {
             throw new IllegalStateException("called getAlpha on empty AnimatedImageDrawable");
         }
         return nGetAlpha(mState.mNativePtr);
@@ -274,7 +288,7 @@
 
     @Override
     public void setColorFilter(@Nullable ColorFilter colorFilter) {
-        if (mState == null) {
+        if (mState.mNativePtr == 0) {
             throw new IllegalStateException("called setColorFilter on empty AnimatedImageDrawable");
         }
 
@@ -298,11 +312,28 @@
     }
 
     @Override
+    public void setAutoMirrored(boolean mirrored) {
+        if (mState.mAutoMirrored != mirrored) {
+            mState.mAutoMirrored = mirrored;
+            invalidateSelf();
+        }
+    }
+
+    @Override
+    public final boolean isAutoMirrored() {
+        return mState.mAutoMirrored;
+    }
+
+    @Override
     public boolean setVisible(boolean visible, boolean restart) {
         if (!super.setVisible(visible, restart)) {
             return false;
         }
 
+        if (mState.mNativePtr == 0) {
+            throw new IllegalStateException("called setVisible on empty AnimatedImageDrawable");
+        }
+
         if (!visible) {
             nMarkInvisible(mState.mNativePtr);
         }
@@ -319,7 +350,7 @@
      */
     @Override
     public boolean isRunning() {
-        if (mState == null) {
+        if (mState.mNativePtr == 0) {
             throw new IllegalStateException("called isRunning on empty AnimatedImageDrawable");
         }
         return nIsRunning(mState.mNativePtr);
@@ -336,7 +367,7 @@
      */
     @Override
     public void start() {
-        if (mState == null) {
+        if (mState.mNativePtr == 0) {
             throw new IllegalStateException("called start on empty AnimatedImageDrawable");
         }
 
@@ -354,7 +385,7 @@
      */
     @Override
     public void stop() {
-        if (mState == null) {
+        if (mState.mNativePtr == 0) {
             throw new IllegalStateException("called stop on empty AnimatedImageDrawable");
         }
         if (nStop(mState.mNativePtr)) {
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerProvider.java b/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerProvider.java
index 06723c3..24449fd 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerProvider.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerProvider.java
@@ -16,6 +16,8 @@
 
 package com.android.printspooler.model;
 
+import static android.content.Context.BIND_AUTO_CREATE;
+
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -32,7 +34,7 @@
         mContext = context;
         mCallback = callback;
         Intent intent = new Intent(mContext, PrintSpoolerService.class);
-        mContext.bindService(intent, this, 0);
+        mContext.bindService(intent, this, BIND_AUTO_CREATE);
     }
 
     public PrintSpoolerService getSpooler() {
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index d73a5d7..83d7e16 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -306,19 +306,22 @@
         // This will take just a few milliseconds, so just wait to
         // bind to the local service before showing the UI.
         mSpoolerProvider = new PrintSpoolerProvider(this,
-                new Runnable() {
-            @Override
-            public void run() {
-                if (isFinishing() || isDestroyed()) {
-                    // onPause might have not been able to cancel the job, see PrintActivity#onPause
-                    // To be sure, cancel the job again. Double canceling does no harm.
-                    mSpoolerProvider.getSpooler().setPrintJobState(mPrintJob.getId(),
-                            PrintJobInfo.STATE_CANCELED, null);
-                } else {
-                    onConnectedToPrintSpooler(adapter);
-                }
-            }
-        });
+                () -> {
+                    if (isFinishing() || isDestroyed()) {
+                        if (savedInstanceState != null) {
+                            // onPause might have not been able to cancel the job, see
+                            // PrintActivity#onPause
+                            // To be sure, cancel the job again. Double canceling does no harm.
+                            mSpoolerProvider.getSpooler().setPrintJobState(mPrintJob.getId(),
+                                    PrintJobInfo.STATE_CANCELED, null);
+                        }
+                    } else {
+                        if (savedInstanceState == null) {
+                            mSpoolerProvider.getSpooler().createPrintJob(mPrintJob);
+                        }
+                        onConnectedToPrintSpooler(adapter);
+                    }
+                });
 
         getLoaderManager().initLoader(LOADER_ID_ENABLED_PRINT_SERVICES, null, this);
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
old mode 100755
new mode 100644
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
old mode 100755
new mode 100644
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
old mode 100755
new mode 100644
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
old mode 100755
new mode 100644
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
old mode 100755
new mode 100644
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java
old mode 100755
new mode 100644
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
old mode 100755
new mode 100644
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfile.java
old mode 100755
new mode 100644
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java
old mode 100755
new mode 100644
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java
old mode 100755
new mode 100644
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
old mode 100755
new mode 100644
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
old mode 100755
new mode 100644
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java b/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java
old mode 100755
new mode 100644
diff --git a/packages/SystemUI/src/com/android/systemui/net/NetworkOverLimitActivity.java b/packages/SystemUI/src/com/android/systemui/net/NetworkOverLimitActivity.java
index f201165..be3168c 100644
--- a/packages/SystemUI/src/com/android/systemui/net/NetworkOverLimitActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/net/NetworkOverLimitActivity.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.net;
 
+import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE;
+import static android.net.NetworkTemplate.MATCH_MOBILE;
+
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.Dialog;
@@ -32,11 +35,6 @@
 
 import com.android.systemui.R;
 
-import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE;
-import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER;
-import static android.net.NetworkTemplate.MATCH_MOBILE_4G;
-import static android.net.NetworkTemplate.MATCH_MOBILE_ALL;
-
 /**
  * Notify user that a {@link NetworkTemplate} is over its
  * {@link NetworkPolicy#limitBytes}, giving them the choice of acknowledging or
@@ -85,11 +83,7 @@
 
     private static int getLimitedDialogTitleForTemplate(NetworkTemplate template) {
         switch (template.getMatchRule()) {
-            case MATCH_MOBILE_3G_LOWER:
-                return R.string.data_usage_disabled_dialog_3g_title;
-            case MATCH_MOBILE_4G:
-                return R.string.data_usage_disabled_dialog_4g_title;
-            case MATCH_MOBILE_ALL:
+            case MATCH_MOBILE:
                 return R.string.data_usage_disabled_dialog_mobile_title;
             default:
                 return R.string.data_usage_disabled_dialog_title;
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/MultipathPolicyTracker.java b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
similarity index 85%
rename from services/core/java/com/android/server/MultipathPolicyTracker.java
rename to services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
index 9e0a230..296b9ac 100644
--- a/services/core/java/com/android/server/MultipathPolicyTracker.java
+++ b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
@@ -16,11 +16,17 @@
 
 package com.android.server.connectivity;
 
+import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_HANDOVER;
+import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_RELIABILITY;
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+
+import static com.android.server.net.NetworkPolicyManagerInternal.QUOTA_TYPE_MULTIPATH;
+
 import android.app.usage.NetworkStatsManager;
 import android.app.usage.NetworkStatsManager.UsageCallback;
 import android.content.Context;
-import android.net.INetworkStatsService;
-import android.net.INetworkPolicyManager;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
 import android.net.Network;
@@ -31,29 +37,17 @@
 import android.net.NetworkTemplate;
 import android.net.StringNetworkSpecifier;
 import android.os.Handler;
-import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.telephony.TelephonyManager;
 import android.util.DebugUtils;
 import android.util.Slog;
 
-import java.util.Calendar;
-import java.util.concurrent.ConcurrentHashMap;
-
-import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.LocalServices;
 import com.android.server.net.NetworkPolicyManagerInternal;
+import com.android.server.net.NetworkStatsManagerInternal;
 
-import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_HANDOVER;
-import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_RELIABILITY;
-import static android.net.ConnectivityManager.TYPE_MOBILE;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
-import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
-import static android.provider.Settings.Global.NETWORK_AVOID_BAD_WIFI;
-import static android.provider.Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE;
-import static com.android.server.net.NetworkPolicyManagerInternal.QUOTA_TYPE_MULTIPATH;
+import java.util.Calendar;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * Manages multipath data budgets.
@@ -76,10 +70,8 @@
     private final Handler mHandler;
 
     private ConnectivityManager mCM;
-    private NetworkStatsManager mStatsManager;
     private NetworkPolicyManager mNPM;
-    private TelephonyManager mTelephonyManager;
-    private INetworkStatsService mStatsService;
+    private NetworkStatsManager mStatsManager;
 
     private NetworkCallback mMobileNetworkCallback;
     private NetworkPolicyManager.Listener mPolicyListener;
@@ -87,8 +79,6 @@
     // STOPSHIP: replace this with a configurable mechanism.
     private static final long DEFAULT_DAILY_MULTIPATH_QUOTA = 2_500_000;
 
-    private volatile int mMeteredMultipathPreference;
-
     public MultipathPolicyTracker(Context ctx, Handler handler) {
         mContext = ctx;
         mHandler = handler;
@@ -97,12 +87,9 @@
     }
 
     public void start() {
-        mCM = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
-        mNPM = (NetworkPolicyManager) mContext.getSystemService(Context.NETWORK_POLICY_SERVICE);
-        mStatsManager = (NetworkStatsManager) mContext.getSystemService(
-                Context.NETWORK_STATS_SERVICE);
-        mStatsService = INetworkStatsService.Stub.asInterface(
-                ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
+        mCM = mContext.getSystemService(ConnectivityManager.class);
+        mNPM = mContext.getSystemService(NetworkPolicyManager.class);
+        mStatsManager = mContext.getSystemService(NetworkStatsManager.class);
 
         registerTrackMobileCallback();
         registerNetworkPolicyListener();
@@ -119,6 +106,9 @@
 
     // Called on an arbitrary binder thread.
     public Integer getMultipathPreference(Network network) {
+        if (network == null) {
+            return null;
+        }
         MultipathTracker t = mMultipathTrackers.get(network);
         if (t != null) {
             return t.getMultipathPreference();
@@ -149,8 +139,7 @@
                         network, nc, e.getMessage()));
             }
 
-            TelephonyManager tele = (TelephonyManager) mContext.getSystemService(
-                    Context.TELEPHONY_SERVICE);
+            TelephonyManager tele = mContext.getSystemService(TelephonyManager.class);
             if (tele == null) {
                 throw new IllegalStateException(String.format("Missing TelephonyManager"));
             }
@@ -162,7 +151,7 @@
 
             subscriberId = tele.getSubscriberId();
             mNetworkTemplate = new NetworkTemplate(
-                    NetworkTemplate.MATCH_MOBILE_ALL, subscriberId, new String[] { subscriberId },
+                    NetworkTemplate.MATCH_MOBILE, subscriberId, new String[] { subscriberId },
                     null, NetworkStats.METERED_ALL, NetworkStats.ROAMING_ALL,
                     NetworkStats.DEFAULT_NETWORK_NO);
             mUsageCallback = new UsageCallback() {
@@ -185,26 +174,21 @@
             start.set(Calendar.SECOND, 0);
             start.set(Calendar.MILLISECOND, 0);
 
-            long bytes;
             try {
-                // TODO: Consider using NetworkStatsManager.getSummaryForDevice instead.
-                bytes = mStatsService.getNetworkTotalBytes(mNetworkTemplate,
-                        start.getTimeInMillis(), end.getTimeInMillis());
-                if (DBG) Slog.w(TAG, "Non-default data usage: " + bytes);
-            } catch (RemoteException e) {
-                Slog.w(TAG, "Can't fetch daily data usage: " + e);
-                bytes = -1;
-            } catch (IllegalStateException e) {
-                // Bandwidth control disabled?
-                bytes = -1;
+                final long bytes = LocalServices.getService(NetworkStatsManagerInternal.class)
+                        .getNetworkTotalBytes(mNetworkTemplate, start.getTimeInMillis(),
+                                end.getTimeInMillis());
+                if (DBG) Slog.d(TAG, "Non-default data usage: " + bytes);
+                return bytes;
+            } catch (RuntimeException e) {
+                Slog.w(TAG, "Failed to get data usage: " + e);
+                return -1;
             }
-            return bytes;
         }
 
         void updateMultipathBudget() {
-            NetworkPolicyManagerInternal npms = LocalServices.getService(
-                    NetworkPolicyManagerInternal.class);
-            long quota = npms.getSubscriptionOpportunisticQuota(this.network, QUOTA_TYPE_MULTIPATH);
+            long quota = LocalServices.getService(NetworkPolicyManagerInternal.class)
+                    .getSubscriptionOpportunisticQuota(this.network, QUOTA_TYPE_MULTIPATH);
             if (DBG) Slog.d(TAG, "Opportunistic quota from data plan: " + quota + " bytes");
 
             if (quota == 0) {
@@ -223,8 +207,10 @@
             }
             mQuota = quota;
 
-            long usage = getDailyNonDefaultDataUsage();
-            long budget = Math.max(0, quota - usage);
+            // If we can't get current usage, assume the worst and don't give
+            // ourselves any budget to work with.
+            final long usage = getDailyNonDefaultDataUsage();
+            final long budget = (usage == -1) ? 0 : Math.max(0, quota - usage);
             if (budget > 0) {
                 if (DBG) Slog.d(TAG, "Setting callback for " + budget +
                         " bytes on network " + network);
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index f3f4dfd..740866c 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;
 
 /**
@@ -163,15 +164,16 @@
      * {@link JobStatus#getServiceToken()}
      */
     final List<JobServiceContext> mActiveServices = new ArrayList<>();
+
     /** List of controllers that will notify this service of updates to jobs. */
-    List<StateController> mControllers;
+    private final List<StateController> mControllers;
     /** Need direct access to this for testing. */
-    BatteryController mBatteryController;
+    private final BatteryController mBatteryController;
     /** Need direct access to this for testing. */
-    StorageController mStorageController;
+    private final StorageController mStorageController;
     /** Need directly for sending uid state changes */
-    private BackgroundJobsController mBackgroundJobsController;
-    private DeviceIdleJobsController mDeviceIdleJobsController;
+    private final DeviceIdleJobsController mDeviceIdleJobsController;
+
     /**
      * Queue of pending jobs. The JobServiceContext class will receive jobs from this list
      * when ready to execute them.
@@ -253,12 +255,48 @@
      */
     int[] mTmpAssignPreferredUidForContext = new int[MAX_JOB_CONTEXTS_COUNT];
 
+    private class ConstantsObserver extends ContentObserver {
+        private ContentResolver mResolver;
+
+        public ConstantsObserver(Handler handler) {
+            super(handler);
+        }
+
+        public void start(ContentResolver resolver) {
+            mResolver = resolver;
+            mResolver.registerContentObserver(Settings.Global.getUriFor(
+                    Settings.Global.JOB_SCHEDULER_CONSTANTS), false, this);
+            updateConstants();
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            updateConstants();
+        }
+
+        private void updateConstants() {
+            synchronized (mLock) {
+                try {
+                    mConstants.updateConstantsLocked(Settings.Global.getString(mResolver,
+                            Settings.Global.JOB_SCHEDULER_CONSTANTS));
+                } catch (IllegalArgumentException e) {
+                    // Failed to parse the settings string, log this and move on
+                    // with defaults.
+                    Slog.e(TAG, "Bad jobscheduler settings", e);
+                }
+            }
+
+            // Reset the heartbeat alarm based on the new heartbeat duration
+            setNextHeartbeatAlarm();
+        }
+    }
+
     /**
      * All times are in milliseconds. These constants are kept synchronized with the system
      * global Settings. Any access to this class or its fields should be done while
      * holding the JobSchedulerService.mLock lock.
      */
-    private final class Constants extends ContentObserver {
+    public static class Constants {
         // Key names stored in the settings value.
         private static final String KEY_MIN_IDLE_COUNT = "min_idle_count";
         private static final String KEY_MIN_CHARGING_COUNT = "min_charging_count";
@@ -283,6 +321,8 @@
         private static final String KEY_STANDBY_WORKING_BEATS = "standby_working_beats";
         private static final String KEY_STANDBY_FREQUENT_BEATS = "standby_frequent_beats";
         private static final String KEY_STANDBY_RARE_BEATS = "standby_rare_beats";
+        private static final String KEY_CONN_CONGESTION_DELAY_FRAC = "conn_congestion_delay_frac";
+        private static final String KEY_CONN_PREFETCH_RELAX_FRAC = "conn_prefetch_relax_frac";
 
         private static final int DEFAULT_MIN_IDLE_COUNT = 1;
         private static final int DEFAULT_MIN_CHARGING_COUNT = 1;
@@ -306,6 +346,8 @@
         private static final int DEFAULT_STANDBY_WORKING_BEATS = 11;  // ~ 2 hours, with 11min beats
         private static final int DEFAULT_STANDBY_FREQUENT_BEATS = 43; // ~ 8 hours
         private static final int DEFAULT_STANDBY_RARE_BEATS = 130; // ~ 24 hours
+        private static final float DEFAULT_CONN_CONGESTION_DELAY_FRAC = 0.5f;
+        private static final float DEFAULT_CONN_PREFETCH_RELAX_FRAC = 0.5f;
 
         /**
          * Minimum # of idle jobs that must be ready in order to force the JMS to schedule things
@@ -400,7 +442,6 @@
          * hour or day, so that the heartbeat drifts relative to wall-clock milestones.
          */
         long STANDBY_HEARTBEAT_TIME = DEFAULT_STANDBY_HEARTBEAT_TIME;
-
         /**
          * Mapping: standby bucket -> number of heartbeats between each sweep of that
          * bucket's jobs.
@@ -415,171 +456,126 @@
                 DEFAULT_STANDBY_FREQUENT_BEATS,
                 DEFAULT_STANDBY_RARE_BEATS
         };
+        /**
+         * The fraction of a job's running window that must pass before we
+         * consider running it when the network is congested.
+         */
+        public float CONN_CONGESTION_DELAY_FRAC = DEFAULT_CONN_CONGESTION_DELAY_FRAC;
+        /**
+         * The fraction of a prefetch job's running window that must pass before
+         * we consider matching it against a metered network.
+         */
+        public float CONN_PREFETCH_RELAX_FRAC = DEFAULT_CONN_PREFETCH_RELAX_FRAC;
 
-        private ContentResolver mResolver;
         private final KeyValueListParser mParser = new KeyValueListParser(',');
 
-        public Constants(Handler handler) {
-            super(handler);
-        }
-
-        public void start(ContentResolver resolver) {
-            mResolver = resolver;
-            mResolver.registerContentObserver(Settings.Global.getUriFor(
-                    Settings.Global.JOB_SCHEDULER_CONSTANTS), false, this);
-            updateConstants();
-        }
-
-        @Override
-        public void onChange(boolean selfChange, Uri uri) {
-            updateConstants();
-        }
-
-        private void updateConstants() {
-            synchronized (mLock) {
-                try {
-                    mParser.setString(Settings.Global.getString(mResolver,
-                            Settings.Global.JOB_SCHEDULER_CONSTANTS));
-                } catch (IllegalArgumentException e) {
-                    // Failed to parse the settings string, log this and move on
-                    // with defaults.
-                    Slog.e(TAG, "Bad jobscheduler settings", e);
-                }
-
-                MIN_IDLE_COUNT = mParser.getInt(KEY_MIN_IDLE_COUNT,
-                        DEFAULT_MIN_IDLE_COUNT);
-                MIN_CHARGING_COUNT = mParser.getInt(KEY_MIN_CHARGING_COUNT,
-                        DEFAULT_MIN_CHARGING_COUNT);
-                MIN_BATTERY_NOT_LOW_COUNT = mParser.getInt(KEY_MIN_BATTERY_NOT_LOW_COUNT,
-                        DEFAULT_MIN_BATTERY_NOT_LOW_COUNT);
-                MIN_STORAGE_NOT_LOW_COUNT = mParser.getInt(KEY_MIN_STORAGE_NOT_LOW_COUNT,
-                        DEFAULT_MIN_STORAGE_NOT_LOW_COUNT);
-                MIN_CONNECTIVITY_COUNT = mParser.getInt(KEY_MIN_CONNECTIVITY_COUNT,
-                        DEFAULT_MIN_CONNECTIVITY_COUNT);
-                MIN_CONTENT_COUNT = mParser.getInt(KEY_MIN_CONTENT_COUNT,
-                        DEFAULT_MIN_CONTENT_COUNT);
-                MIN_READY_JOBS_COUNT = mParser.getInt(KEY_MIN_READY_JOBS_COUNT,
-                        DEFAULT_MIN_READY_JOBS_COUNT);
-                HEAVY_USE_FACTOR = mParser.getFloat(KEY_HEAVY_USE_FACTOR,
-                        DEFAULT_HEAVY_USE_FACTOR);
-                MODERATE_USE_FACTOR = mParser.getFloat(KEY_MODERATE_USE_FACTOR,
-                        DEFAULT_MODERATE_USE_FACTOR);
-                FG_JOB_COUNT = mParser.getInt(KEY_FG_JOB_COUNT,
-                        DEFAULT_FG_JOB_COUNT);
-                BG_NORMAL_JOB_COUNT = mParser.getInt(KEY_BG_NORMAL_JOB_COUNT,
-                        DEFAULT_BG_NORMAL_JOB_COUNT);
-                if ((FG_JOB_COUNT+BG_NORMAL_JOB_COUNT) > MAX_JOB_CONTEXTS_COUNT) {
-                    BG_NORMAL_JOB_COUNT = MAX_JOB_CONTEXTS_COUNT - FG_JOB_COUNT;
-                }
-                BG_MODERATE_JOB_COUNT = mParser.getInt(KEY_BG_MODERATE_JOB_COUNT,
-                        DEFAULT_BG_MODERATE_JOB_COUNT);
-                if ((FG_JOB_COUNT+BG_MODERATE_JOB_COUNT) > MAX_JOB_CONTEXTS_COUNT) {
-                    BG_MODERATE_JOB_COUNT = MAX_JOB_CONTEXTS_COUNT - FG_JOB_COUNT;
-                }
-                BG_LOW_JOB_COUNT = mParser.getInt(KEY_BG_LOW_JOB_COUNT,
-                        DEFAULT_BG_LOW_JOB_COUNT);
-                if ((FG_JOB_COUNT+BG_LOW_JOB_COUNT) > MAX_JOB_CONTEXTS_COUNT) {
-                    BG_LOW_JOB_COUNT = MAX_JOB_CONTEXTS_COUNT - FG_JOB_COUNT;
-                }
-                BG_CRITICAL_JOB_COUNT = mParser.getInt(KEY_BG_CRITICAL_JOB_COUNT,
-                        DEFAULT_BG_CRITICAL_JOB_COUNT);
-                if ((FG_JOB_COUNT+BG_CRITICAL_JOB_COUNT) > MAX_JOB_CONTEXTS_COUNT) {
-                    BG_CRITICAL_JOB_COUNT = MAX_JOB_CONTEXTS_COUNT - FG_JOB_COUNT;
-                }
-                MAX_STANDARD_RESCHEDULE_COUNT = mParser.getInt(KEY_MAX_STANDARD_RESCHEDULE_COUNT,
-                        DEFAULT_MAX_STANDARD_RESCHEDULE_COUNT);
-                MAX_WORK_RESCHEDULE_COUNT = mParser.getInt(KEY_MAX_WORK_RESCHEDULE_COUNT,
-                        DEFAULT_MAX_WORK_RESCHEDULE_COUNT);
-                MIN_LINEAR_BACKOFF_TIME = mParser.getDurationMillis(KEY_MIN_LINEAR_BACKOFF_TIME,
-                        DEFAULT_MIN_LINEAR_BACKOFF_TIME);
-                MIN_EXP_BACKOFF_TIME = mParser.getDurationMillis(KEY_MIN_EXP_BACKOFF_TIME,
-                        DEFAULT_MIN_EXP_BACKOFF_TIME);
-                STANDBY_HEARTBEAT_TIME = mParser.getDurationMillis(KEY_STANDBY_HEARTBEAT_TIME,
-                        DEFAULT_STANDBY_HEARTBEAT_TIME);
-                STANDBY_BEATS[1] = mParser.getInt(KEY_STANDBY_WORKING_BEATS,
-                        DEFAULT_STANDBY_WORKING_BEATS);
-                STANDBY_BEATS[2] = mParser.getInt(KEY_STANDBY_FREQUENT_BEATS,
-                        DEFAULT_STANDBY_FREQUENT_BEATS);
-                STANDBY_BEATS[3] = mParser.getInt(KEY_STANDBY_RARE_BEATS,
-                        DEFAULT_STANDBY_RARE_BEATS);
+        void updateConstantsLocked(String value) {
+            try {
+                mParser.setString(value);
+            } catch (Exception e) {
+                // Failed to parse the settings string, log this and move on
+                // with defaults.
+                Slog.e(TAG, "Bad jobscheduler settings", e);
             }
 
-            // Reset the heartbeat alarm based on the new heartbeat duration
-            setNextHeartbeatAlarm();
+            MIN_IDLE_COUNT = mParser.getInt(KEY_MIN_IDLE_COUNT,
+                    DEFAULT_MIN_IDLE_COUNT);
+            MIN_CHARGING_COUNT = mParser.getInt(KEY_MIN_CHARGING_COUNT,
+                    DEFAULT_MIN_CHARGING_COUNT);
+            MIN_BATTERY_NOT_LOW_COUNT = mParser.getInt(KEY_MIN_BATTERY_NOT_LOW_COUNT,
+                    DEFAULT_MIN_BATTERY_NOT_LOW_COUNT);
+            MIN_STORAGE_NOT_LOW_COUNT = mParser.getInt(KEY_MIN_STORAGE_NOT_LOW_COUNT,
+                    DEFAULT_MIN_STORAGE_NOT_LOW_COUNT);
+            MIN_CONNECTIVITY_COUNT = mParser.getInt(KEY_MIN_CONNECTIVITY_COUNT,
+                    DEFAULT_MIN_CONNECTIVITY_COUNT);
+            MIN_CONTENT_COUNT = mParser.getInt(KEY_MIN_CONTENT_COUNT,
+                    DEFAULT_MIN_CONTENT_COUNT);
+            MIN_READY_JOBS_COUNT = mParser.getInt(KEY_MIN_READY_JOBS_COUNT,
+                    DEFAULT_MIN_READY_JOBS_COUNT);
+            HEAVY_USE_FACTOR = mParser.getFloat(KEY_HEAVY_USE_FACTOR,
+                    DEFAULT_HEAVY_USE_FACTOR);
+            MODERATE_USE_FACTOR = mParser.getFloat(KEY_MODERATE_USE_FACTOR,
+                    DEFAULT_MODERATE_USE_FACTOR);
+            FG_JOB_COUNT = mParser.getInt(KEY_FG_JOB_COUNT,
+                    DEFAULT_FG_JOB_COUNT);
+            BG_NORMAL_JOB_COUNT = mParser.getInt(KEY_BG_NORMAL_JOB_COUNT,
+                    DEFAULT_BG_NORMAL_JOB_COUNT);
+            if ((FG_JOB_COUNT+BG_NORMAL_JOB_COUNT) > MAX_JOB_CONTEXTS_COUNT) {
+                BG_NORMAL_JOB_COUNT = MAX_JOB_CONTEXTS_COUNT - FG_JOB_COUNT;
+            }
+            BG_MODERATE_JOB_COUNT = mParser.getInt(KEY_BG_MODERATE_JOB_COUNT,
+                    DEFAULT_BG_MODERATE_JOB_COUNT);
+            if ((FG_JOB_COUNT+BG_MODERATE_JOB_COUNT) > MAX_JOB_CONTEXTS_COUNT) {
+                BG_MODERATE_JOB_COUNT = MAX_JOB_CONTEXTS_COUNT - FG_JOB_COUNT;
+            }
+            BG_LOW_JOB_COUNT = mParser.getInt(KEY_BG_LOW_JOB_COUNT,
+                    DEFAULT_BG_LOW_JOB_COUNT);
+            if ((FG_JOB_COUNT+BG_LOW_JOB_COUNT) > MAX_JOB_CONTEXTS_COUNT) {
+                BG_LOW_JOB_COUNT = MAX_JOB_CONTEXTS_COUNT - FG_JOB_COUNT;
+            }
+            BG_CRITICAL_JOB_COUNT = mParser.getInt(KEY_BG_CRITICAL_JOB_COUNT,
+                    DEFAULT_BG_CRITICAL_JOB_COUNT);
+            if ((FG_JOB_COUNT+BG_CRITICAL_JOB_COUNT) > MAX_JOB_CONTEXTS_COUNT) {
+                BG_CRITICAL_JOB_COUNT = MAX_JOB_CONTEXTS_COUNT - FG_JOB_COUNT;
+            }
+            MAX_STANDARD_RESCHEDULE_COUNT = mParser.getInt(KEY_MAX_STANDARD_RESCHEDULE_COUNT,
+                    DEFAULT_MAX_STANDARD_RESCHEDULE_COUNT);
+            MAX_WORK_RESCHEDULE_COUNT = mParser.getInt(KEY_MAX_WORK_RESCHEDULE_COUNT,
+                    DEFAULT_MAX_WORK_RESCHEDULE_COUNT);
+            MIN_LINEAR_BACKOFF_TIME = mParser.getDurationMillis(KEY_MIN_LINEAR_BACKOFF_TIME,
+                    DEFAULT_MIN_LINEAR_BACKOFF_TIME);
+            MIN_EXP_BACKOFF_TIME = mParser.getDurationMillis(KEY_MIN_EXP_BACKOFF_TIME,
+                    DEFAULT_MIN_EXP_BACKOFF_TIME);
+            STANDBY_HEARTBEAT_TIME = mParser.getDurationMillis(KEY_STANDBY_HEARTBEAT_TIME,
+                    DEFAULT_STANDBY_HEARTBEAT_TIME);
+            STANDBY_BEATS[1] = mParser.getInt(KEY_STANDBY_WORKING_BEATS,
+                    DEFAULT_STANDBY_WORKING_BEATS);
+            STANDBY_BEATS[2] = mParser.getInt(KEY_STANDBY_FREQUENT_BEATS,
+                    DEFAULT_STANDBY_FREQUENT_BEATS);
+            STANDBY_BEATS[3] = mParser.getInt(KEY_STANDBY_RARE_BEATS,
+                    DEFAULT_STANDBY_RARE_BEATS);
+            CONN_CONGESTION_DELAY_FRAC = mParser.getFloat(KEY_CONN_CONGESTION_DELAY_FRAC,
+                    DEFAULT_CONN_CONGESTION_DELAY_FRAC);
+            CONN_PREFETCH_RELAX_FRAC = mParser.getFloat(KEY_CONN_PREFETCH_RELAX_FRAC,
+                    DEFAULT_CONN_PREFETCH_RELAX_FRAC);
         }
 
-        void dump(PrintWriter pw) {
-            pw.println("  Settings:");
-
-            pw.print("    "); pw.print(KEY_MIN_IDLE_COUNT); pw.print("=");
-            pw.print(MIN_IDLE_COUNT); pw.println();
-
-            pw.print("    "); pw.print(KEY_MIN_CHARGING_COUNT); pw.print("=");
-            pw.print(MIN_CHARGING_COUNT); pw.println();
-
-            pw.print("    "); pw.print(KEY_MIN_BATTERY_NOT_LOW_COUNT); pw.print("=");
-            pw.print(MIN_BATTERY_NOT_LOW_COUNT); pw.println();
-
-            pw.print("    "); pw.print(KEY_MIN_STORAGE_NOT_LOW_COUNT); pw.print("=");
-            pw.print(MIN_STORAGE_NOT_LOW_COUNT); pw.println();
-
-            pw.print("    "); pw.print(KEY_MIN_CONNECTIVITY_COUNT); pw.print("=");
-            pw.print(MIN_CONNECTIVITY_COUNT); pw.println();
-
-            pw.print("    "); pw.print(KEY_MIN_CONTENT_COUNT); pw.print("=");
-            pw.print(MIN_CONTENT_COUNT); pw.println();
-
-            pw.print("    "); pw.print(KEY_MIN_READY_JOBS_COUNT); pw.print("=");
-            pw.print(MIN_READY_JOBS_COUNT); pw.println();
-
-            pw.print("    "); pw.print(KEY_HEAVY_USE_FACTOR); pw.print("=");
-            pw.print(HEAVY_USE_FACTOR); pw.println();
-
-            pw.print("    "); pw.print(KEY_MODERATE_USE_FACTOR); pw.print("=");
-            pw.print(MODERATE_USE_FACTOR); pw.println();
-
-            pw.print("    "); pw.print(KEY_FG_JOB_COUNT); pw.print("=");
-            pw.print(FG_JOB_COUNT); pw.println();
-
-            pw.print("    "); pw.print(KEY_BG_NORMAL_JOB_COUNT); pw.print("=");
-            pw.print(BG_NORMAL_JOB_COUNT); pw.println();
-
-            pw.print("    "); pw.print(KEY_BG_MODERATE_JOB_COUNT); pw.print("=");
-            pw.print(BG_MODERATE_JOB_COUNT); pw.println();
-
-            pw.print("    "); pw.print(KEY_BG_LOW_JOB_COUNT); pw.print("=");
-            pw.print(BG_LOW_JOB_COUNT); pw.println();
-
-            pw.print("    "); pw.print(KEY_BG_CRITICAL_JOB_COUNT); pw.print("=");
-            pw.print(BG_CRITICAL_JOB_COUNT); pw.println();
-
-            pw.print("    "); pw.print(KEY_MAX_STANDARD_RESCHEDULE_COUNT); pw.print("=");
-            pw.print(MAX_STANDARD_RESCHEDULE_COUNT); pw.println();
-
-            pw.print("    "); pw.print(KEY_MAX_WORK_RESCHEDULE_COUNT); pw.print("=");
-            pw.print(MAX_WORK_RESCHEDULE_COUNT); pw.println();
-
-            pw.print("    "); pw.print(KEY_MIN_LINEAR_BACKOFF_TIME); pw.print("=");
-            pw.print(MIN_LINEAR_BACKOFF_TIME); pw.println();
-
-            pw.print("    "); pw.print(KEY_MIN_EXP_BACKOFF_TIME); pw.print("=");
-            pw.print(MIN_EXP_BACKOFF_TIME); pw.println();
-
-            pw.print("    "); pw.print(KEY_STANDBY_HEARTBEAT_TIME); pw.print("=");
-            pw.print(STANDBY_HEARTBEAT_TIME); pw.println();
-
-            pw.print("    standby_beats={");
+        void dump(IndentingPrintWriter pw) {
+            pw.println("Settings:");
+            pw.increaseIndent();
+            pw.printPair(KEY_MIN_IDLE_COUNT, MIN_IDLE_COUNT).println();
+            pw.printPair(KEY_MIN_CHARGING_COUNT, MIN_CHARGING_COUNT).println();
+            pw.printPair(KEY_MIN_BATTERY_NOT_LOW_COUNT, MIN_BATTERY_NOT_LOW_COUNT).println();
+            pw.printPair(KEY_MIN_STORAGE_NOT_LOW_COUNT, MIN_STORAGE_NOT_LOW_COUNT).println();
+            pw.printPair(KEY_MIN_CONNECTIVITY_COUNT, MIN_CONNECTIVITY_COUNT).println();
+            pw.printPair(KEY_MIN_CONTENT_COUNT, MIN_CONTENT_COUNT).println();
+            pw.printPair(KEY_MIN_READY_JOBS_COUNT, MIN_READY_JOBS_COUNT).println();
+            pw.printPair(KEY_HEAVY_USE_FACTOR, HEAVY_USE_FACTOR).println();
+            pw.printPair(KEY_MODERATE_USE_FACTOR, MODERATE_USE_FACTOR).println();
+            pw.printPair(KEY_FG_JOB_COUNT, FG_JOB_COUNT).println();
+            pw.printPair(KEY_BG_NORMAL_JOB_COUNT, BG_NORMAL_JOB_COUNT).println();
+            pw.printPair(KEY_BG_MODERATE_JOB_COUNT, BG_MODERATE_JOB_COUNT).println();
+            pw.printPair(KEY_BG_LOW_JOB_COUNT, BG_LOW_JOB_COUNT).println();
+            pw.printPair(KEY_BG_CRITICAL_JOB_COUNT, BG_CRITICAL_JOB_COUNT).println();
+            pw.printPair(KEY_MAX_STANDARD_RESCHEDULE_COUNT, MAX_STANDARD_RESCHEDULE_COUNT).println();
+            pw.printPair(KEY_MAX_WORK_RESCHEDULE_COUNT, MAX_WORK_RESCHEDULE_COUNT).println();
+            pw.printPair(KEY_MIN_LINEAR_BACKOFF_TIME, MIN_LINEAR_BACKOFF_TIME).println();
+            pw.printPair(KEY_MIN_EXP_BACKOFF_TIME, MIN_EXP_BACKOFF_TIME).println();
+            pw.printPair(KEY_STANDBY_HEARTBEAT_TIME, STANDBY_HEARTBEAT_TIME).println();
+            pw.print("standby_beats={");
             pw.print(STANDBY_BEATS[0]);
             for (int i = 1; i < STANDBY_BEATS.length; i++) {
                 pw.print(", ");
                 pw.print(STANDBY_BEATS[i]);
             }
             pw.println('}');
+            pw.printPair(KEY_CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC).println();
+            pw.printPair(KEY_CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC).println();
+            pw.decreaseIndent();
         }
 
         void dump(ProtoOutputStream proto, long fieldId) {
             final long token = proto.start(fieldId);
-
             proto.write(ConstantsProto.MIN_IDLE_COUNT, MIN_IDLE_COUNT);
             proto.write(ConstantsProto.MIN_CHARGING_COUNT, MIN_CHARGING_COUNT);
             proto.write(ConstantsProto.MIN_BATTERY_NOT_LOW_COUNT, MIN_BATTERY_NOT_LOW_COUNT);
@@ -599,16 +595,17 @@
             proto.write(ConstantsProto.MIN_LINEAR_BACKOFF_TIME_MS, MIN_LINEAR_BACKOFF_TIME);
             proto.write(ConstantsProto.MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME);
             proto.write(ConstantsProto.STANDBY_HEARTBEAT_TIME_MS, STANDBY_HEARTBEAT_TIME);
-
             for (int period : STANDBY_BEATS) {
                 proto.write(ConstantsProto.STANDBY_BEATS, period);
             }
-
+            proto.write(ConstantsProto.CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC);
+            proto.write(ConstantsProto.CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC);
             proto.end(token);
         }
     }
 
     final Constants mConstants;
+    final ConstantsObserver mConstantsObserver;
 
     static final Comparator<JobStatus> mEnqueueTimeComparator = (o1, o2) -> {
         if (o1.enqueueTime < o2.enqueueTime) {
@@ -778,6 +775,10 @@
         return mJobs;
     }
 
+    public Constants getConstants() {
+        return mConstants;
+    }
+
     @Override
     public void onStartUser(int userHandle) {
         mStartedUsers = ArrayUtils.appendInt(mStartedUsers, userHandle);
@@ -1097,7 +1098,8 @@
                 LocalServices.getService(ActivityManagerInternal.class));
 
         mHandler = new JobHandler(context.getMainLooper());
-        mConstants = new Constants(mHandler);
+        mConstants = new Constants();
+        mConstantsObserver = new ConstantsObserver(mHandler);
         mJobSchedulerStub = new JobSchedulerStub();
 
         // Set up the app standby bucketing tracker
@@ -1113,17 +1115,17 @@
 
         // Create the controllers.
         mControllers = new ArrayList<StateController>();
-        mControllers.add(ConnectivityController.get(this));
-        mControllers.add(TimeController.get(this));
-        mControllers.add(IdleController.get(this));
-        mBatteryController = BatteryController.get(this);
+        mControllers.add(new ConnectivityController(this));
+        mControllers.add(new TimeController(this));
+        mControllers.add(new IdleController(this));
+        mBatteryController = new BatteryController(this);
         mControllers.add(mBatteryController);
-        mStorageController = StorageController.get(this);
+        mStorageController = new StorageController(this);
         mControllers.add(mStorageController);
-        mControllers.add(BackgroundJobsController.get(this));
-        mControllers.add(AppIdleController.get(this));
-        mControllers.add(ContentObserverController.get(this));
-        mDeviceIdleJobsController = DeviceIdleJobsController.get(this);
+        mControllers.add(new BackgroundJobsController(this));
+        mControllers.add(new AppIdleController(this));
+        mControllers.add(new ContentObserverController(this));
+        mDeviceIdleJobsController = new DeviceIdleJobsController(this);
         mControllers.add(mDeviceIdleJobsController);
 
         // If the job store determined that it can't yet reschedule persisted jobs,
@@ -1184,7 +1186,7 @@
     @Override
     public void onBootPhase(int phase) {
         if (PHASE_SYSTEM_SERVICES_READY == phase) {
-            mConstants.start(getContext().getContentResolver());
+            mConstantsObserver.start(getContext().getContentResolver());
 
             mAppStateTracker = Preconditions.checkNotNull(
                     LocalServices.getService(AppStateTracker.class));
@@ -1227,13 +1229,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 +1601,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 +1639,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 +1655,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 +2172,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 +2303,7 @@
         }
     }
 
-    static class DeferredJobCounter implements JobStatusFunctor {
+    static class DeferredJobCounter implements Consumer<JobStatus> {
         private int mDeferred = 0;
 
         public int numDeferred() {
@@ -2315,7 +2311,7 @@
         }
 
         @Override
-        public void process(JobStatus job) {
+        public void accept(JobStatus job) {
             if (job.getWhenStandbyDeferred() > 0) {
                 mDeferred++;
             }
@@ -2596,12 +2592,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 +2897,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 +2920,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 +2954,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 +3060,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 +3078,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 +3111,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..bd8fe28 100644
--- a/services/core/java/com/android/server/job/controllers/AppIdleController.java
+++ b/services/core/java/com/android/server/job/controllers/AppIdleController.java
@@ -17,18 +17,18 @@
 package com.android.server.job.controllers;
 
 import android.app.usage.UsageStatsManagerInternal;
-import android.content.Context;
 import android.os.UserHandle;
 import android.util.Log;
 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
@@ -41,18 +41,15 @@
     private static final boolean DEBUG = JobSchedulerService.DEBUG
             || Log.isLoggable(TAG, Log.DEBUG);
 
-    // Singleton factory
-    private static Object sCreationLock = new Object();
-    private static volatile AppIdleController sController;
-    private final JobSchedulerService mJobSchedulerService;
     private final UsageStatsManagerInternal mUsageStatsInternal;
     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 +60,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 +74,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,21 +87,10 @@
                 }
             }
         }
-    };
-
-    public static AppIdleController get(JobSchedulerService service) {
-        synchronized (sCreationLock) {
-            if (sController == null) {
-                sController = new AppIdleController(service, service.getContext(),
-                        service.getLock());
-            }
-            return sController;
-        }
     }
 
-    private AppIdleController(JobSchedulerService service, Context context, Object lock) {
-        super(service, context, lock);
-        mJobSchedulerService = service;
+    public AppIdleController(JobSchedulerService service) {
+        super(service);
         mUsageStatsInternal = LocalServices.getService(UsageStatsManagerInternal.class);
         mAppIdleParoleOn = true;
         mUsageStatsInternal.addAppIdleStateChangeListener(new AppIdleStateChangeListener());
@@ -131,56 +118,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();
+
+        mService.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);
-            }
+        mService.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);
@@ -196,7 +173,7 @@
             }
             mAppIdleParoleOn = isAppIdleParoleOn;
             GlobalUpdateFunc update = new GlobalUpdateFunc();
-            mJobSchedulerService.getJobStore().forEachJob(update);
+            mService.getJobStore().forEachJob(update);
             if (update.mChanged) {
                 changed = true;
             }
@@ -217,7 +194,7 @@
                 }
 
                 PackageUpdateFunc update = new PackageUpdateFunc(userId, packageName, idle);
-                mJobSchedulerService.getJobStore().forEachJob(update);
+                mService.getJobStore().forEachJob(update);
                 if (update.mChanged) {
                     changed = true;
                 }
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..36e7511 100644
--- a/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java
+++ b/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java
@@ -16,50 +16,33 @@
 
 package com.android.server.job.controllers;
 
-import android.content.Context;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.util.Log;
 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";
     private static final boolean DEBUG = JobSchedulerService.DEBUG
             || Log.isLoggable(TAG, Log.DEBUG);
 
-    // Singleton factory
-    private static final Object sCreationLock = new Object();
-    private static volatile BackgroundJobsController sController;
-
-    private final JobSchedulerService mJobSchedulerService;
-
     private final AppStateTracker mAppStateTracker;
 
-    public static BackgroundJobsController get(JobSchedulerService service) {
-        synchronized (sCreationLock) {
-            if (sController == null) {
-                sController = new BackgroundJobsController(service, service.getContext(),
-                        service.getLock());
-            }
-            return sController;
-        }
-    }
-
-    private BackgroundJobsController(JobSchedulerService service, Context context, Object lock) {
-        super(service, context, lock);
-        mJobSchedulerService = service;
+    public BackgroundJobsController(JobSchedulerService service) {
+        super(service);
 
         mAppStateTracker = Preconditions.checkNotNull(
                 LocalServices.getService(AppStateTracker.class));
@@ -77,19 +60,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;
-            }
+        mService.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 +94,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;
-            }
+        mService.getJobStore().forEachJob(predicate, (jobStatus) -> {
             final long jsToken =
                     proto.start(StateControllerProto.BackgroundJobsController.TRACKED_JOBS);
 
@@ -176,7 +153,7 @@
 
         final long start = DEBUG ? SystemClock.elapsedRealtimeNanos() : 0;
 
-        mJobSchedulerService.getJobStore().forEachJob(updateTrackedJobs);
+        mService.getJobStore().forEachJob(updateTrackedJobs);
 
         final long time = DEBUG ? (SystemClock.elapsedRealtimeNanos() - start) : 0;
         if (DEBUG) {
@@ -205,7 +182,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 +194,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..46658ad 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,12 @@
 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
@@ -48,36 +48,16 @@
     private static final boolean DEBUG = JobSchedulerService.DEBUG
             || Log.isLoggable(TAG, Log.DEBUG);
 
-    private static final Object sCreationLock = new Object();
-    private static volatile BatteryController sController;
-
     private final ArraySet<JobStatus> mTrackedTasks = new ArraySet<>();
     private ChargingTracker mChargeTracker;
 
-    public static BatteryController get(JobSchedulerService taskManagerService) {
-        synchronized (sCreationLock) {
-            if (sController == null) {
-                sController = new BatteryController(taskManagerService,
-                        taskManagerService.getContext(), taskManagerService.getLock());
-            }
-        }
-        return sController;
-    }
-
     @VisibleForTesting
     public ChargingTracker getTracker() {
         return mChargeTracker;
     }
 
-    @VisibleForTesting
-    public static BatteryController getForTesting(StateChangedListener stateChangedListener,
-                                           Context context) {
-        return new BatteryController(stateChangedListener, context, new Object());
-    }
-
-    private BatteryController(StateChangedListener stateChangedListener, Context context,
-            Object lock) {
-        super(stateChangedListener, context, lock);
+    public BatteryController(JobSchedulerService service) {
+        super(service);
         mChargeTracker = new ChargingTracker();
         mChargeTracker.startTracking();
     }
@@ -244,24 +224,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 +249,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 +266,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..abe55bb 100644
--- a/services/core/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
@@ -21,7 +21,6 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
 
 import android.app.job.JobInfo;
-import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
 import android.net.INetworkPolicyListener;
@@ -41,12 +40,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.JobSchedulerService.Constants;
 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.
@@ -68,22 +68,8 @@
     @GuardedBy("mLock")
     private final ArraySet<JobStatus> mTrackedJobs = new ArraySet<>();
 
-    /** Singleton. */
-    private static ConnectivityController sSingleton;
-    private static Object sCreationLock = new Object();
-
-    public static ConnectivityController get(JobSchedulerService jms) {
-        synchronized (sCreationLock) {
-            if (sSingleton == null) {
-                sSingleton = new ConnectivityController(jms, jms.getContext(), jms.getLock());
-            }
-            return sSingleton;
-        }
-    }
-
-    private ConnectivityController(StateChangedListener stateChangedListener, Context context,
-            Object lock) {
-        super(stateChangedListener, context, lock);
+    public ConnectivityController(JobSchedulerService service) {
+        super(service);
 
         mConnManager = mContext.getSystemService(ConnectivityManager.class);
         mNetPolicyManager = mContext.getSystemService(NetworkPolicyManager.class);
@@ -122,7 +108,7 @@
      */
     @SuppressWarnings("unused")
     private static boolean isInsane(JobStatus jobStatus, Network network,
-            NetworkCapabilities capabilities) {
+            NetworkCapabilities capabilities, Constants constants) {
         final long estimatedBytes = jobStatus.getEstimatedNetworkBytes();
         if (estimatedBytes == JobInfo.NETWORK_BYTES_UNKNOWN) {
             // We don't know how large the job is; cross our fingers!
@@ -153,11 +139,11 @@
 
     @SuppressWarnings("unused")
     private static boolean isCongestionDelayed(JobStatus jobStatus, Network network,
-            NetworkCapabilities capabilities) {
+            NetworkCapabilities capabilities, Constants constants) {
         // If network is congested, and job is less than 50% through the
         // developer-requested window, then we're okay delaying the job.
         if (!capabilities.hasCapability(NET_CAPABILITY_NOT_CONGESTED)) {
-            return jobStatus.getFractionRunTime() < 0.5;
+            return jobStatus.getFractionRunTime() < constants.CONN_CONGESTION_DELAY_FRAC;
         } else {
             return false;
         }
@@ -165,14 +151,14 @@
 
     @SuppressWarnings("unused")
     private static boolean isStrictSatisfied(JobStatus jobStatus, Network network,
-            NetworkCapabilities capabilities) {
+            NetworkCapabilities capabilities, Constants constants) {
         return jobStatus.getJob().getRequiredNetwork().networkCapabilities
                 .satisfiedByNetworkCapabilities(capabilities);
     }
 
     @SuppressWarnings("unused")
     private static boolean isRelaxedSatisfied(JobStatus jobStatus, Network network,
-            NetworkCapabilities capabilities) {
+            NetworkCapabilities capabilities, Constants constants) {
         // Only consider doing this for prefetching jobs
         if ((jobStatus.getJob().getFlags() & JobInfo.FLAG_IS_PREFETCH) == 0) {
             return false;
@@ -184,7 +170,7 @@
                         .removeCapability(NET_CAPABILITY_NOT_METERED);
         if (relaxed.satisfiedByNetworkCapabilities(capabilities)) {
             // TODO: treat this as "maybe" response; need to check quotas
-            return jobStatus.getFractionRunTime() > 0.5;
+            return jobStatus.getFractionRunTime() > constants.CONN_PREFETCH_RELAX_FRAC;
         } else {
             return false;
         }
@@ -192,21 +178,21 @@
 
     @VisibleForTesting
     static boolean isSatisfied(JobStatus jobStatus, Network network,
-            NetworkCapabilities capabilities) {
+            NetworkCapabilities capabilities, Constants constants) {
         // Zeroth, we gotta have a network to think about being satisfied
         if (network == null || capabilities == null) return false;
 
         // First, are we insane?
-        if (isInsane(jobStatus, network, capabilities)) return false;
+        if (isInsane(jobStatus, network, capabilities, constants)) return false;
 
         // Second, is the network congested?
-        if (isCongestionDelayed(jobStatus, network, capabilities)) return false;
+        if (isCongestionDelayed(jobStatus, network, capabilities, constants)) return false;
 
         // Third, is the network a strict match?
-        if (isStrictSatisfied(jobStatus, network, capabilities)) return true;
+        if (isStrictSatisfied(jobStatus, network, capabilities, constants)) return true;
 
         // Third, is the network a relaxed match?
-        if (isRelaxedSatisfied(jobStatus, network, capabilities)) return true;
+        if (isRelaxedSatisfied(jobStatus, network, capabilities, constants)) return true;
 
         return false;
     }
@@ -222,7 +208,7 @@
         final NetworkCapabilities capabilities = mConnManager.getNetworkCapabilities(network);
 
         final boolean connected = (info != null) && info.isConnected();
-        final boolean satisfied = isSatisfied(jobStatus, network, capabilities);
+        final boolean satisfied = isSatisfied(jobStatus, network, capabilities, mConstants);
 
         final boolean changed = jobStatus
                 .setConnectivityConstraintSatisfied(connected && satisfied);
@@ -331,18 +317,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 +338,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 +347,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..a775cf5 100644
--- a/services/core/java/com/android/server/job/controllers/ContentObserverController.java
+++ b/services/core/java/com/android/server/job/controllers/ContentObserverController.java
@@ -18,7 +18,6 @@
 
 import android.annotation.UserIdInt;
 import android.app.job.JobInfo;
-import android.content.Context;
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Handler;
@@ -31,14 +30,13 @@
 import android.util.TimeUtils;
 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.
@@ -60,9 +58,6 @@
      */
     private static final int URIS_URGENT_THRESHOLD = 40;
 
-    private static final Object sCreationLock = new Object();
-    private static volatile ContentObserverController sController;
-
     final private ArraySet<JobStatus> mTrackedTasks = new ArraySet<>();
     /**
      * Per-userid {@link JobInfo.TriggerContentUri} keyed ContentObserver cache.
@@ -71,26 +66,9 @@
             new SparseArray<>();
     final Handler mHandler;
 
-    public static ContentObserverController get(JobSchedulerService taskManagerService) {
-        synchronized (sCreationLock) {
-            if (sController == null) {
-                sController = new ContentObserverController(taskManagerService,
-                        taskManagerService.getContext(), taskManagerService.getLock());
-            }
-        }
-        return sController;
-    }
-
-    @VisibleForTesting
-    public static ContentObserverController getForTesting(StateChangedListener stateChangedListener,
-                                           Context context) {
-        return new ContentObserverController(stateChangedListener, context, new Object());
-    }
-
-    private ContentObserverController(StateChangedListener stateChangedListener, Context context,
-                Object lock) {
-        super(stateChangedListener, context, lock);
-        mHandler = new Handler(context.getMainLooper());
+    public ContentObserverController(JobSchedulerService service) {
+        super(service);
+        mHandler = new Handler(mContext.getMainLooper());
     }
 
     @Override
@@ -375,22 +353,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 +383,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 +391,6 @@
                     if (!shouldDump) {
                         continue;
                     }
-                    pw.print("    ");
                     JobInfo.TriggerContentUri trigger = observersOfUser.keyAt(observerIdx);
                     pw.print(trigger.getUri());
                     pw.print(" 0x");
@@ -418,17 +398,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 +419,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 +479,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..127a5c8 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.
@@ -56,10 +57,6 @@
 
     static final int PROCESS_BACKGROUND_JOBS = 1;
 
-    // Singleton factory
-    private static Object sCreationLock = new Object();
-    private static DeviceIdleJobsController sController;
-
     /**
      * These are jobs added with a special flag to indicate that they should be exempted from doze
      * when the app is temp whitelisted or in the foreground.
@@ -68,7 +65,6 @@
     private final SparseBooleanArray mForegroundUids;
     private final DeviceIdleUpdateFunctor mDeviceIdleUpdateFunctor;
     private final DeviceIdleJobsDelayHandler mHandler;
-    private final JobSchedulerService mJobSchedulerService;
     private final PowerManager mPowerManager;
     private final DeviceIdleController.LocalService mLocalDeviceIdleController;
 
@@ -79,19 +75,6 @@
     private int[] mDeviceIdleWhitelistAppIds;
     private int[] mPowerSaveTempWhitelistAppIds;
 
-    /**
-     * Returns a singleton for the DeviceIdleJobsController
-     */
-    public static DeviceIdleJobsController get(JobSchedulerService service) {
-        synchronized (sCreationLock) {
-            if (sController == null) {
-                sController = new DeviceIdleJobsController(service, service.getContext(),
-                        service.getLock());
-            }
-            return sController;
-        }
-    }
-
     // onReceive
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
@@ -133,12 +116,10 @@
         }
     };
 
-    private DeviceIdleJobsController(JobSchedulerService jobSchedulerService, Context context,
-            Object lock) {
-        super(jobSchedulerService, context, lock);
+    public DeviceIdleJobsController(JobSchedulerService service) {
+        super(service);
 
-        mJobSchedulerService = jobSchedulerService;
-        mHandler = new DeviceIdleJobsDelayHandler(context.getMainLooper());
+        mHandler = new DeviceIdleJobsDelayHandler(mContext.getMainLooper());
         // Register for device idle mode changes
         mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
         mLocalDeviceIdleController =
@@ -168,13 +149,13 @@
             if (DEBUG) Slog.d(TAG, "mDeviceIdleMode=" + mDeviceIdleMode);
             if (enabled) {
                 mHandler.removeMessages(PROCESS_BACKGROUND_JOBS);
-                mJobSchedulerService.getJobStore().forEachJob(mDeviceIdleUpdateFunctor);
+                mService.getJobStore().forEachJob(mDeviceIdleUpdateFunctor);
             } else {
                 // When coming out of doze, process all foreground uids immediately, while others
                 // will be processed after a delay of 3 seconds.
                 for (int i = 0; i < mForegroundUids.size(); i++) {
                     if (mForegroundUids.valueAt(i)) {
-                        mJobSchedulerService.getJobStore().forEachJobForSourceUid(
+                        mService.getJobStore().forEachJobForSourceUid(
                                 mForegroundUids.keyAt(i), mDeviceIdleUpdateFunctor);
                     }
                 }
@@ -200,7 +181,7 @@
         }
         mForegroundUids.put(uid, active);
         mDeviceIdleUpdateFunctor.mChanged = false;
-        mJobSchedulerService.getJobStore().forEachJobForSourceUid(uid, mDeviceIdleUpdateFunctor);
+        mService.getJobStore().forEachJobForSourceUid(uid, mDeviceIdleUpdateFunctor);
         if (mDeviceIdleUpdateFunctor.mChanged) {
             mStateChangedListener.onControllerStateChanged();
         }
@@ -247,71 +228,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();
+
+        mService.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);
+        mService.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);
         }
     }
@@ -328,7 +302,7 @@
                     // Just process all the jobs, the ones in foreground should already be running.
                     synchronized (mLock) {
                         mDeviceIdleUpdateFunctor.mChanged = false;
-                        mJobSchedulerService.getJobStore().forEachJob(mDeviceIdleUpdateFunctor);
+                        mService.getJobStore().forEachJob(mDeviceIdleUpdateFunctor);
                         if (mDeviceIdleUpdateFunctor.mChanged) {
                             mStateChangedListener.onControllerStateChanged();
                         }
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..1dbcfd6 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,12 @@
 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";
@@ -49,22 +49,8 @@
     final ArraySet<JobStatus> mTrackedTasks = new ArraySet<>();
     IdlenessTracker mIdleTracker;
 
-    // Singleton factory
-    private static Object sCreationLock = new Object();
-    private static volatile IdleController sController;
-
-    public static IdleController get(JobSchedulerService service) {
-        synchronized (sCreationLock) {
-            if (sController == null) {
-                sController = new IdleController(service, service.getContext(), service.getLock());
-            }
-            return sController;
-        }
-    }
-
-    private IdleController(StateChangedListener stateChangedListener, Context context,
-                Object lock) {
-        super(stateChangedListener, context, lock);
+    public IdleController(JobSchedulerService service) {
+        super(service);
         initIdleStateTracking();
     }
 
@@ -203,18 +189,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 +208,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 +217,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 d1bb63a..5616197 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -888,11 +888,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..495109d 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,12 @@
 import android.content.Context;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.job.JobSchedulerService;
+import com.android.server.job.JobSchedulerService.Constants;
 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.
@@ -30,15 +32,18 @@
  * are ready to run, or whether they must be stopped.
  */
 public abstract class StateController {
+    protected final JobSchedulerService mService;
+    protected final StateChangedListener mStateChangedListener;
     protected final Context mContext;
     protected final Object mLock;
-    protected final StateChangedListener mStateChangedListener;
+    protected final Constants mConstants;
 
-    public StateController(StateChangedListener stateChangedListener, Context context,
-            Object lock) {
-        mStateChangedListener = stateChangedListener;
-        mContext = context;
-        mLock = lock;
+    StateController(JobSchedulerService service) {
+        mService = service;
+        mStateChangedListener = service;
+        mContext = service.getContext();
+        mLock = service.getLock();
+        mConstants = service.getConstants();
     }
 
     /**
@@ -64,7 +69,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..c2ae53f 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,12 @@
 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.
@@ -44,36 +44,16 @@
     private static final boolean DEBUG = JobSchedulerService.DEBUG
             || Log.isLoggable(TAG, Log.DEBUG);
 
-    private static final Object sCreationLock = new Object();
-    private static volatile StorageController sController;
-
     private final ArraySet<JobStatus> mTrackedTasks = new ArraySet<JobStatus>();
-    private StorageTracker mStorageTracker;
-
-    public static StorageController get(JobSchedulerService taskManagerService) {
-        synchronized (sCreationLock) {
-            if (sController == null) {
-                sController = new StorageController(taskManagerService,
-                        taskManagerService.getContext(), taskManagerService.getLock());
-            }
-        }
-        return sController;
-    }
+    private final StorageTracker mStorageTracker;
 
     @VisibleForTesting
     public StorageTracker getTracker() {
         return mStorageTracker;
     }
 
-    @VisibleForTesting
-    public static StorageController getForTesting(StateChangedListener stateChangedListener,
-            Context context) {
-        return new StorageController(stateChangedListener, context, new Object());
-    }
-
-    private StorageController(StateChangedListener stateChangedListener, Context context,
-            Object lock) {
-        super(stateChangedListener, context, lock);
+    public StorageController(JobSchedulerService service) {
+        super(service);
         mStorageTracker = new StorageTracker();
         mStorageTracker.startTracking();
     }
@@ -175,20 +155,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 +175,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 +187,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..fa48b5e 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,15 @@
 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
@@ -62,23 +62,13 @@
     private AlarmManager mAlarmService = null;
     /** List of tracked jobs, sorted asc. by deadline */
     private final List<JobStatus> mTrackedJobs = new LinkedList<>();
-    /** Singleton. */
-    private static TimeController mSingleton;
 
-    public static synchronized TimeController get(JobSchedulerService jms) {
-        if (mSingleton == null) {
-            mSingleton = new TimeController(jms, jms.getContext(), jms.getLock());
-        }
-        return mSingleton;
-    }
-
-    private TimeController(StateChangedListener stateChangedListener, Context context,
-                Object lock) {
-        super(stateChangedListener, context, lock);
+    public TimeController(JobSchedulerService service) {
+        super(service);
 
         mNextJobExpiredElapsedMillis = Long.MAX_VALUE;
         mNextDelayExpiredElapsedMillis = Long.MAX_VALUE;
-        mChainedAttributionEnabled = WorkSource.isChainedBatteryAttributionEnabled(context);
+        mChainedAttributionEnabled = WorkSource.isChainedBatteryAttributionEnabled(mContext);
     }
 
     /**
@@ -348,25 +338,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 +376,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 +389,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);
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index bd9ec55..f29e0bb 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -28,6 +28,11 @@
 import static android.content.Intent.ACTION_USER_ADDED;
 import static android.content.Intent.ACTION_USER_REMOVED;
 import static android.content.Intent.EXTRA_UID;
+import static android.content.pm.PackageManager.MATCH_ANY_USER;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
+import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
 import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
 import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
@@ -61,9 +66,7 @@
 import static android.net.NetworkPolicyManager.resolveNetworkId;
 import static android.net.NetworkPolicyManager.uidPoliciesToString;
 import static android.net.NetworkPolicyManager.uidRulesToString;
-import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER;
-import static android.net.NetworkTemplate.MATCH_MOBILE_4G;
-import static android.net.NetworkTemplate.MATCH_MOBILE_ALL;
+import static android.net.NetworkTemplate.MATCH_MOBILE;
 import static android.net.NetworkTemplate.MATCH_WIFI;
 import static android.net.NetworkTemplate.buildTemplateMobileAll;
 import static android.net.TrafficStats.MB_IN_BYTES;
@@ -71,7 +74,6 @@
 import static android.telephony.CarrierConfigManager.DATA_CYCLE_THRESHOLD_DISABLED;
 import static android.telephony.CarrierConfigManager.DATA_CYCLE_USE_PLATFORM_DEFAULT;
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-import static android.text.format.DateUtils.DAY_IN_MILLIS;
 
 import static com.android.internal.util.ArrayUtils.appendInt;
 import static com.android.internal.util.Preconditions.checkNotNull;
@@ -139,15 +141,16 @@
 import android.net.NetworkRequest;
 import android.net.NetworkSpecifier;
 import android.net.NetworkState;
+import android.net.NetworkStats;
 import android.net.NetworkTemplate;
 import android.net.StringNetworkSpecifier;
 import android.net.TrafficStats;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
+import android.os.BestClock;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Environment;
-import android.os.BestClock;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IDeviceIdleController;
@@ -359,7 +362,7 @@
 
     private final Context mContext;
     private final IActivityManager mActivityManager;
-    private final INetworkStatsService mNetworkStats;
+    private NetworkStatsManagerInternal mNetworkStats;
     private final INetworkManagementService mNetworkManager;
     private UsageStatsManagerInternal mUsageStats;
     private final Clock mClock;
@@ -516,10 +519,9 @@
     // TODO: migrate notifications to SystemUI
 
     public NetworkPolicyManagerService(Context context, IActivityManager activityManager,
-            INetworkStatsService networkStats, INetworkManagementService networkManagement) {
-        this(context, activityManager, networkStats, networkManagement,
-                AppGlobals.getPackageManager(), getDefaultClock(), getDefaultSystemDir(),
-                false);
+            INetworkManagementService networkManagement) {
+        this(context, activityManager, networkManagement, AppGlobals.getPackageManager(),
+                getDefaultClock(), getDefaultSystemDir(), false);
     }
 
     private static @NonNull File getDefaultSystemDir() {
@@ -532,11 +534,10 @@
     }
 
     public NetworkPolicyManagerService(Context context, IActivityManager activityManager,
-            INetworkStatsService networkStats, INetworkManagementService networkManagement,
-            IPackageManager pm, Clock clock, File systemDir, boolean suppressDefaultPolicy) {
+            INetworkManagementService networkManagement, IPackageManager pm, Clock clock,
+            File systemDir, boolean suppressDefaultPolicy) {
         mContext = checkNotNull(context, "missing context");
         mActivityManager = checkNotNull(activityManager, "missing activityManager");
-        mNetworkStats = checkNotNull(networkStats, "missing networkStats");
         mNetworkManager = checkNotNull(networkManagement, "missing networkManagement");
         mDeviceIdleController = IDeviceIdleController.Stub.asInterface(ServiceManager.getService(
                 Context.DEVICE_IDLE_CONTROLLER));
@@ -660,6 +661,7 @@
             }
 
             mUsageStats = LocalServices.getService(UsageStatsManagerInternal.class);
+            mNetworkStats = LocalServices.getService(NetworkStatsManagerInternal.class);
 
             synchronized (mUidRulesFirstLock) {
                 synchronized (mNetworkPoliciesSecondLock) {
@@ -1063,9 +1065,9 @@
             if (policy.isOverLimit(totalBytes)) {
                 final boolean snoozedThisCycle = policy.lastLimitSnooze >= cycleStart;
                 if (snoozedThisCycle) {
-                    enqueueNotification(policy, TYPE_LIMIT_SNOOZED, totalBytes);
+                    enqueueNotification(policy, TYPE_LIMIT_SNOOZED, totalBytes, null);
                 } else {
-                    enqueueNotification(policy, TYPE_LIMIT, totalBytes);
+                    enqueueNotification(policy, TYPE_LIMIT, totalBytes, null);
                     notifyOverLimitNL(policy.template);
                 }
 
@@ -1074,7 +1076,7 @@
 
                 final boolean snoozedThisCycle = policy.lastWarningSnooze >= cycleStart;
                 if (policy.isOverWarning(totalBytes) && !snoozedThisCycle) {
-                    enqueueNotification(policy, TYPE_WARNING, totalBytes);
+                    enqueueNotification(policy, TYPE_WARNING, totalBytes, null);
                 }
             }
 
@@ -1082,7 +1084,9 @@
             // far past the plan limits.
             if (policy.limitBytes != LIMIT_DISABLED) {
                 final long recentDuration = TimeUnit.DAYS.toMillis(4);
-                final long recentBytes = getTotalBytes(policy.template, now - recentDuration, now);
+                final long recentStart = now - recentDuration;
+                final long recentEnd = now;
+                final long recentBytes = getTotalBytes(policy.template, recentStart, recentEnd);
 
                 final long cycleDuration = cycleEnd - cycleStart;
                 final long projectedBytes = (recentBytes * cycleDuration) / recentDuration;
@@ -1096,7 +1100,8 @@
                 final boolean snoozedRecently = policy.lastRapidSnooze >= now
                         - DateUtils.DAY_IN_MILLIS;
                 if (projectedBytes > alertBytes && !snoozedRecently) {
-                    enqueueNotification(policy, TYPE_RAPID, 0);
+                    enqueueNotification(policy, TYPE_RAPID, 0,
+                            findRapidBlame(policy.template, recentStart, recentEnd));
                 }
             }
         }
@@ -1111,6 +1116,45 @@
     }
 
     /**
+     * Attempt to find a specific app to blame for rapid data usage during the
+     * given time period.
+     */
+    private @Nullable ApplicationInfo findRapidBlame(NetworkTemplate template,
+            long start, long end) {
+        long totalBytes = 0;
+        long maxBytes = 0;
+        int maxUid = 0;
+
+        final NetworkStats stats = getNetworkUidBytes(template, start, end);
+        NetworkStats.Entry entry = null;
+        for (int i = 0; i < stats.size(); i++) {
+            entry = stats.getValues(i, entry);
+            final long bytes = entry.rxBytes + entry.txBytes;
+            totalBytes += bytes;
+            if (bytes > maxBytes) {
+                maxBytes = bytes;
+                maxUid = entry.uid;
+            }
+        }
+
+        // Only point blame if the majority of usage was done by a single app.
+        // TODO: support shared UIDs
+        if (maxBytes > 0 && maxBytes > totalBytes / 2) {
+            final String[] packageNames = mContext.getPackageManager().getPackagesForUid(maxUid);
+            if (packageNames.length == 1) {
+                try {
+                    return mContext.getPackageManager().getApplicationInfo(packageNames[0],
+                            MATCH_ANY_USER | MATCH_DISABLED_COMPONENTS | MATCH_DIRECT_BOOT_AWARE
+                                    | MATCH_DIRECT_BOOT_UNAWARE | MATCH_UNINSTALLED_PACKAGES);
+                } catch (NameNotFoundException ignored) {
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /**
      * Test if given {@link NetworkTemplate} is relevant to user based on
      * current device state, such as when
      * {@link TelephonyManager#getSubscriberId()} matches. This is regardless of
@@ -1157,7 +1201,8 @@
      * Show notification for combined {@link NetworkPolicy} and specific type,
      * like {@link #TYPE_LIMIT}. Okay to call multiple times.
      */
-    private void enqueueNotification(NetworkPolicy policy, int type, long totalBytes) {
+    private void enqueueNotification(NetworkPolicy policy, int type, long totalBytes,
+            ApplicationInfo rapidBlame) {
         final NotificationId notificationId = new NotificationId(policy, type);
         final Notification.Builder builder =
                 new Notification.Builder(mContext, SystemNotificationChannels.NETWORK_ALERTS);
@@ -1167,16 +1212,15 @@
                 com.android.internal.R.color.system_notification_accent_color));
 
         final Resources res = mContext.getResources();
-        CharSequence body = null;
+        final CharSequence title;
+        final CharSequence body;
         switch (type) {
             case TYPE_WARNING: {
-                final CharSequence title = res.getText(R.string.data_usage_warning_title);
-                body = res.getString(R.string.data_usage_warning_body);
+                title = res.getText(R.string.data_usage_warning_title);
+                body = res.getString(R.string.data_usage_warning_body,
+                        Formatter.formatFileSize(mContext, totalBytes));
 
                 builder.setSmallIcon(R.drawable.stat_notify_error);
-                builder.setTicker(title);
-                builder.setContentTitle(title);
-                builder.setContentText(body);
 
                 final Intent snoozeIntent = buildSnoozeWarningIntent(policy.template);
                 builder.setDeleteIntent(PendingIntent.getBroadcast(
@@ -1189,34 +1233,20 @@
                 break;
             }
             case TYPE_LIMIT: {
-                body = res.getText(R.string.data_usage_limit_body);
-
-                final CharSequence title;
-                int icon = R.drawable.stat_notify_disabled_data;
                 switch (policy.template.getMatchRule()) {
-                    case MATCH_MOBILE_3G_LOWER:
-                        title = res.getText(R.string.data_usage_3g_limit_title);
-                        break;
-                    case MATCH_MOBILE_4G:
-                        title = res.getText(R.string.data_usage_4g_limit_title);
-                        break;
-                    case MATCH_MOBILE_ALL:
+                    case MATCH_MOBILE:
                         title = res.getText(R.string.data_usage_mobile_limit_title);
                         break;
                     case MATCH_WIFI:
                         title = res.getText(R.string.data_usage_wifi_limit_title);
-                        icon = R.drawable.stat_notify_error;
                         break;
                     default:
-                        title = null;
-                        break;
+                        return;
                 }
+                body = res.getText(R.string.data_usage_limit_body);
 
                 builder.setOngoing(true);
-                builder.setSmallIcon(icon);
-                builder.setTicker(title);
-                builder.setContentTitle(title);
-                builder.setContentText(body);
+                builder.setSmallIcon(R.drawable.stat_notify_disabled_data);
 
                 final Intent intent = buildNetworkOverLimitIntent(res, policy.template);
                 builder.setContentIntent(PendingIntent.getActivity(
@@ -1224,34 +1254,22 @@
                 break;
             }
             case TYPE_LIMIT_SNOOZED: {
-                final long overBytes = totalBytes - policy.limitBytes;
-                body = res.getString(R.string.data_usage_limit_snoozed_body,
-                        Formatter.formatFileSize(mContext, overBytes));
-
-                final CharSequence title;
                 switch (policy.template.getMatchRule()) {
-                    case MATCH_MOBILE_3G_LOWER:
-                        title = res.getText(R.string.data_usage_3g_limit_snoozed_title);
-                        break;
-                    case MATCH_MOBILE_4G:
-                        title = res.getText(R.string.data_usage_4g_limit_snoozed_title);
-                        break;
-                    case MATCH_MOBILE_ALL:
+                    case MATCH_MOBILE:
                         title = res.getText(R.string.data_usage_mobile_limit_snoozed_title);
                         break;
                     case MATCH_WIFI:
                         title = res.getText(R.string.data_usage_wifi_limit_snoozed_title);
                         break;
                     default:
-                        title = null;
-                        break;
+                        return;
                 }
+                final long overBytes = totalBytes - policy.limitBytes;
+                body = res.getString(R.string.data_usage_limit_snoozed_body,
+                        Formatter.formatFileSize(mContext, overBytes));
 
                 builder.setOngoing(true);
                 builder.setSmallIcon(R.drawable.stat_notify_error);
-                builder.setTicker(title);
-                builder.setContentTitle(title);
-                builder.setContentText(body);
                 builder.setChannelId(SystemNotificationChannels.NETWORK_STATUS);
 
                 final Intent intent = buildViewDataUsageIntent(res, policy.template);
@@ -1260,13 +1278,15 @@
                 break;
             }
             case TYPE_RAPID: {
-                final CharSequence title = res.getText(R.string.data_usage_rapid_title);
-                body = res.getText(R.string.data_usage_rapid_body);
+                title = res.getText(R.string.data_usage_rapid_title);
+                if (rapidBlame != null) {
+                    body = res.getString(R.string.data_usage_rapid_app_body,
+                            rapidBlame.loadLabel(mContext.getPackageManager()));
+                } else {
+                    body = res.getString(R.string.data_usage_rapid_body);
+                }
 
                 builder.setSmallIcon(R.drawable.stat_notify_error);
-                builder.setTicker(title);
-                builder.setContentTitle(title);
-                builder.setContentText(body);
 
                 final Intent snoozeIntent = buildSnoozeRapidIntent(policy.template);
                 builder.setDeleteIntent(PendingIntent.getBroadcast(
@@ -1277,11 +1297,15 @@
                         mContext, 0, viewIntent, PendingIntent.FLAG_UPDATE_CURRENT));
                 break;
             }
+            default: {
+                return;
+            }
         }
 
-        if (!TextUtils.isEmpty(body)) {
-            builder.setStyle(new Notification.BigTextStyle().bigText(body));
-        }
+        builder.setTicker(title);
+        builder.setContentTitle(title);
+        builder.setContentText(body);
+        builder.setStyle(new Notification.BigTextStyle().bigText(body));
 
         mContext.getSystemService(NotificationManager.class).notifyAsUser(notificationId.getTag(),
                 notificationId.getId(), builder.build(), UserHandle.ALL);
@@ -1537,7 +1561,7 @@
         // TODO: reach into ConnectivityManager to proactively disable bringing
         // up this network, since we know that traffic will be blocked.
 
-        if (template.getMatchRule() == MATCH_MOBILE_ALL) {
+        if (template.getMatchRule() == MATCH_MOBILE) {
             // If mobile data usage hits the limit or if the user resumes the data, we need to
             // notify telephony.
             final SubscriptionManager sm = mContext.getSystemService(SubscriptionManager.class);
@@ -1954,9 +1978,7 @@
                             metered = readBooleanAttribute(in, ATTR_METERED);
                         } else {
                             switch (networkTemplate) {
-                                case MATCH_MOBILE_3G_LOWER:
-                                case MATCH_MOBILE_4G:
-                                case MATCH_MOBILE_ALL:
+                                case MATCH_MOBILE:
                                     metered = true;
                                     break;
                                 default:
@@ -3243,8 +3265,6 @@
         }
         try {
             mNetworkStats.setUidForeground(uid, uidForeground);
-        } catch (RemoteException e) {
-            // ignored; service lives in system_server
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
         }
@@ -4028,13 +4048,9 @@
 
                     synchronized (mNetworkPoliciesSecondLock) {
                         if (mMeteredIfaces.contains(iface)) {
-                            try {
-                                // force stats update to make sure we have
-                                // numbers that caused alert to trigger.
-                                mNetworkStats.forceUpdate();
-                            } catch (RemoteException e) {
-                                // ignored; service lives in system_server
-                            }
+                            // force stats update to make sure we have
+                            // numbers that caused alert to trigger.
+                            mNetworkStats.forceUpdate();
 
                             updateNetworkEnabledNL();
                             updateNotificationsNL();
@@ -4075,14 +4091,10 @@
                 }
                 case MSG_ADVISE_PERSIST_THRESHOLD: {
                     final long lowestRule = (Long) msg.obj;
-                    try {
-                        // make sure stats are recorded frequently enough; we aim
-                        // for 2MB threshold for 2GB/month rules.
-                        final long persistThreshold = lowestRule / 1000;
-                        mNetworkStats.advisePersistThreshold(persistThreshold);
-                    } catch (RemoteException e) {
-                        // ignored; service lives in system_server
-                    }
+                    // make sure stats are recorded frequently enough; we aim
+                    // for 2MB threshold for 2GB/month rules.
+                    final long persistThreshold = lowestRule / 1000;
+                    mNetworkStats.advisePersistThreshold(persistThreshold);
                     return true;
                 }
                 case MSG_UPDATE_INTERFACE_QUOTA: {
@@ -4366,15 +4378,26 @@
         }
     }
 
+    @Deprecated
     private long getTotalBytes(NetworkTemplate template, long start, long end) {
+        return getNetworkTotalBytes(template, start, end);
+    }
+
+    private long getNetworkTotalBytes(NetworkTemplate template, long start, long end) {
         try {
             return mNetworkStats.getNetworkTotalBytes(template, start, end);
         } catch (RuntimeException e) {
-            Slog.w(TAG, "problem reading network stats: " + e);
+            Slog.w(TAG, "Failed to read network stats: " + e);
             return 0;
-        } catch (RemoteException e) {
-            // ignored; service lives in system_server
-            return 0;
+        }
+    }
+
+    private NetworkStats getNetworkUidBytes(NetworkTemplate template, long start, long end) {
+        try {
+            return mNetworkStats.getNetworkUidBytes(template, start, end);
+        } catch (RuntimeException e) {
+            Slog.w(TAG, "Failed to read network stats: " + e);
+            return new NetworkStats(SystemClock.elapsedRealtime(), 0);
         }
     }
 
diff --git a/services/core/java/com/android/server/net/NetworkStatsCollection.java b/services/core/java/com/android/server/net/NetworkStatsCollection.java
index 3cc4d83..a5f8dc7 100644
--- a/services/core/java/com/android/server/net/NetworkStatsCollection.java
+++ b/services/core/java/com/android/server/net/NetworkStatsCollection.java
@@ -106,6 +106,10 @@
         reset();
     }
 
+    public void clear() {
+        reset();
+    }
+
     public void reset() {
         mStats.clear();
         mStartMillis = Long.MAX_VALUE;
diff --git a/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java b/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java
new file mode 100644
index 0000000..4843ede
--- /dev/null
+++ b/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net;
+
+import android.net.NetworkStats;
+import android.net.NetworkTemplate;
+
+public abstract class NetworkStatsManagerInternal {
+    /** Return network layer usage total for traffic that matches template. */
+    public abstract long getNetworkTotalBytes(NetworkTemplate template, long start, long end);
+
+    /** Return network layer usage per-UID for traffic that matches template. */
+    public abstract NetworkStats getNetworkUidBytes(NetworkTemplate template, long start, long end);
+
+    /** Mark given UID as being in foreground for stats purposes. */
+    public abstract void setUidForeground(int uid, boolean uidForeground);
+
+    /** Advise persistance threshold; may be overridden internally. */
+    public abstract void advisePersistThreshold(long thresholdBytes);
+
+    /** Force update of statistics. */
+    public abstract void forceUpdate();
+}
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index 32e15c9..93c04fe 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -49,7 +49,6 @@
 import static android.provider.Settings.Global.NETSTATS_GLOBAL_ALERT_BYTES;
 import static android.provider.Settings.Global.NETSTATS_POLL_INTERVAL;
 import static android.provider.Settings.Global.NETSTATS_SAMPLE_ENABLED;
-import static android.provider.Settings.Global.NETSTATS_TIME_CACHE_MAX_AGE;
 import static android.provider.Settings.Global.NETSTATS_UID_BUCKET_DURATION;
 import static android.provider.Settings.Global.NETSTATS_UID_DELETE_AGE;
 import static android.provider.Settings.Global.NETSTATS_UID_PERSIST_BYTES;
@@ -95,10 +94,10 @@
 import android.net.NetworkStatsHistory;
 import android.net.NetworkTemplate;
 import android.net.TrafficStats;
+import android.os.BestClock;
 import android.os.Binder;
 import android.os.DropBoxManager;
 import android.os.Environment;
-import android.os.BestClock;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
@@ -134,6 +133,7 @@
 import com.android.internal.util.FileRotator;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.EventLogTags;
+import com.android.server.LocalServices;
 import com.android.server.connectivity.Tethering;
 
 import java.io.File;
@@ -333,6 +333,9 @@
         mStatsObservers = checkNotNull(statsObservers, "missing NetworkStatsObservers");
         mSystemDir = checkNotNull(systemDir, "missing systemDir");
         mBaseDir = checkNotNull(baseDir, "missing baseDir");
+
+        LocalServices.addService(NetworkStatsManagerInternal.class,
+                new NetworkStatsManagerInternalImpl());
     }
 
     @VisibleForTesting
@@ -640,7 +643,7 @@
     private SubscriptionPlan resolveSubscriptionPlan(NetworkTemplate template, int flags) {
         SubscriptionPlan plan = null;
         if ((flags & NetworkStatsManager.FLAG_AUGMENT_WITH_SUBSCRIPTION_PLAN) != 0
-                && (template.getMatchRule() == NetworkTemplate.MATCH_MOBILE_ALL)
+                && (template.getMatchRule() == NetworkTemplate.MATCH_MOBILE)
                 && mSettings.getAugmentEnabled()) {
             if (LOGD) Slog.d(TAG, "Resolving plan for " + template);
             final long token = Binder.clearCallingIdentity();
@@ -701,12 +704,8 @@
         }
     }
 
-    @Override
-    public long getNetworkTotalBytes(NetworkTemplate template, long start, long end) {
-        // Special case - since this is for internal use only, don't worry about
-        // a full access level check and just require the signature/privileged
-        // permission.
-        mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
+    private long getNetworkTotalBytes(NetworkTemplate template, long start, long end) {
+        assertSystemReady();
         assertBandwidthControlEnabled();
 
         // NOTE: if callers want to get non-augmented data, they should go
@@ -716,6 +715,18 @@
                 NetworkStatsAccess.Level.DEVICE, Binder.getCallingUid()).getTotalBytes();
     }
 
+    private NetworkStats getNetworkUidBytes(NetworkTemplate template, long start, long end) {
+        assertSystemReady();
+        assertBandwidthControlEnabled();
+
+        final NetworkStatsCollection uidComplete;
+        synchronized (mStatsLock) {
+            uidComplete = mUidRecorder.getOrLoadCompleteLocked();
+        }
+        return uidComplete.getSummary(template, start, end, NetworkStatsAccess.Level.DEVICE,
+                android.os.Process.SYSTEM_UID);
+    }
+
     @Override
     public NetworkStats getDataLayerSnapshotForUid(int uid) throws RemoteException {
         if (Binder.getCallingUid() != uid) {
@@ -777,10 +788,8 @@
         }
     }
 
-    @Override
-    public void setUidForeground(int uid, boolean uidForeground) {
-        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-
+    @VisibleForTesting
+    void setUidForeground(int uid, boolean uidForeground) {
         synchronized (mStatsLock) {
             final int set = uidForeground ? SET_FOREGROUND : SET_DEFAULT;
             final int oldSet = mActiveUidCounterSet.get(uid, SET_DEFAULT);
@@ -817,9 +826,7 @@
         }
     }
 
-    @Override
-    public void advisePersistThreshold(long thresholdBytes) {
-        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+    private void advisePersistThreshold(long thresholdBytes) {
         assertBandwidthControlEnabled();
 
         // clamp threshold into safe range
@@ -1330,6 +1337,33 @@
         removeUidsLocked(uids);
     }
 
+    private class NetworkStatsManagerInternalImpl extends NetworkStatsManagerInternal {
+        @Override
+        public long getNetworkTotalBytes(NetworkTemplate template, long start, long end) {
+            return NetworkStatsService.this.getNetworkTotalBytes(template, start, end);
+        }
+
+        @Override
+        public NetworkStats getNetworkUidBytes(NetworkTemplate template, long start, long end) {
+            return NetworkStatsService.this.getNetworkUidBytes(template, start, end);
+        }
+
+        @Override
+        public void setUidForeground(int uid, boolean uidForeground) {
+            NetworkStatsService.this.setUidForeground(uid, uidForeground);
+        }
+
+        @Override
+        public void advisePersistThreshold(long thresholdBytes) {
+            NetworkStatsService.this.advisePersistThreshold(thresholdBytes);
+        }
+
+        @Override
+        public void forceUpdate() {
+            NetworkStatsService.this.forceUpdate();
+        }
+    }
+
     @Override
     protected void dump(FileDescriptor fd, PrintWriter rawWriter, String[] args) {
         if (!DumpUtils.checkDumpPermission(mContext, TAG, rawWriter)) return;
@@ -1550,6 +1584,12 @@
         }
     }
 
+    private void assertSystemReady() {
+        if (!mSystemReady) {
+            throw new IllegalStateException("System not ready");
+        }
+    }
+
     private void assertBandwidthControlEnabled() {
         if (!isBandwidthControlEnabled()) {
             throw new IllegalStateException("Bandwidth module disabled");
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index d24b3ee..5b5de0e 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1088,8 +1088,8 @@
 
             traceBeginAndSlog("StartNetworkPolicyManagerService");
             try {
-                networkPolicy = new NetworkPolicyManagerService(context,
-                    mActivityManagerService, networkStats, networkManagement);
+                networkPolicy = new NetworkPolicyManagerService(context, mActivityManagerService,
+                        networkManagement);
                 ServiceManager.addService(Context.NETWORK_POLICY_SERVICE, networkPolicy);
             } catch (Throwable e) {
                 reportWtf("starting NetworkPolicy Service", e);
diff --git a/services/print/java/com/android/server/print/UserState.java b/services/print/java/com/android/server/print/UserState.java
index 84c1bb2..3c09cb1 100644
--- a/services/print/java/com/android/server/print/UserState.java
+++ b/services/print/java/com/android/server/print/UserState.java
@@ -38,7 +38,6 @@
 import android.content.pm.ResolveInfo;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
-import android.os.AsyncTask;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
@@ -236,15 +235,6 @@
             return null;
         }
 
-        // Spin the spooler to add the job and show the config UI.
-        new AsyncTask<Void, Void, Void>() {
-            @Override
-            protected Void doInBackground(Void... params) {
-                mSpooler.createPrintJob(printJob);
-                return null;
-            }
-        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
-
         final long identity = Binder.clearCallingIdentity();
         try {
             Intent intent = new Intent(PrintManager.ACTION_PRINT_DIALOG);
diff --git a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
index e1b4422..d31d550 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
@@ -26,6 +26,9 @@
 import static android.net.NetworkPolicyManager.POLICY_NONE;
 import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
 import static android.net.NetworkPolicyManager.uidPoliciesToString;
+import static android.net.NetworkStats.IFACE_ALL;
+import static android.net.NetworkStats.SET_ALL;
+import static android.net.NetworkStats.TAG_ALL;
 import static android.net.NetworkTemplate.buildTemplateMobileAll;
 import static android.net.TrafficStats.MB_IN_BYTES;
 import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED;
@@ -42,8 +45,6 @@
 import static com.android.server.net.NetworkPolicyManagerService.TYPE_RAPID;
 import static com.android.server.net.NetworkPolicyManagerService.TYPE_WARNING;
 
-import static com.google.common.truth.Truth.assertThat;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -86,7 +87,6 @@
 import android.net.IConnectivityManager;
 import android.net.INetworkManagementEventObserver;
 import android.net.INetworkPolicyListener;
-import android.net.INetworkStatsService;
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
@@ -105,6 +105,7 @@
 import android.os.PowerSaveState;
 import android.os.RemoteException;
 import android.os.SimpleClock;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.runner.AndroidJUnit4;
@@ -125,6 +126,7 @@
 import com.android.internal.util.test.BroadcastInterceptingContext.FutureIntent;
 import com.android.server.net.NetworkPolicyManagerInternal;
 import com.android.server.net.NetworkPolicyManagerService;
+import com.android.server.net.NetworkStatsManagerInternal;
 
 import libcore.io.IoUtils;
 import libcore.io.Streams;
@@ -214,7 +216,6 @@
     private String mNetpolicyXml;
 
     private @Mock IActivityManager mActivityManager;
-    private @Mock INetworkStatsService mStatsService;
     private @Mock INetworkManagementService mNetworkManager;
     private @Mock IConnectivityManager mConnManager;
     private @Mock NotificationManager mNotifManager;
@@ -224,7 +225,8 @@
     private @Mock CarrierConfigManager mCarrierConfigManager;
     private @Mock TelephonyManager mTelephonyManager;
 
-    private static ActivityManagerInternal mActivityManagerInternal;
+    private ActivityManagerInternal mActivityManagerInternal;
+    private NetworkStatsManagerInternal mStatsService;
 
     private IUidObserver mUidObserver;
     private INetworkManagementEventObserver mNetworkObserver;
@@ -264,6 +266,8 @@
     private static final int UID_F = UserHandle.getUid(USER_ID, APP_ID_F);
 
     private static final String PKG_NAME_A = "name.is.A,pkg.A";
+    private static final String PKG_NAME_B = "name.is.B,pkg.B";
+    private static final String PKG_NAME_C = "name.is.C,pkg.C";
 
     public final @Rule NetPolicyMethodRule mNetPolicyXmlRule = new NetPolicyMethodRule();
 
@@ -287,6 +291,8 @@
                 .setBatterySaverEnabled(false).build();
         final PowerManagerInternal pmInternal = addLocalServiceMock(PowerManagerInternal.class);
         when(pmInternal.getLowPowerState(anyInt())).thenReturn(state);
+
+        mStatsService = addLocalServiceMock(NetworkStatsManagerInternal.class);
     }
 
     @Before
@@ -347,7 +353,7 @@
                 eq(ActivityManager.PROCESS_STATE_UNKNOWN), isNull(String.class));
 
         mFutureIntent = newRestrictBackgroundChangedFuture();
-        mService = new NetworkPolicyManagerService(mServiceContext, mActivityManager, mStatsService,
+        mService = new NetworkPolicyManagerService(mServiceContext, mActivityManager,
                 mNetworkManager, mIpm, mClock, mPolicyDir, true);
         mService.bindConnectivityManager(mConnManager);
         mPolicyListener = new NetworkPolicyListenerAnswer(mService);
@@ -375,6 +381,14 @@
         when(mPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
                 .thenReturn(new ApplicationInfo());
         when(mPackageManager.getPackagesForUid(UID_A)).thenReturn(new String[] {PKG_NAME_A});
+        when(mPackageManager.getPackagesForUid(UID_B)).thenReturn(new String[] {PKG_NAME_B});
+        when(mPackageManager.getPackagesForUid(UID_C)).thenReturn(new String[] {PKG_NAME_C});
+        when(mPackageManager.getApplicationInfo(eq(PKG_NAME_A), anyInt()))
+                .thenReturn(buildApplicationInfo(PKG_NAME_A));
+        when(mPackageManager.getApplicationInfo(eq(PKG_NAME_B), anyInt()))
+                .thenReturn(buildApplicationInfo(PKG_NAME_B));
+        when(mPackageManager.getApplicationInfo(eq(PKG_NAME_C), anyInt()))
+                .thenReturn(buildApplicationInfo(PKG_NAME_C));
         when(mNetworkManager.isBandwidthControlEnabled()).thenReturn(true);
         when(mNetworkManager.setDataSaverModeEnabled(anyBoolean())).thenReturn(true);
 
@@ -409,6 +423,7 @@
         LocalServices.removeServiceForTest(PowerManagerInternal.class);
         LocalServices.removeServiceForTest(DeviceIdleController.LocalService.class);
         LocalServices.removeServiceForTest(UsageStatsManagerInternal.class);
+        LocalServices.removeServiceForTest(NetworkStatsManagerInternal.class);
     }
 
     @After
@@ -515,7 +530,7 @@
         mService.updateRestrictBackgroundByLowPowerModeUL(stateOn);
 
         // RestrictBackground should be on even though battery saver want to turn it off
-        assertThat(mService.getRestrictBackground()).isTrue();
+        assertTrue(mService.getRestrictBackground());
 
         PowerSaveState stateOff = new PowerSaveState.Builder()
                 .setGlobalBatterySaverEnabled(false)
@@ -524,7 +539,7 @@
         mService.updateRestrictBackgroundByLowPowerModeUL(stateOff);
 
         // RestrictBackground should be on, following its previous state
-        assertThat(mService.getRestrictBackground()).isTrue();
+        assertTrue(mService.getRestrictBackground());
     }
 
     @Test
@@ -539,7 +554,7 @@
         mService.updateRestrictBackgroundByLowPowerModeUL(stateOn);
 
         // RestrictBackground should be turned on because of battery saver
-        assertThat(mService.getRestrictBackground()).isTrue();
+        assertTrue(mService.getRestrictBackground());
 
         PowerSaveState stateOff = new PowerSaveState.Builder()
                 .setGlobalBatterySaverEnabled(false)
@@ -548,7 +563,7 @@
         mService.updateRestrictBackgroundByLowPowerModeUL(stateOff);
 
         // RestrictBackground should be off, following its previous state
-        assertThat(mService.getRestrictBackground()).isFalse();
+        assertFalse(mService.getRestrictBackground());
     }
 
     @Test
@@ -562,7 +577,7 @@
         mService.updateRestrictBackgroundByLowPowerModeUL(stateOn);
 
         // RestrictBackground should still be on
-        assertThat(mService.getRestrictBackground()).isTrue();
+        assertTrue(mService.getRestrictBackground());
 
         // User turns off RestrictBackground manually
         setRestrictBackground(false);
@@ -571,7 +586,7 @@
         mService.updateRestrictBackgroundByLowPowerModeUL(stateOff);
 
         // RestrictBackground should be off because user changes it manually
-        assertThat(mService.getRestrictBackground()).isFalse();
+        assertFalse(mService.getRestrictBackground());
     }
 
     private void removeRestrictBackgroundWhitelist(boolean expectIntent) throws Exception {
@@ -974,6 +989,7 @@
     public void testNotificationWarningLimitSnooze() throws Exception {
         // Create a place to store fake usage
         final NetworkStatsHistory history = new NetworkStatsHistory(TimeUnit.HOURS.toMillis(1));
+        final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0);
         when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong()))
                 .thenAnswer(new Answer<Long>() {
                     @Override
@@ -983,6 +999,13 @@
                         return entry.rxBytes + entry.txBytes;
                     }
                 });
+        when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong()))
+                .thenAnswer(new Answer<NetworkStats>() {
+                    @Override
+                    public NetworkStats answer(InvocationOnMock invocation) throws Throwable {
+                        return stats;
+                    }
+                });
 
         // Get active mobile network in place
         expectMobileDefaults();
@@ -1003,7 +1026,7 @@
 
         // Normal usage means no notification
         {
-            history.removeBucketsBefore(Long.MAX_VALUE);
+            history.clear();
             history.recordData(start, end,
                     new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(360), 0L, 0L, 0L, 0));
 
@@ -1020,7 +1043,7 @@
 
         // Push over warning
         {
-            history.removeBucketsBefore(Long.MAX_VALUE);
+            history.clear();
             history.recordData(start, end,
                     new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(1799), 0L, 0L, 0L, 0));
 
@@ -1038,7 +1061,7 @@
 
         // Push over limit
         {
-            history.removeBucketsBefore(Long.MAX_VALUE);
+            history.clear();
             history.recordData(start, end,
                     new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(1810), 0L, 0L, 0L, 0));
 
@@ -1073,6 +1096,7 @@
     public void testNotificationRapid() throws Exception {
         // Create a place to store fake usage
         final NetworkStatsHistory history = new NetworkStatsHistory(TimeUnit.HOURS.toMillis(1));
+        final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0);
         when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong()))
                 .thenAnswer(new Answer<Long>() {
                     @Override
@@ -1082,6 +1106,13 @@
                         return entry.rxBytes + entry.txBytes;
                     }
                 });
+        when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong()))
+                .thenAnswer(new Answer<NetworkStats>() {
+                    @Override
+                    public NetworkStats answer(InvocationOnMock invocation) throws Throwable {
+                        return stats;
+                    }
+                });
 
         // Get active mobile network in place
         expectMobileDefaults();
@@ -1102,7 +1133,7 @@
 
         // Using 20% data in 20% time is normal
         {
-            history.removeBucketsBefore(Long.MAX_VALUE);
+            history.clear();
             history.recordData(start, end,
                     new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(360), 0L, 0L, 0L, 0));
 
@@ -1111,16 +1142,58 @@
             verify(mNotifManager, never()).notifyAsUser(any(), anyInt(), any(), any());
         }
 
-        // Using 80% data in 20% time is alarming
+        // Using 80% data in 20% time is alarming; but spread equally among
+        // three UIDs means we get generic alert
         {
-            history.removeBucketsBefore(Long.MAX_VALUE);
+            history.clear();
             history.recordData(start, end,
                     new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(1440), 0L, 0L, 0L, 0));
+            stats.clear();
+            stats.addValues(IFACE_ALL, UID_A, SET_ALL, TAG_ALL,
+                    DataUnit.MEGABYTES.toBytes(480), 0, 0, 0, 0);
+            stats.addValues(IFACE_ALL, UID_B, SET_ALL, TAG_ALL,
+                    DataUnit.MEGABYTES.toBytes(480), 0, 0, 0, 0);
+            stats.addValues(IFACE_ALL, UID_C, SET_ALL, TAG_ALL,
+                    DataUnit.MEGABYTES.toBytes(480), 0, 0, 0, 0);
 
             reset(mNotifManager);
             mService.updateNetworks();
+
+            final ArgumentCaptor<Notification> notif = ArgumentCaptor.forClass(Notification.class);
             verify(mNotifManager, atLeastOnce()).notifyAsUser(any(), eq(TYPE_RAPID),
-                    isA(Notification.class), eq(UserHandle.ALL));
+                    notif.capture(), eq(UserHandle.ALL));
+
+            final String text = notif.getValue().extras.getCharSequence(Notification.EXTRA_TEXT)
+                    .toString();
+            assertFalse(text.contains(PKG_NAME_A));
+            assertFalse(text.contains(PKG_NAME_B));
+            assertFalse(text.contains(PKG_NAME_C));
+        }
+
+        // Using 80% data in 20% time is alarming; but mostly done by one UID
+        // means we get specific alert
+        {
+            history.clear();
+            history.recordData(start, end,
+                    new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(1440), 0L, 0L, 0L, 0));
+            stats.clear();
+            stats.addValues(IFACE_ALL, UID_A, SET_ALL, TAG_ALL,
+                    DataUnit.MEGABYTES.toBytes(960), 0, 0, 0, 0);
+            stats.addValues(IFACE_ALL, UID_B, SET_ALL, TAG_ALL,
+                    DataUnit.MEGABYTES.toBytes(480), 0, 0, 0, 0);
+
+            reset(mNotifManager);
+            mService.updateNetworks();
+
+            final ArgumentCaptor<Notification> notif = ArgumentCaptor.forClass(Notification.class);
+            verify(mNotifManager, atLeastOnce()).notifyAsUser(any(), eq(TYPE_RAPID),
+                    notif.capture(), eq(UserHandle.ALL));
+
+            final String text = notif.getValue().extras.getCharSequence(Notification.EXTRA_TEXT)
+                    .toString();
+            assertTrue(text.contains(PKG_NAME_A));
+            assertFalse(text.contains(PKG_NAME_B));
+            assertFalse(text.contains(PKG_NAME_C));
         }
     }
 
@@ -1411,6 +1484,12 @@
                 true);
     }
 
+    private ApplicationInfo buildApplicationInfo(String label) {
+        final ApplicationInfo ai = new ApplicationInfo();
+        ai.nonLocalizedLabel = label;
+        return ai;
+    }
+
     private NetworkInfo buildNetworkInfo() {
         final NetworkInfo ni = new NetworkInfo(ConnectivityManager.TYPE_MOBILE,
                 TelephonyManager.NETWORK_TYPE_LTE, null, null);
diff --git a/services/tests/servicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/servicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
index 35cba18..8874894 100644
--- a/services/tests/servicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
@@ -33,12 +33,14 @@
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.os.Build;
+import android.os.Handler;
 import android.os.SystemClock;
 import android.support.test.runner.AndroidJUnit4;
 import android.util.DataUnit;
 
 import com.android.server.LocalServices;
 import com.android.server.job.JobSchedulerService;
+import com.android.server.job.JobSchedulerService.Constants;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -49,6 +51,8 @@
 
 @RunWith(AndroidJUnit4.class)
 public class ConnectivityControllerTest {
+    private Constants mConstants;
+
     @Before
     public void setUp() throws Exception {
         // Assume all packages are current SDK
@@ -65,23 +69,26 @@
                 Clock.fixed(SystemClock.uptimeMillisClock().instant(), ZoneOffset.UTC);
         JobSchedulerService.sElapsedRealtimeClock =
                 Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC);
+
+        // Assume default constants for now
+        mConstants = new Constants();
     }
 
     @Test
     public void testInsane() throws Exception {
-        final Network network = new Network(101);
+        final Network net = new Network(101);
         final JobInfo.Builder job = createJob()
                 .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1))
                 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
 
         // Slow network is too slow
-        assertFalse(ConnectivityController.isSatisfied(createJobStatus(job), network,
+        assertFalse(ConnectivityController.isSatisfied(createJobStatus(job), net,
                 createCapabilities().setLinkUpstreamBandwidthKbps(1)
-                        .setLinkDownstreamBandwidthKbps(1)));
+                        .setLinkDownstreamBandwidthKbps(1), mConstants));
         // Fast network looks great
-        assertTrue(ConnectivityController.isSatisfied(createJobStatus(job), network,
+        assertTrue(ConnectivityController.isSatisfied(createJobStatus(job), net,
                 createCapabilities().setLinkUpstreamBandwidthKbps(1024)
-                        .setLinkDownstreamBandwidthKbps(1024)));
+                        .setLinkDownstreamBandwidthKbps(1024), mConstants));
     }
 
     @Test
@@ -95,19 +102,19 @@
 
         // Uncongested network is whenever
         {
-            final Network network = new Network(101);
-            final NetworkCapabilities capabilities = createCapabilities()
+            final Network net = new Network(101);
+            final NetworkCapabilities caps = createCapabilities()
                     .addCapability(NET_CAPABILITY_NOT_CONGESTED);
-            assertTrue(ConnectivityController.isSatisfied(early, network, capabilities));
-            assertTrue(ConnectivityController.isSatisfied(late, network, capabilities));
+            assertTrue(ConnectivityController.isSatisfied(early, net, caps, mConstants));
+            assertTrue(ConnectivityController.isSatisfied(late, net, caps, mConstants));
         }
 
         // Congested network is more selective
         {
-            final Network network = new Network(101);
-            final NetworkCapabilities capabilities = createCapabilities();
-            assertFalse(ConnectivityController.isSatisfied(early, network, capabilities));
-            assertTrue(ConnectivityController.isSatisfied(late, network, capabilities));
+            final Network net = new Network(101);
+            final NetworkCapabilities caps = createCapabilities();
+            assertFalse(ConnectivityController.isSatisfied(early, net, caps, mConstants));
+            assertTrue(ConnectivityController.isSatisfied(late, net, caps, mConstants));
         }
     }
 
@@ -126,25 +133,25 @@
 
         // Unmetered network is whenever
         {
-            final Network network = new Network(101);
-            final NetworkCapabilities capabilities = createCapabilities()
+            final Network net = new Network(101);
+            final NetworkCapabilities caps = createCapabilities()
                     .addCapability(NET_CAPABILITY_NOT_CONGESTED)
                     .addCapability(NET_CAPABILITY_NOT_METERED);
-            assertTrue(ConnectivityController.isSatisfied(early, network, capabilities));
-            assertTrue(ConnectivityController.isSatisfied(late, network, capabilities));
-            assertTrue(ConnectivityController.isSatisfied(earlyPrefetch, network, capabilities));
-            assertTrue(ConnectivityController.isSatisfied(latePrefetch, network, capabilities));
+            assertTrue(ConnectivityController.isSatisfied(early, net, caps, mConstants));
+            assertTrue(ConnectivityController.isSatisfied(late, net, caps, mConstants));
+            assertTrue(ConnectivityController.isSatisfied(earlyPrefetch, net, caps, mConstants));
+            assertTrue(ConnectivityController.isSatisfied(latePrefetch, net, caps, mConstants));
         }
 
         // Metered network is only when prefetching and late
         {
-            final Network network = new Network(101);
-            final NetworkCapabilities capabilities = createCapabilities()
+            final Network net = new Network(101);
+            final NetworkCapabilities caps = createCapabilities()
                     .addCapability(NET_CAPABILITY_NOT_CONGESTED);
-            assertFalse(ConnectivityController.isSatisfied(early, network, capabilities));
-            assertFalse(ConnectivityController.isSatisfied(late, network, capabilities));
-            assertFalse(ConnectivityController.isSatisfied(earlyPrefetch, network, capabilities));
-            assertTrue(ConnectivityController.isSatisfied(latePrefetch, network, capabilities));
+            assertFalse(ConnectivityController.isSatisfied(early, net, caps, mConstants));
+            assertFalse(ConnectivityController.isSatisfied(late, net, caps, mConstants));
+            assertFalse(ConnectivityController.isSatisfied(earlyPrefetch, net, caps, mConstants));
+            assertTrue(ConnectivityController.isSatisfied(latePrefetch, net, caps, mConstants));
         }
     }
 
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 28f8122..2aea1d7 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -914,8 +914,10 @@
                 mock(INetworkPolicyManager.class),
                 mock(IpConnectivityLog.class));
 
-        mService.systemReady();
+        // Create local CM before sending system ready so that we can answer
+        // getSystemService() correctly.
         mCm = new WrappedConnectivityManager(InstrumentationRegistry.getContext(), mService);
+        mService.systemReady();
         mCm.bindProcessToNetwork(null);
 
         // Ensure that the default setting for Captive Portals is used for most tests
@@ -3412,8 +3414,10 @@
 
     @Test
     public void testNetworkCallbackMaximum() {
-        final int MAX_REQUESTS = 100;
-        final int CALLBACKS = 90;
+        // We can only have 99 callbacks, because MultipathPolicyTracker is
+        // already one of them.
+        final int MAX_REQUESTS = 99;
+        final int CALLBACKS = 89;
         final int INTENTS = 10;
         assertEquals(MAX_REQUESTS, CALLBACKS + INTENTS);
 
diff --git a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
index b1b05e8..49b2643 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -95,6 +95,7 @@
 
 import com.android.internal.net.VpnInfo;
 import com.android.internal.util.test.BroadcastInterceptingContext;
+import com.android.server.LocalServices;
 import com.android.server.net.NetworkStatsService.NetworkStatsSettings;
 import com.android.server.net.NetworkStatsService.NetworkStatsSettings.Config;
 
@@ -221,6 +222,9 @@
 
     @After
     public void tearDown() throws Exception {
+        // Registered by NetworkStatsService's constructor.
+        LocalServices.removeServiceForTest(NetworkStatsManagerInternal.class);
+
         IoUtils.deleteContents(mStatsDir);
 
         mServiceContext = null;