Merge "Significantly increase group coalesce time"
diff --git a/Android.bp b/Android.bp
index efe53079..8c85631 100644
--- a/Android.bp
+++ b/Android.bp
@@ -416,6 +416,13 @@
 }
 
 filegroup {
+    name: "graphicsstats_proto",
+    srcs: [
+        "libs/hwui/protos/graphicsstats.proto",
+    ],
+}
+
+filegroup {
     name: "libvibrator_aidl",
     srcs: [
         "core/java/android/os/IExternalVibrationController.aidl",
@@ -442,8 +449,6 @@
         "libcore-platform-compat-config",
         "services-platform-compat-config",
         "media-provider-platform-compat-config",
-        "services-devicepolicy-platform-compat-config",
-        "services-core-platform-compat-config",
     ],
     static_libs: [
         // If MimeMap ever becomes its own APEX, then this dependency would need to be removed
@@ -489,7 +494,8 @@
         // TODO(b/140299412): should be framework-wifi-stubs
         "framework-wifi",
         "ike-stubs",
-        // TODO(jiyong): add more stubs for APEXes here
+        // TODO(b/147200698): should be the stub of framework-tethering
+        "framework-tethering",
     ],
     sdk_version: "core_platform",
     apex_available: ["//apex_available:platform"],
@@ -619,6 +625,15 @@
 }
 
 filegroup {
+    name: "framework-ike-shared-srcs",
+    visibility: ["//frameworks/opt/net/ike"],
+    srcs: [
+        "core/java/android/net/annotations/PolicyDirection.java",
+        "telephony/java/android/telephony/Annotation.java",
+    ],
+}
+
+filegroup {
     name: "framework-networkstack-shared-srcs",
     srcs: [
         // TODO: remove these annotations as soon as we can use andoid.support.annotations.*
diff --git a/apex/appsearch/framework/Android.bp b/apex/appsearch/framework/Android.bp
index 3dc5a2c..1f30dda 100644
--- a/apex/appsearch/framework/Android.bp
+++ b/apex/appsearch/framework/Android.bp
@@ -26,9 +26,16 @@
   installable: true,
   sdk_version: "core_platform", // TODO(b/146218515) should be core_current
   srcs: [":framework-appsearch-sources"],
+  hostdex: true, // for hiddenapi check
   libs: [
     "framework-minus-apex",  // TODO(b/146218515) should be framework-system-stubs
   ],
+  visibility: [
+      "//frameworks/base/apex/appsearch:__subpackages__",
+      // TODO(b/146218515) remove this when framework is built with the stub of appsearch
+      "//frameworks/base",
+  ],
+  apex_available: ["com.android.appsearch"],
 }
 
 metalava_appsearch_docs_args =
diff --git a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
index 041825c..6109b71 100644
--- a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
@@ -1,5 +1,6 @@
 package com.android.server.usage;
 
+import android.annotation.NonNull;
 import android.annotation.UserIdInt;
 import android.app.usage.AppStandbyInfo;
 import android.app.usage.UsageEvents;
@@ -99,8 +100,18 @@
 
     List<AppStandbyInfo> getAppStandbyBuckets(int userId);
 
-    void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket,
-            int reason, long elapsedRealtime, boolean resetTimeout);
+    /**
+     * Changes an app's standby bucket to the provided value. The caller can only set the standby
+     * bucket for a different app than itself.
+     */
+    void setAppStandbyBucket(@NonNull String packageName, int bucket, int userId, int callingUid,
+            int callingPid);
+
+    /**
+     * Changes the app standby bucket for multiple apps at once.
+     */
+    void setAppStandbyBuckets(@NonNull List<AppStandbyInfo> appBuckets, int userId, int callingUid,
+            int callingPid);
 
     void addActiveDeviceAdmin(String adminPkg, int userId);
 
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index 2f8b513..58eb589 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -47,6 +47,7 @@
 
 import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
 
+import android.annotation.NonNull;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.AppGlobals;
@@ -101,7 +102,6 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.LocalServices;
 import com.android.server.usage.AppIdleHistory.AppUsageHistory;
-import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;
 
 import java.io.File;
 import java.io.PrintWriter;
@@ -109,6 +109,7 @@
 import java.time.format.DateTimeParseException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
@@ -1014,14 +1015,57 @@
         }
     }
 
-    @VisibleForTesting
-    void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket,
-            int reason, long elapsedRealtime) {
-        setAppStandbyBucket(packageName, userId, newBucket, reason, elapsedRealtime, false);
+    @Override
+    public void setAppStandbyBucket(@NonNull String packageName, int bucket, int userId,
+            int callingUid, int callingPid) {
+        setAppStandbyBuckets(
+                Collections.singletonList(new AppStandbyInfo(packageName, bucket)),
+                userId, callingUid, callingPid);
     }
 
     @Override
-    public void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket,
+    public void setAppStandbyBuckets(@NonNull List<AppStandbyInfo> appBuckets, int userId,
+            int callingUid, int callingPid) {
+        userId = ActivityManager.handleIncomingUser(
+                callingPid, callingUid, userId, false, true, "setAppStandbyBucket", null);
+        final boolean shellCaller = callingUid == Process.ROOT_UID
+                || callingUid == Process.SHELL_UID;
+        final boolean systemCaller = UserHandle.isCore(callingUid);
+        final int reason = systemCaller ? REASON_MAIN_FORCED : REASON_MAIN_PREDICTED;
+        final int packageFlags = PackageManager.MATCH_ANY_USER
+                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+                | PackageManager.MATCH_DIRECT_BOOT_AWARE;
+        final int numApps = appBuckets.size();
+        final long elapsedRealtime = mInjector.elapsedRealtime();
+        for (int i = 0; i < numApps; ++i) {
+            final AppStandbyInfo bucketInfo = appBuckets.get(i);
+            final String packageName = bucketInfo.mPackageName;
+            final int bucket = bucketInfo.mStandbyBucket;
+            if (bucket < STANDBY_BUCKET_ACTIVE || bucket > STANDBY_BUCKET_NEVER) {
+                throw new IllegalArgumentException("Cannot set the standby bucket to " + bucket);
+            }
+            final int packageUid = mInjector.getPackageManagerInternal()
+                    .getPackageUid(packageName, packageFlags, userId);
+            // Caller cannot set their own standby state
+            if (packageUid == callingUid) {
+                throw new IllegalArgumentException("Cannot set your own standby bucket");
+            }
+            if (packageUid < 0) {
+                throw new IllegalArgumentException(
+                        "Cannot set standby bucket for non existent package (" + packageName + ")");
+            }
+            setAppStandbyBucket(packageName, userId, bucket, reason, elapsedRealtime, shellCaller);
+        }
+    }
+
+    @VisibleForTesting
+    void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket,
+            int reason) {
+        setAppStandbyBucket(
+                packageName, userId, newBucket, reason, mInjector.elapsedRealtime(), false);
+    }
+
+    private void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket,
             int reason, long elapsedRealtime, boolean resetTimeout) {
         synchronized (mAppIdleLock) {
             // If the package is not installed, don't allow the bucket to be set.
@@ -1444,6 +1488,10 @@
             mBatteryStats.noteEvent(event, packageName, uid);
         }
 
+        PackageManagerInternal getPackageManagerInternal() {
+            return mPackageManagerInternal;
+        }
+
         boolean isPackageEphemeral(int userId, String packageName) {
             return mPackageManagerInternal.isPackageEphemeral(userId, packageName);
         }
diff --git a/apex/media/framework/Android.bp b/apex/media/framework/Android.bp
index 6bd0086..18382a4 100644
--- a/apex/media/framework/Android.bp
+++ b/apex/media/framework/Android.bp
@@ -55,6 +55,13 @@
     jarjar_rules: "jarjar_rules.txt",
 
     plugins: ["java_api_finder"],
+
+    hostdex: true, // for hiddenapi check
+    visibility: ["//frameworks/av/apex:__subpackages__"],
+    apex_available: [
+        "com.android.media",
+        "test_com.android.media",
+    ],
 }
 
 filegroup {
diff --git a/apex/sdkextensions/TEST_MAPPING b/apex/sdkextensions/TEST_MAPPING
index 7e77623..4e18833 100644
--- a/apex/sdkextensions/TEST_MAPPING
+++ b/apex/sdkextensions/TEST_MAPPING
@@ -1,7 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "CtsSdkExtTestCases"
+      "name": "CtsSdkExtensionsTestCases"
     },
     {
       "name": "apiextensions_e2e_tests"
diff --git a/apex/sdkextensions/framework/Android.bp b/apex/sdkextensions/framework/Android.bp
index 5504f4e..dd17473 100644
--- a/apex/sdkextensions/framework/Android.bp
+++ b/apex/sdkextensions/framework/Android.bp
@@ -36,6 +36,11 @@
         "//frameworks/base/apex/sdkextensions",
         "//frameworks/base/apex/sdkextensions/testing",
     ],
+    hostdex: true, // for hiddenapi check
+    apex_available: [
+        "com.android.sdkext",
+        "test_com.android.sdkext",
+    ],
 }
 
 droidstubs {
diff --git a/apex/statsd/aidl/android/os/IStatsCompanionService.aidl b/apex/statsd/aidl/android/os/IStatsCompanionService.aidl
index 21b7767..99b9d39 100644
--- a/apex/statsd/aidl/android/os/IStatsCompanionService.aidl
+++ b/apex/statsd/aidl/android/os/IStatsCompanionService.aidl
@@ -67,11 +67,4 @@
 
     /** Tells StatsCompaionService to grab the uid map snapshot and send it to statsd. */
     oneway void triggerUidSnapshot();
-
-    /** Tells StatsCompanionService to tell statsd to register a puller for the given atom id */
-    oneway void registerPullAtomCallback(int atomTag, long coolDownNs, long timeoutNs,
-            in int[] additiveFields, IPullAtomCallback pullerCallback);
-
-    /** Tells StatsCompanionService to tell statsd to unregister a puller for the given atom id */
-    oneway void unregisterPullAtomCallback(int atomTag);
 }
diff --git a/apex/statsd/aidl/android/os/IStatsManagerService.aidl b/apex/statsd/aidl/android/os/IStatsManagerService.aidl
index dec5634..4a259f5 100644
--- a/apex/statsd/aidl/android/os/IStatsManagerService.aidl
+++ b/apex/statsd/aidl/android/os/IStatsManagerService.aidl
@@ -17,6 +17,7 @@
 package android.os;
 
 import android.app.PendingIntent;
+import android.os.IPullAtomCallback;
 
 /**
   * Binder interface to communicate with the Java-based statistics service helper.
@@ -125,4 +126,11 @@
      * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
      */
     void removeConfiguration(in long configId, in String packageName);
-}
\ No newline at end of file
+
+    /** Tell StatsManagerService to register a puller for the given atom tag with statsd. */
+    oneway void registerPullAtomCallback(int atomTag, long coolDownNs, long timeoutNs,
+            in int[] additiveFields, IPullAtomCallback pullerCallback);
+
+    /** Tell StatsManagerService to unregister the pulller for the given atom tag from statsd. */
+    oneway void unregisterPullAtomCallback(int atomTag);
+}
diff --git a/apex/statsd/framework/Android.bp b/apex/statsd/framework/Android.bp
index a2b0577..0b46645a 100644
--- a/apex/statsd/framework/Android.bp
+++ b/apex/statsd/framework/Android.bp
@@ -37,7 +37,16 @@
         // TODO(b/146230220): Use framework-system-stubs instead.
         "android_system_stubs_current",
     ],
-    // TODO:(b/146210774): Add apex_available field.
+    hostdex: true, // for hiddenapi check
+    visibility: [
+        "//frameworks/base/apex/statsd:__subpackages__",
+        //TODO(b/146167933) remove this when framework is built with framework-statsd-stubs
+        "//frameworks/base",
+    ],
+    apex_available: [
+        "com.android.os.statsd",
+        "test_com.android.os.statsd",
+    ],
 }
 
 droidstubs {
diff --git a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
index d57afee..a908178 100644
--- a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
+++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
@@ -75,7 +75,6 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
-import android.os.IPullAtomCallback;
 import android.os.IStatsCompanionService;
 import android.os.IStatsd;
 import android.os.IStoraged;
@@ -263,71 +262,6 @@
 
     private StatsManagerService mStatsManagerService;
 
-    private static final class PullerKey {
-        private final int mUid;
-        private final int mAtomTag;
-
-        PullerKey(int uid, int atom) {
-            mUid = uid;
-            mAtomTag = atom;
-        }
-
-        public int getUid() {
-            return mUid;
-        }
-
-        public int getAtom() {
-            return mAtomTag;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(mUid, mAtomTag);
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (obj instanceof PullerKey) {
-                PullerKey other = (PullerKey) obj;
-                return this.mUid == other.getUid() && this.mAtomTag == other.getAtom();
-            }
-            return false;
-        }
-    }
-
-    private static final class PullerValue {
-        private final long mCoolDownNs;
-        private final long mTimeoutNs;
-        private int[] mAdditiveFields;
-        private IPullAtomCallback mCallback;
-
-        PullerValue(long coolDownNs, long timeoutNs, int[] additiveFields,
-                IPullAtomCallback callback) {
-            mCoolDownNs = coolDownNs;
-            mTimeoutNs = timeoutNs;
-            mAdditiveFields = additiveFields;
-            mCallback = callback;
-        }
-
-        public long getCoolDownNs() {
-            return mCoolDownNs;
-        }
-
-        public long getTimeoutNs() {
-            return mTimeoutNs;
-        }
-
-        public int[] getAdditiveFields() {
-            return mAdditiveFields;
-        }
-
-        public IPullAtomCallback getCallback() {
-            return mCallback;
-        }
-    }
-
-    private final HashMap<PullerKey, PullerValue> mPullers = new HashMap<>();
-
     private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader();
     private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();
     private WifiManager mWifiManager = null;
@@ -2634,57 +2568,6 @@
         }
     }
 
-    @Override
-    public void registerPullAtomCallback(int atomTag, long coolDownNs, long timeoutNs,
-            int[] additiveFields, IPullAtomCallback pullerCallback) {
-        synchronized (sStatsdLock) {
-            // Always cache the puller in SCS.
-            // If statsd is down, we will register it when it comes back up.
-            int callingUid = Binder.getCallingUid();
-            final long token = Binder.clearCallingIdentity();
-            PullerKey key = new PullerKey(callingUid, atomTag);
-            PullerValue val = new PullerValue(
-                    coolDownNs, timeoutNs, additiveFields, pullerCallback);
-            mPullers.put(key, val);
-
-            if (sStatsd == null) {
-                Slog.w(TAG, "Could not access statsd for registering puller for atom " + atomTag);
-                return;
-            }
-            try {
-                sStatsd.registerPullAtomCallback(
-                        callingUid, atomTag, coolDownNs, timeoutNs, additiveFields, pullerCallback);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to access statsd to register puller for atom " + atomTag);
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-    }
-
-    @Override
-    public void unregisterPullAtomCallback(int atomTag) {
-        synchronized (sStatsdLock) {
-            // Always remove the puller in SCS.
-            // If statsd is down, we will not register it when it comes back up.
-            int callingUid = Binder.getCallingUid();
-            final long token = Binder.clearCallingIdentity();
-            PullerKey key = new PullerKey(callingUid, atomTag);
-            mPullers.remove(key);
-
-            if (sStatsd == null) {
-                Slog.w(TAG, "Could not access statsd for registering puller for atom " + atomTag);
-                return;
-            }
-            try {
-                sStatsd.unregisterPullAtomCallback(callingUid, atomTag);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to access statsd to register puller for atom " + atomTag);
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-    }
 
     // Statsd related code
 
@@ -2763,8 +2646,6 @@
                     // Pull the latest state of UID->app name, version mapping when
                     // statsd starts.
                     informAllUidsLocked(mContext);
-                    // Register all pullers. If SCS has just started, this should be empty.
-                    registerAllPullersLocked();
                 } finally {
                     restoreCallingIdentity(token);
                 }
@@ -2776,17 +2657,6 @@
         }
     }
 
-    @GuardedBy("sStatsdLock")
-    private void registerAllPullersLocked() throws RemoteException {
-        // TODO: pass in one call, using a file descriptor (similar to uidmap).
-        for (Map.Entry<PullerKey, PullerValue> entry : mPullers.entrySet()) {
-            PullerKey key = entry.getKey();
-            PullerValue val = entry.getValue();
-            sStatsd.registerPullAtomCallback(key.getUid(), key.getAtom(), val.getCoolDownNs(),
-                    val.getTimeoutNs(), val.getAdditiveFields(), val.getCallback());
-        }
-    }
-
     private class StatsdDeathRecipient implements IBinder.DeathRecipient {
         @Override
         public void binderDied() {
diff --git a/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java b/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java
index b27d0f7..04d8b00 100644
--- a/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java
+++ b/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java
@@ -24,6 +24,7 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.os.Binder;
+import android.os.IPullAtomCallback;
 import android.os.IStatsManagerService;
 import android.os.IStatsd;
 import android.os.Process;
@@ -60,8 +61,7 @@
     @GuardedBy("mLock")
     private ArrayMap<ConfigKey, PendingIntentRef> mDataFetchPirMap = new ArrayMap<>();
     @GuardedBy("mLock")
-    private ArrayMap<Integer, PendingIntentRef> mActiveConfigsPirMap =
-            new ArrayMap<>();
+    private ArrayMap<Integer, PendingIntentRef> mActiveConfigsPirMap = new ArrayMap<>();
     @GuardedBy("mLock")
     private ArrayMap<ConfigKey, ArrayMap<Long, PendingIntentRef>> mBroadcastSubscriberPirMap =
             new ArrayMap<>();
@@ -72,8 +72,8 @@
     }
 
     private static class ConfigKey {
-        private int mUid;
-        private long mConfigId;
+        private final int mUid;
+        private final long mConfigId;
 
         ConfigKey(int uid, long configId) {
             mUid = uid;
@@ -103,6 +103,126 @@
         }
     }
 
+    private static class PullerKey {
+        private final int mUid;
+        private final int mAtomTag;
+
+        PullerKey(int uid, int atom) {
+            mUid = uid;
+            mAtomTag = atom;
+        }
+
+        public int getUid() {
+            return mUid;
+        }
+
+        public int getAtom() {
+            return mAtomTag;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mUid, mAtomTag);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj instanceof PullerKey) {
+                PullerKey other = (PullerKey) obj;
+                return this.mUid == other.getUid() && this.mAtomTag == other.getAtom();
+            }
+            return false;
+        }
+    }
+
+    private static class PullerValue {
+        private final long mCoolDownNs;
+        private final long mTimeoutNs;
+        private final int[] mAdditiveFields;
+        private final IPullAtomCallback mCallback;
+
+        PullerValue(long coolDownNs, long timeoutNs, int[] additiveFields,
+                IPullAtomCallback callback) {
+            mCoolDownNs = coolDownNs;
+            mTimeoutNs = timeoutNs;
+            mAdditiveFields = additiveFields;
+            mCallback = callback;
+        }
+
+        public long getCoolDownNs() {
+            return mCoolDownNs;
+        }
+
+        public long getTimeoutNs() {
+            return mTimeoutNs;
+        }
+
+        public int[] getAdditiveFields() {
+            return mAdditiveFields;
+        }
+
+        public IPullAtomCallback getCallback() {
+            return mCallback;
+        }
+    }
+
+    private final ArrayMap<PullerKey, PullerValue> mPullers = new ArrayMap<>();
+
+    @Override
+    public void registerPullAtomCallback(int atomTag, long coolDownNs, long timeoutNs,
+            int[] additiveFields, IPullAtomCallback pullerCallback) {
+        int callingUid = Binder.getCallingUid();
+        final long token = Binder.clearCallingIdentity();
+        PullerKey key = new PullerKey(callingUid, atomTag);
+        PullerValue val = new PullerValue(coolDownNs, timeoutNs, additiveFields, pullerCallback);
+
+        // Always cache the puller in StatsManagerService. If statsd is down, we will register the
+        // puller when statsd comes back up.
+        synchronized (mLock) {
+            mPullers.put(key, val);
+        }
+
+        IStatsd statsd = getStatsdNonblocking();
+        if (statsd == null) {
+            return;
+        }
+
+        try {
+            statsd.registerPullAtomCallback(
+                    callingUid, atomTag, coolDownNs, timeoutNs, additiveFields, pullerCallback);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to access statsd to register puller for atom " + atomTag);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override
+    public void unregisterPullAtomCallback(int atomTag) {
+        int callingUid = Binder.getCallingUid();
+        final long token = Binder.clearCallingIdentity();
+        PullerKey key = new PullerKey(callingUid, atomTag);
+
+        // Always remove the puller from StatsManagerService even if statsd is down. When statsd
+        // comes back up, we will not re-register the removed puller.
+        synchronized (mLock) {
+            mPullers.remove(key);
+        }
+
+        IStatsd statsd = getStatsdNonblocking();
+        if (statsd == null) {
+            return;
+        }
+
+        try {
+            statsd.unregisterPullAtomCallback(callingUid, atomTag);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to access statsd to unregister puller for atom " + atomTag);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
     @Override
     public void setDataFetchOperation(long configId, PendingIntent pendingIntent,
             String packageName) {
@@ -441,46 +561,85 @@
         if (statsd == null) {
             return;
         }
-        // Since we do not want to make an IPC with the a lock held, we first create local deep
-        // copies of the data with the lock held before iterating through the maps.
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            registerAllPullers(statsd);
+            registerAllDataFetchOperations(statsd);
+            registerAllActiveConfigsChangedOperations(statsd);
+            registerAllBroadcastSubscribers(statsd);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "StatsManager failed to (re-)register data with statsd");
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    // Pre-condition: the Binder calling identity has already been cleared
+    private void registerAllPullers(IStatsd statsd) throws RemoteException {
+        // Since we do not want to make an IPC with the lock held, we first create a copy of the
+        // data with the lock held before iterating through the map.
+        ArrayMap<PullerKey, PullerValue> pullersCopy;
+        synchronized (mLock) {
+            pullersCopy = new ArrayMap<>(mPullers);
+        }
+
+        for (Map.Entry<PullerKey, PullerValue> entry : pullersCopy.entrySet()) {
+            PullerKey key = entry.getKey();
+            PullerValue value = entry.getValue();
+            statsd.registerPullAtomCallback(key.getUid(), key.getAtom(), value.getCoolDownNs(),
+                    value.getTimeoutNs(), value.getAdditiveFields(), value.getCallback());
+        }
+    }
+
+    // Pre-condition: the Binder calling identity has already been cleared
+    private void registerAllDataFetchOperations(IStatsd statsd) throws RemoteException {
+        // Since we do not want to make an IPC with the lock held, we first create a copy of the
+        // data with the lock held before iterating through the map.
         ArrayMap<ConfigKey, PendingIntentRef> dataFetchCopy;
-        ArrayMap<Integer, PendingIntentRef> activeConfigsChangedCopy;
-        ArrayMap<ConfigKey, ArrayMap<Long, PendingIntentRef>> broadcastSubscriberCopy;
         synchronized (mLock) {
             dataFetchCopy = new ArrayMap<>(mDataFetchPirMap);
-            activeConfigsChangedCopy = new ArrayMap<>(mActiveConfigsPirMap);
-            broadcastSubscriberCopy = new ArrayMap<>();
-            for (Map.Entry<ConfigKey, ArrayMap<Long, PendingIntentRef>> entry
-                    : mBroadcastSubscriberPirMap.entrySet()) {
-                broadcastSubscriberCopy.put(entry.getKey(), new ArrayMap<>(entry.getValue()));
-            }
         }
+
         for (Map.Entry<ConfigKey, PendingIntentRef> entry : dataFetchCopy.entrySet()) {
             ConfigKey key = entry.getKey();
-            try {
-                statsd.setDataFetchOperation(key.getConfigId(), entry.getValue(), key.getUid());
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to setDataFetchOperation from pirMap");
+            statsd.setDataFetchOperation(key.getConfigId(), entry.getValue(), key.getUid());
+        }
+    }
+
+    // Pre-condition: the Binder calling identity has already been cleared
+    private void registerAllActiveConfigsChangedOperations(IStatsd statsd) throws RemoteException {
+        // Since we do not want to make an IPC with the lock held, we first create a copy of the
+        // data with the lock held before iterating through the map.
+        ArrayMap<Integer, PendingIntentRef> activeConfigsChangedCopy;
+        synchronized (mLock) {
+            activeConfigsChangedCopy = new ArrayMap<>(mActiveConfigsPirMap);
+        }
+
+        for (Map.Entry<Integer, PendingIntentRef> entry : activeConfigsChangedCopy.entrySet()) {
+            statsd.setActiveConfigsChangedOperation(entry.getValue(), entry.getKey());
+        }
+    }
+
+    // Pre-condition: the Binder calling identity has already been cleared
+    private void registerAllBroadcastSubscribers(IStatsd statsd) throws RemoteException {
+        // Since we do not want to make an IPC with the lock held, we first create a deep copy of
+        // the data with the lock held before iterating through the map.
+        ArrayMap<ConfigKey, ArrayMap<Long, PendingIntentRef>> broadcastSubscriberCopy =
+                new ArrayMap<>();
+        synchronized (mLock) {
+            for (Map.Entry<ConfigKey, ArrayMap<Long, PendingIntentRef>> entry :
+                    mBroadcastSubscriberPirMap.entrySet()) {
+                broadcastSubscriberCopy.put(entry.getKey(), new ArrayMap(entry.getValue()));
             }
         }
-        for (Map.Entry<Integer, PendingIntentRef> entry
-                : activeConfigsChangedCopy.entrySet()) {
-            try {
-                statsd.setActiveConfigsChangedOperation(entry.getValue(), entry.getKey());
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to setActiveConfigsChangedOperation from pirMap");
-            }
-        }
-        for (Map.Entry<ConfigKey, ArrayMap<Long, PendingIntentRef>> entry
-                : broadcastSubscriberCopy.entrySet()) {
+
+        for (Map.Entry<ConfigKey, ArrayMap<Long, PendingIntentRef>> entry :
+                mBroadcastSubscriberPirMap.entrySet()) {
+            ConfigKey configKey = entry.getKey();
             for (Map.Entry<Long, PendingIntentRef> subscriberEntry : entry.getValue().entrySet()) {
-                ConfigKey configKey = entry.getKey();
-                try {
-                    statsd.setBroadcastSubscriber(configKey.getConfigId(), subscriberEntry.getKey(),
-                            subscriberEntry.getValue(), configKey.getUid());
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Failed to setBroadcastSubscriber from pirMap");
-                }
+                statsd.setBroadcastSubscriber(configKey.getConfigId(), subscriberEntry.getKey(),
+                        subscriberEntry.getValue(), configKey.getUid());
             }
         }
     }
diff --git a/api/current.txt b/api/current.txt
index b673df0..b0d5fbf 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5837,6 +5837,7 @@
     method public void enableLights(boolean);
     method public void enableVibration(boolean);
     method public android.media.AudioAttributes getAudioAttributes();
+    method @Nullable public String getConversationId();
     method public String getDescription();
     method public String getGroup();
     method public String getId();
@@ -5844,12 +5845,14 @@
     method public int getLightColor();
     method public int getLockscreenVisibility();
     method public CharSequence getName();
+    method @Nullable public String getParentChannelId();
     method public android.net.Uri getSound();
     method public long[] getVibrationPattern();
     method public boolean hasUserSetImportance();
     method public boolean hasUserSetSound();
     method public void setAllowBubbles(boolean);
     method public void setBypassDnd(boolean);
+    method public void setConversationId(@Nullable String, @Nullable String);
     method public void setDescription(String);
     method public void setGroup(String);
     method public void setImportance(int);
@@ -5862,6 +5865,7 @@
     method public boolean shouldShowLights();
     method public boolean shouldVibrate();
     method public void writeToParcel(android.os.Parcel, int);
+    field public static final String CONVERSATION_CHANNEL_ID_FORMAT = "%1$s : %2$s";
     field @NonNull public static final android.os.Parcelable.Creator<android.app.NotificationChannel> CREATOR;
     field public static final String DEFAULT_CHANNEL_ID = "miscellaneous";
   }
@@ -5903,6 +5907,7 @@
     method public final int getCurrentInterruptionFilter();
     method public int getImportance();
     method public android.app.NotificationChannel getNotificationChannel(String);
+    method @Nullable public android.app.NotificationChannel getNotificationChannel(@NonNull String, @NonNull String);
     method public android.app.NotificationChannelGroup getNotificationChannelGroup(String);
     method public java.util.List<android.app.NotificationChannelGroup> getNotificationChannelGroups();
     method public java.util.List<android.app.NotificationChannel> getNotificationChannels();
@@ -9823,6 +9828,7 @@
     method public boolean bindIsolatedService(@NonNull @RequiresPermission android.content.Intent, int, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.content.ServiceConnection);
     method public abstract boolean bindService(@RequiresPermission android.content.Intent, @NonNull android.content.ServiceConnection, int);
     method public boolean bindService(@NonNull @RequiresPermission android.content.Intent, int, @NonNull java.util.concurrent.Executor, @NonNull android.content.ServiceConnection);
+    method @RequiresPermission(anyOf={"android.permission.INTERACT_ACROSS_USERS", android.Manifest.permission.INTERACT_ACROSS_PROFILES}) public boolean bindServiceAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.content.ServiceConnection, int, @NonNull android.os.UserHandle);
     method @CheckResult(suggest="#enforceCallingOrSelfPermission(String,String)") public abstract int checkCallingOrSelfPermission(@NonNull String);
     method @CheckResult(suggest="#enforceCallingOrSelfUriPermission(Uri,int,String)") public abstract int checkCallingOrSelfUriPermission(android.net.Uri, int);
     method @CheckResult(suggest="#enforceCallingPermission(String,String)") public abstract int checkCallingPermission(@NonNull String);
@@ -11356,6 +11362,9 @@
   }
 
   public class CrossProfileApps {
+    method public boolean canInteractAcrossProfiles();
+    method public boolean canRequestInteractAcrossProfiles();
+    method @Nullable public android.content.Intent createRequestInteractAcrossProfilesIntent();
     method @NonNull public android.graphics.drawable.Drawable getProfileSwitchingIconDrawable(@NonNull android.os.UserHandle);
     method @NonNull public CharSequence getProfileSwitchingLabel(@NonNull android.os.UserHandle);
     method @NonNull public java.util.List<android.os.UserHandle> getTargetUserProfiles();
@@ -11928,6 +11937,7 @@
     field public static final String FEATURE_STRONGBOX_KEYSTORE = "android.hardware.strongbox_keystore";
     field public static final String FEATURE_TELEPHONY = "android.hardware.telephony";
     field public static final String FEATURE_TELEPHONY_CDMA = "android.hardware.telephony.cdma";
+    field public static final String FEATURE_TELEPHONY_DATA = "android.hardware.telephony.data";
     field public static final String FEATURE_TELEPHONY_EUICC = "android.hardware.telephony.euicc";
     field public static final String FEATURE_TELEPHONY_GSM = "android.hardware.telephony.gsm";
     field public static final String FEATURE_TELEPHONY_IMS = "android.hardware.telephony.ims";
@@ -29155,6 +29165,7 @@
     ctor public DhcpInfo();
     method public int describeContents();
     method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.DhcpInfo> CREATOR;
     field public int dns1;
     field public int dns2;
     field public int gateway;
@@ -35827,6 +35838,7 @@
     field public static final int THREAD_PRIORITY_URGENT_AUDIO = -19; // 0xffffffed
     field public static final int THREAD_PRIORITY_URGENT_DISPLAY = -8; // 0xfffffff8
     field public static final int THREAD_PRIORITY_VIDEO = -10; // 0xfffffff6
+    field public static final int WIFI_UID = 1010; // 0x3f2
   }
 
   public abstract class ProxyFileDescriptorCallback {
@@ -39524,7 +39536,9 @@
     field public static final String ACTION_LOCALE_SETTINGS = "android.settings.LOCALE_SETTINGS";
     field public static final String ACTION_LOCATION_SOURCE_SETTINGS = "android.settings.LOCATION_SOURCE_SETTINGS";
     field public static final String ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS = "android.settings.MANAGE_ALL_APPLICATIONS_SETTINGS";
+    field public static final String ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION = "android.settings.MANAGE_ALL_FILES_ACCESS_PERMISSION";
     field public static final String ACTION_MANAGE_APPLICATIONS_SETTINGS = "android.settings.MANAGE_APPLICATIONS_SETTINGS";
+    field public static final String ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION = "android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION";
     field public static final String ACTION_MANAGE_DEFAULT_APPS_SETTINGS = "android.settings.MANAGE_DEFAULT_APPS_SETTINGS";
     field public static final String ACTION_MANAGE_OVERLAY_PERMISSION = "android.settings.action.MANAGE_OVERLAY_PERMISSION";
     field public static final String ACTION_MANAGE_UNKNOWN_APP_SOURCES = "android.settings.MANAGE_UNKNOWN_APP_SOURCES";
@@ -42668,9 +42682,13 @@
     method public android.content.Intent createEnrollIntent();
     method public android.content.Intent createReEnrollIntent();
     method public android.content.Intent createUnEnrollIntent();
+    method public int getParameter(int);
     method public int getSupportedRecognitionModes();
+    method @Nullable public android.service.voice.AlwaysOnHotwordDetector.ModelParamRange queryParameter(int);
+    method public int setParameter(int, int);
     method public boolean startRecognition(int);
     method public boolean stopRecognition();
+    field public static final int MODEL_PARAM_THRESHOLD_FACTOR = 0; // 0x0
     field public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 2; // 0x2
     field public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 1; // 0x1
     field public static final int RECOGNITION_MODE_USER_IDENTIFICATION = 2; // 0x2
@@ -42695,6 +42713,11 @@
     method @Nullable public byte[] getTriggerAudio();
   }
 
+  public static final class AlwaysOnHotwordDetector.ModelParamRange {
+    method public int end();
+    method public int start();
+  }
+
   public class VoiceInteractionService extends android.app.Service {
     ctor public VoiceInteractionService();
     method public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, android.service.voice.AlwaysOnHotwordDetector.Callback);
@@ -45010,6 +45033,7 @@
     field public static final String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED";
     field public static final int DATA_CYCLE_THRESHOLD_DISABLED = -2; // 0xfffffffe
     field public static final int DATA_CYCLE_USE_PLATFORM_DEFAULT = -1; // 0xffffffff
+    field public static final String ENABLE_EAP_METHOD_PREFIX_BOOL = "enable_eap_method_prefix_bool";
     field public static final String EXTRA_SLOT_INDEX = "android.telephony.extra.SLOT_INDEX";
     field public static final String EXTRA_SUBSCRIPTION_INDEX = "android.telephony.extra.SUBSCRIPTION_INDEX";
     field public static final String KEY_5G_NR_SSRSRP_THRESHOLDS_INT_ARRAY = "5g_nr_ssrsrp_thresholds_int_array";
@@ -45207,6 +45231,7 @@
 
   public static final class CarrierConfigManager.Ims {
     field public static final String KEY_PREFIX = "ims.";
+    field public static final String KEY_WIFI_OFF_DEFERRING_TIME_INT = "ims.wifi_off_deferring_time_int";
   }
 
   public abstract class CellIdentity implements android.os.Parcelable {
@@ -46134,12 +46159,12 @@
     method public android.net.Uri getVoicemailRingtoneUri(android.telecom.PhoneAccountHandle);
     method public boolean hasCarrierPrivileges();
     method public boolean hasIccCard();
-    method public boolean iccCloseLogicalChannel(int);
-    method public byte[] iccExchangeSimIO(int, int, int, int, int, String);
+    method @Deprecated public boolean iccCloseLogicalChannel(int);
+    method @Deprecated public byte[] iccExchangeSimIO(int, int, int, int, int, String);
     method @Deprecated public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(String);
-    method public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(String, int);
-    method public String iccTransmitApduBasicChannel(int, int, int, int, int, String);
-    method public String iccTransmitApduLogicalChannel(int, int, int, int, int, int, String);
+    method @Deprecated public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(String, int);
+    method @Deprecated public String iccTransmitApduBasicChannel(int, int, int, int, int, String);
+    method @Deprecated public String iccTransmitApduLogicalChannel(int, int, int, int, int, int, String);
     method public boolean isConcurrentVoiceAndDataSupported();
     method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean isDataEnabled();
     method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isDataRoamingEnabled();
@@ -46157,7 +46182,7 @@
     method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void requestCellInfoUpdate(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback);
     method @RequiresPermission(allOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public android.telephony.NetworkScan requestNetworkScan(android.telephony.NetworkScanRequest, java.util.concurrent.Executor, android.telephony.TelephonyScanManager.NetworkScanCallback);
     method public void sendDialerSpecialCode(String);
-    method public String sendEnvelopeWithStatus(String);
+    method @Deprecated public String sendEnvelopeWithStatus(String);
     method @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void sendUssdRequest(String, android.telephony.TelephonyManager.UssdResponseCallback, android.os.Handler);
     method public void sendVisualVoicemailSms(String, int, String, android.app.PendingIntent);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabled(boolean);
@@ -51599,9 +51624,10 @@
     method public boolean dispatchUnhandledMove(android.view.View, int);
     method protected void dispatchVisibilityChanged(@NonNull android.view.View, int);
     method public void dispatchWindowFocusChanged(boolean);
-    method public void dispatchWindowInsetsAnimationFinished(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation);
+    method public void dispatchWindowInsetsAnimationFinish(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation);
+    method public void dispatchWindowInsetsAnimationPrepare(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation);
     method @NonNull public android.view.WindowInsets dispatchWindowInsetsAnimationProgress(@NonNull android.view.WindowInsets);
-    method @NonNull public android.view.WindowInsetsAnimationCallback.AnimationBounds dispatchWindowInsetsAnimationStarted(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation, @NonNull android.view.WindowInsetsAnimationCallback.AnimationBounds);
+    method @NonNull public android.view.WindowInsetsAnimationCallback.AnimationBounds dispatchWindowInsetsAnimationStart(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation, @NonNull android.view.WindowInsetsAnimationCallback.AnimationBounds);
     method public void dispatchWindowSystemUiVisiblityChanged(int);
     method public void dispatchWindowVisibilityChanged(int);
     method @CallSuper public void draw(android.graphics.Canvas);
@@ -53281,9 +53307,10 @@
   }
 
   public interface WindowInsetsAnimationCallback {
-    method public default void onFinished(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation);
+    method public default void onFinish(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation);
+    method public default void onPrepare(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation);
     method @NonNull public android.view.WindowInsets onProgress(@NonNull android.view.WindowInsets);
-    method @NonNull public default android.view.WindowInsetsAnimationCallback.AnimationBounds onStarted(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation, @NonNull android.view.WindowInsetsAnimationCallback.AnimationBounds);
+    method @NonNull public default android.view.WindowInsetsAnimationCallback.AnimationBounds onStart(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation, @NonNull android.view.WindowInsetsAnimationCallback.AnimationBounds);
   }
 
   public static final class WindowInsetsAnimationCallback.AnimationBounds {
diff --git a/api/system-current.txt b/api/system-current.txt
index 384dc13..5aff7c5 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -152,6 +152,7 @@
     field public static final String PROVIDE_RESOLVER_RANKER_SERVICE = "android.permission.PROVIDE_RESOLVER_RANKER_SERVICE";
     field public static final String PROVIDE_TRUST_AGENT = "android.permission.PROVIDE_TRUST_AGENT";
     field public static final String QUERY_TIME_ZONE_RULES = "android.permission.QUERY_TIME_ZONE_RULES";
+    field public static final String RADIO_SCAN_WITHOUT_LOCATION = "android.permission.RADIO_SCAN_WITHOUT_LOCATION";
     field public static final String READ_ACTIVE_EMERGENCY_SESSION = "android.permission.READ_ACTIVE_EMERGENCY_SESSION";
     field public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS";
     field public static final String READ_CONTENT_RATING_SYSTEMS = "android.permission.READ_CONTENT_RATING_SYSTEMS";
@@ -1671,7 +1672,6 @@
   }
 
   public abstract class Context {
-    method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public boolean bindServiceAsUser(@RequiresPermission android.content.Intent, android.content.ServiceConnection, int, android.os.UserHandle);
     method @NonNull public android.content.Context createContextAsUser(@NonNull android.os.UserHandle, int);
     method public abstract android.content.Context createCredentialProtectedStorageContext();
     method @NonNull public android.content.Context createPackageContextAsUser(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -1780,6 +1780,7 @@
     field @Deprecated public static final String EXTRA_SIM_STATE = "ss";
     field public static final String EXTRA_UNKNOWN_INSTANT_APP = "android.intent.extra.UNKNOWN_INSTANT_APP";
     field public static final String EXTRA_VERIFICATION_BUNDLE = "android.intent.extra.VERIFICATION_BUNDLE";
+    field public static final int FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT = 67108864; // 0x4000000
     field public static final String METADATA_SETUP_VERSION = "android.SETUP_VERSION";
     field @Deprecated public static final String SIM_ABSENT_ON_PERM_DISABLED = "PERM_DISABLED";
     field @Deprecated public static final String SIM_LOCKED_NETWORK = "NETWORK";
@@ -3545,7 +3546,6 @@
   }
 
   public static final class SoundTrigger.ModelParamRange implements android.os.Parcelable {
-    method public int describeContents();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.ModelParamRange> CREATOR;
     field public final int end;
@@ -3566,6 +3566,7 @@
     field public final int powerConsumptionMw;
     field public final int recognitionModes;
     field public final boolean returnsTriggerInEvent;
+    field @NonNull public final String supportedModelArch;
     field public final boolean supportsCaptureTransition;
     field public final boolean supportsConcurrentCapture;
     field @NonNull public final java.util.UUID uuid;
@@ -4311,7 +4312,7 @@
   }
 
   public static interface MediaSessionManager.OnMediaKeyEventDispatchedListener {
-    method public default void onMediaKeyEventDispatched(@NonNull android.view.KeyEvent, @NonNull String, @NonNull android.media.session.MediaSession.Token);
+    method public default void onMediaKeyEventDispatched(@NonNull android.view.KeyEvent, @NonNull String, @Nullable android.media.session.MediaSession.Token);
   }
 
   public static interface MediaSessionManager.OnMediaKeyEventSessionChangedListener {
@@ -4367,9 +4368,9 @@
     method public int getDetectionServiceOperationsTimeout();
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.media.soundtrigger.SoundTriggerManager.Model getModel(java.util.UUID);
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.hardware.soundtrigger.SoundTrigger.ModuleProperties getModuleProperties();
-    method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int getParameter(@NonNull java.util.UUID, int) throws java.lang.IllegalArgumentException, java.lang.UnsupportedOperationException;
+    method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int getParameter(@NonNull java.util.UUID, int);
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.hardware.soundtrigger.SoundTrigger.ModelParamRange queryParameter(@Nullable java.util.UUID, int);
-    method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int setParameter(@Nullable java.util.UUID, int, int) throws java.lang.IllegalArgumentException, java.lang.UnsupportedOperationException;
+    method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int setParameter(@Nullable java.util.UUID, int, int);
     method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public void updateModel(android.media.soundtrigger.SoundTriggerManager.Model);
   }
 
@@ -4855,6 +4856,7 @@
   }
 
   public final class NetworkCapabilities implements android.os.Parcelable {
+    method public boolean deduceRestrictedCapability();
     method @NonNull public int[] getTransportTypes();
     method public boolean satisfiedByNetworkCapabilities(@Nullable android.net.NetworkCapabilities);
     method @NonNull public android.net.NetworkCapabilities setSSID(@Nullable String);
@@ -5835,8 +5837,8 @@
     method public int getMaxNumberOfClients();
     method @Nullable public String getPassphrase();
     method public int getSecurityType();
+    method public int getShutdownTimeoutMillis();
     method @Nullable public String getSsid();
-    method @Nullable public String getWpa2Passphrase();
     method public boolean isHiddenSsid();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field public static final int BAND_2GHZ = 1; // 0x1
@@ -5860,8 +5862,8 @@
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setHiddenSsid(boolean);
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setMaxNumberOfClients(int);
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setPassphrase(@Nullable String, int);
+    method @NonNull public android.net.wifi.SoftApConfiguration.Builder setShutdownTimeoutMillis(int);
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setSsid(@Nullable String);
-    method @NonNull public android.net.wifi.SoftApConfiguration.Builder setWpa2Passphrase(@Nullable String);
   }
 
   public final class SoftApInfo implements android.os.Parcelable {
@@ -6024,6 +6026,7 @@
     method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public void getWifiActivityEnergyInfoAsync(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.OnWifiActivityEnergyInfoListener);
     method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public android.net.wifi.WifiConfiguration getWifiApConfiguration();
     method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public int getWifiApState();
+    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.List<android.net.wifi.WifiConfiguration> getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(@NonNull java.util.List<android.net.wifi.ScanResult>);
     method public boolean isApMacRandomizationSupported();
     method public boolean isConnectedMacRandomizationSupported();
     method @Deprecated public boolean isDeviceToDeviceRttSupported();
@@ -7185,7 +7188,7 @@
     ctor public UpdateEngine();
     method @NonNull public android.os.UpdateEngine.AllocateSpaceResult allocateSpace(@NonNull String, @NonNull String[]);
     method public void applyPayload(String, long, long, String[]);
-    method public void applyPayload(@NonNull android.os.ParcelFileDescriptor, long, long, @NonNull String[]);
+    method public void applyPayload(@NonNull android.content.res.AssetFileDescriptor, @NonNull String[]);
     method public boolean bind(android.os.UpdateEngineCallback, android.os.Handler);
     method public boolean bind(android.os.UpdateEngineCallback);
     method public void cancel();
@@ -7860,6 +7863,7 @@
   }
 
   public final class Settings {
+    method public static boolean checkAndNoteWriteSettingsOperation(@NonNull android.content.Context, int, @NonNull String, boolean);
     field public static final String ACTION_ACCESSIBILITY_DETAILS_SETTINGS = "android.settings.ACCESSIBILITY_DETAILS_SETTINGS";
     field public static final String ACTION_BUGREPORT_HANDLER_SETTINGS = "android.settings.BUGREPORT_HANDLER_SETTINGS";
     field public static final String ACTION_ENTERPRISE_PRIVACY_SETTINGS = "android.settings.ENTERPRISE_PRIVACY_SETTINGS";
@@ -10399,10 +10403,10 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getVoiceActivationState();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean handlePinMmi(String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean handlePinMmiForSubscriber(int, String);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean iccCloseLogicalChannelBySlot(int, int);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean iccCloseLogicalChannelBySlot(int, int);
     method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannelBySlot(int, @Nullable String, int);
-    method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String iccTransmitApduBasicChannelBySlot(int, int, int, int, int, int, @Nullable String);
-    method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String iccTransmitApduLogicalChannelBySlot(int, int, int, int, int, int, int, @Nullable String);
+    method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String iccTransmitApduBasicChannelBySlot(int, int, int, int, int, int, @Nullable String);
+    method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String iccTransmitApduLogicalChannelBySlot(int, int, int, int, int, int, int, @Nullable String);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isAnyRadioPoweredOn();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isApnMetered(int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isApplicationOnUicc(int);
@@ -11833,6 +11837,8 @@
   public final class AccessibilityManager {
     method public int getAccessibilityWindowId(@Nullable android.os.IBinder);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public void performAccessibilityShortcut();
+    method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public void registerSystemAction(@NonNull android.app.RemoteAction, int);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public void unregisterSystemAction(int);
   }
 
 }
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index 118a508..1c6867c3 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -78,7 +78,6 @@
         "src/external/StatsPuller.cpp",
         "src/external/StatsPullerManager.cpp",
         "src/external/SubsystemSleepStatePuller.cpp",
-        "src/external/SurfaceflingerStatsPuller.cpp",
         "src/external/TrainInfoPuller.cpp",
         "src/FieldValue.cpp",
         "src/guardrail/StatsdStats.cpp",
@@ -148,7 +147,6 @@
         "libincident",
         "libservices",
         "libstatsmetadata",
-        "libtimestats_proto",
         "libutils",
     ],
 }
@@ -297,7 +295,6 @@
         "tests/external/puller_util_test.cpp",
         "tests/external/StatsCallbackPuller_test.cpp",
         "tests/external/StatsPuller_test.cpp",
-        "tests/external/SurfaceflingerStatsPuller_test.cpp",
         "tests/FieldValue_test.cpp",
         "tests/guardrail/StatsdStats_test.cpp",
         "tests/indexed_priority_queue_test.cpp",
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 19b9709..f6680f3 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -336,7 +336,7 @@
     }
 
     // Pulled events will start at field 10000.
-    // Next: 10068
+    // Next: 10069
     oneof pulled {
         WifiBytesTransfer wifi_bytes_transfer = 10000;
         WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001;
@@ -405,6 +405,7 @@
         VmsClientStats vms_client_stats = 10065;
         NotificationRemoteViews notification_remote_views = 10066;
         DangerousPermissionStateSampled dangerous_permission_state_sampled = 10067;
+        GraphicsStats graphics_stats = 10068;
     }
 
     // DO NOT USE field numbers above 100,000 in AOSP.
@@ -7553,3 +7554,69 @@
     optional int32 permission_flags = 4;
 }
 
+/**
+ * HWUI renders pipeline type: GL (0) or Vulkan (1).
+ */
+enum PipelineType {
+    GL = 0;
+    VULKAN = 1;
+}
+
+/**
+ * HWUI stats for a given app.
+ */
+message GraphicsStats {
+    // The package name of the app
+    optional string package_name = 1;
+
+    // The version code of the app
+    optional int64 version_code = 2;
+
+    // The start & end timestamps in UTC as
+    // milliseconds since January 1, 1970
+    // Compatible with java.util.Date#setTime()
+    optional int64 stats_start = 3;
+
+    optional int64 stats_end = 4;
+
+    // HWUI renders pipeline type: GL or Vulkan.
+    optional PipelineType pipeline = 5;
+
+    // Distinct frame count.
+    optional int32 total_frames = 6;
+
+    // Number of "missed vsync" events.
+    optional int32 missed_vsync_count = 7;
+
+    // Number of frames in triple-buffering scenario (high input latency)
+    optional int32 high_input_latency_count = 8;
+
+    // Number of "slow UI thread" events.
+    optional int32 slow_ui_thread_count = 9;
+
+    // Number of "slow bitmap upload" events.
+    optional int32 slow_bitmap_upload_count = 10;
+
+    // Number of "slow draw" events.
+    optional int32 slow_draw_count = 11;
+
+    // Number of frames that missed their deadline (aka, visibly janked)
+    optional int32 missed_deadline_count = 12;
+
+    // The frame time histogram for the package
+    optional FrameTimingHistogram cpu_histogram = 13
+    [(android.os.statsd.log_mode) = MODE_BYTES];
+
+    // The gpu frame time histogram for the package
+    optional FrameTimingHistogram gpu_histogram = 14
+    [(android.os.statsd.log_mode) = MODE_BYTES];
+
+    // UI mainline module version.
+    optional int64 version_ui_module = 15;
+
+    // If true, these are HWUI stats for up to a 24h period for a given app from today.
+    // If false, these are HWUI stats for a 24h period for a given app from the last complete
+    // day (yesterday). Stats from yesterday stay constant, while stats from today may change as
+    // more apps are running / rendering.
+    optional bool is_today = 16;
+}
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 50896f8..1d31873 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -41,7 +41,6 @@
 #include "StatsCallbackPullerDeprecated.h"
 #include "StatsCompanionServicePuller.h"
 #include "SubsystemSleepStatePuller.h"
-#include "SurfaceflingerStatsPuller.h"
 #include "TrainInfoPuller.h"
 #include "statslog.h"
 
@@ -273,10 +272,6 @@
         // App ops
         {{.atomTag = android::util::APP_OPS},
          {.puller = new StatsCompanionServicePuller(android::util::APP_OPS)}},
-        // SurfaceflingerStatsGlobalInfo
-        {{.atomTag = android::util::SURFACEFLINGER_STATS_GLOBAL_INFO},
-         {.puller =
-                  new SurfaceflingerStatsPuller(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO)}},
         // VmsClientStats
         {{.atomTag = android::util::VMS_CLIENT_STATS},
          {.additiveFields = {5, 6, 7, 8, 9, 10},
diff --git a/cmds/statsd/src/external/SurfaceflingerStatsPuller.cpp b/cmds/statsd/src/external/SurfaceflingerStatsPuller.cpp
deleted file mode 100644
index 23b2236..0000000
--- a/cmds/statsd/src/external/SurfaceflingerStatsPuller.cpp
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright 2019 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.
- */
-
-#include "SurfaceflingerStatsPuller.h"
-
-#include <cutils/compiler.h>
-
-#include <numeric>
-
-#include "logd/LogEvent.h"
-#include "stats_log_util.h"
-#include "statslog.h"
-
-namespace android {
-namespace os {
-namespace statsd {
-
-SurfaceflingerStatsPuller::SurfaceflingerStatsPuller(const int tagId) : StatsPuller(tagId) {
-}
-
-bool SurfaceflingerStatsPuller::PullInternal(std::vector<std::shared_ptr<LogEvent>>* data) {
-    switch (mTagId) {
-        case android::util::SURFACEFLINGER_STATS_GLOBAL_INFO:
-            return pullGlobalInfo(data);
-        default:
-            break;
-    }
-
-    return false;
-}
-
-static int64_t getTotalTime(
-        const google::protobuf::RepeatedPtrField<surfaceflinger::SFTimeStatsHistogramBucketProto>&
-                buckets) {
-    int64_t total = 0;
-    for (const auto& bucket : buckets) {
-        if (bucket.time_millis() == 1000) {
-            continue;
-        }
-
-        total += bucket.time_millis() * bucket.frame_count();
-    }
-
-    return total;
-}
-
-bool SurfaceflingerStatsPuller::pullGlobalInfo(std::vector<std::shared_ptr<LogEvent>>* data) {
-    std::string protoBytes;
-    if (CC_UNLIKELY(mStatsProvider)) {
-        protoBytes = mStatsProvider();
-    } else {
-        std::unique_ptr<FILE, decltype(&pclose)> pipe(popen("dumpsys SurfaceFlinger --timestats -dump --proto", "r"), pclose);
-        if (!pipe.get()) {
-            return false;
-        }
-        char buf[1024];
-        size_t bytesRead = 0;
-        do {
-            bytesRead = fread(buf, 1, sizeof(buf), pipe.get());
-            protoBytes.append(buf, bytesRead);
-        } while (bytesRead > 0);
-    }
-    surfaceflinger::SFTimeStatsGlobalProto proto;
-    proto.ParseFromString(protoBytes);
-
-    int64_t totalTime = getTotalTime(proto.present_to_present());
-
-    data->clear();
-    data->reserve(1);
-    std::shared_ptr<LogEvent> event =
-            make_shared<LogEvent>(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO, getWallClockNs(),
-                                  getElapsedRealtimeNs());
-    if (!event->write(proto.total_frames())) return false;
-    if (!event->write(proto.missed_frames())) return false;
-    if (!event->write(proto.client_composition_frames())) return false;
-    if (!event->write(proto.display_on_time())) return false;
-    if (!event->write(totalTime)) return false;
-    event->init();
-    data->emplace_back(event);
-
-    return true;
-}
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/cmds/statsd/src/external/SurfaceflingerStatsPuller.h b/cmds/statsd/src/external/SurfaceflingerStatsPuller.h
deleted file mode 100644
index ed7153e..0000000
--- a/cmds/statsd/src/external/SurfaceflingerStatsPuller.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright 2019 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.
- */
-
-#pragma once
-
-#include <timestatsproto/TimeStatsProtoHeader.h>
-
-#include "StatsPuller.h"
-
-namespace android {
-namespace os {
-namespace statsd {
-
-/**
- * Pull metrics from Surfaceflinger
- */
-class SurfaceflingerStatsPuller : public StatsPuller {
-public:
-    explicit SurfaceflingerStatsPuller(const int tagId);
-
-    // StatsPuller interface
-    bool PullInternal(std::vector<std::shared_ptr<LogEvent>>* data) override;
-
-protected:
-    // Test-only, for injecting fake data
-    using StatsProvider = std::function<std::string()>;
-    StatsProvider mStatsProvider;
-
-private:
-    bool pullGlobalInfo(std::vector<std::shared_ptr<LogEvent>>* data);
-};
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp b/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp
deleted file mode 100644
index 5b7a30d..0000000
--- a/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright 2019 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.
- */
-
-#undef LOG_TAG
-#define LOG_TAG "SurfaceflingerStatsPuller_test"
-
-#include "src/external/SurfaceflingerStatsPuller.h"
-#include "statslog.h"
-
-#include <gtest/gtest.h>
-#include <log/log.h>
-
-#ifdef __ANDROID__
-
-namespace android {
-namespace os {
-namespace statsd {
-
-class TestableSurfaceflingerStatsPuller : public SurfaceflingerStatsPuller {
-public:
-    TestableSurfaceflingerStatsPuller(const int tagId) : SurfaceflingerStatsPuller(tagId){};
-
-    void injectStats(const StatsProvider& statsProvider) {
-        mStatsProvider = statsProvider;
-    }
-};
-
-class SurfaceflingerStatsPullerTest : public ::testing::Test {
-public:
-    SurfaceflingerStatsPullerTest() {
-        const ::testing::TestInfo* const test_info =
-                ::testing::UnitTest::GetInstance()->current_test_info();
-        ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
-    }
-
-    ~SurfaceflingerStatsPullerTest() {
-        const ::testing::TestInfo* const test_info =
-                ::testing::UnitTest::GetInstance()->current_test_info();
-        ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
-    }
-};
-
-TEST_F(SurfaceflingerStatsPullerTest, pullGlobalStats) {
-    surfaceflinger::SFTimeStatsGlobalProto proto;
-    proto.set_total_frames(1);
-    proto.set_missed_frames(2);
-    proto.set_client_composition_frames(2);
-    proto.set_display_on_time(4);
-
-    auto bucketOne = proto.add_present_to_present();
-    bucketOne->set_time_millis(2);
-    bucketOne->set_frame_count(4);
-    auto bucketTwo = proto.add_present_to_present();
-    bucketTwo->set_time_millis(4);
-    bucketTwo->set_frame_count(1);
-    auto bucketThree = proto.add_present_to_present();
-    bucketThree->set_time_millis(1000);
-    bucketThree->set_frame_count(1);
-    static constexpr int64_t expectedAnimationMillis = 12;
-    TestableSurfaceflingerStatsPuller puller(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO);
-
-    puller.injectStats([&] {
-        return proto.SerializeAsString();
-    });
-    puller.ForceClearCache();
-    vector<std::shared_ptr<LogEvent>> outData;
-    puller.Pull(&outData);
-
-    ASSERT_EQ(1, outData.size());
-    EXPECT_EQ(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO, outData[0]->GetTagId());
-    EXPECT_EQ(proto.total_frames(), outData[0]->getValues()[0].mValue.long_value);
-    EXPECT_EQ(proto.missed_frames(), outData[0]->getValues()[1].mValue.long_value);
-    EXPECT_EQ(proto.client_composition_frames(), outData[0]->getValues()[2].mValue.long_value);
-    EXPECT_EQ(proto.display_on_time(), outData[0]->getValues()[3].mValue.long_value);
-    EXPECT_EQ(expectedAnimationMillis, outData[0]->getValues()[4].mValue.long_value);
-}
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
-#else
-GTEST_LOG_(INFO) << "This test does nothing.\n";
-#endif
diff --git a/config/boot-image-profile.txt b/config/boot-image-profile.txt
index 4d0acb3..4aaf727 100644
--- a/config/boot-image-profile.txt
+++ b/config/boot-image-profile.txt
@@ -22519,8 +22519,6 @@
 HSPLcom/android/internal/telephony/PhoneFactory;->makeDefaultPhones(Landroid/content/Context;)V
 HSPLcom/android/internal/telephony/PhoneInternalInterface$DataActivityState;-><init>(Ljava/lang/String;I)V
 HSPLcom/android/internal/telephony/PhoneInternalInterface$DataActivityState;->values()[Lcom/android/internal/telephony/PhoneInternalInterface$DataActivityState;
-HSPLcom/android/internal/telephony/PhoneStateIntentReceiver;-><init>(Landroid/content/Context;Landroid/os/Handler;)V
-HSPLcom/android/internal/telephony/PhoneStateIntentReceiver;->notifyServiceState(I)V
 HSPLcom/android/internal/telephony/PhoneSubInfoController;->callPhoneMethodWithPermissionCheck(ILjava/lang/String;Ljava/lang/String;Lcom/android/internal/telephony/PhoneSubInfoController$CallPhoneMethodHelper;Lcom/android/internal/telephony/PhoneSubInfoController$PermissionCheckHelper;)Ljava/lang/Object;
 HSPLcom/android/internal/telephony/PhoneSubInfoController;->getCarrierInfoForImsiEncryption(IILjava/lang/String;)Landroid/telephony/ImsiEncryptionInfo;
 HSPLcom/android/internal/telephony/PhoneSubInfoController;->getGroupIdLevel1ForSubscriber(ILjava/lang/String;)Ljava/lang/String;
@@ -37729,7 +37727,6 @@
 Lcom/android/internal/telephony/PhoneInternalInterface$DataActivityState;
 Lcom/android/internal/telephony/PhoneInternalInterface;
 Lcom/android/internal/telephony/PhoneNotifier;
-Lcom/android/internal/telephony/PhoneStateIntentReceiver;
 Lcom/android/internal/telephony/PhoneSubInfoController$CallPhoneMethodHelper;
 Lcom/android/internal/telephony/PhoneSubInfoController$PermissionCheckHelper;
 Lcom/android/internal/telephony/PhoneSubInfoController;
diff --git a/config/preloaded-classes b/config/preloaded-classes
index 97f009c..e53c74b 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -4841,7 +4841,6 @@
 com.android.internal.telephony.PhoneFactory
 com.android.internal.telephony.PhoneInternalInterface
 com.android.internal.telephony.PhoneNotifier
-com.android.internal.telephony.PhoneStateIntentReceiver
 com.android.internal.telephony.PhoneSubInfoController$CallPhoneMethodHelper
 com.android.internal.telephony.PhoneSubInfoController$PermissionCheckHelper
 com.android.internal.telephony.PhoneSubInfoController
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 032e824..4f3e8ec 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -45,8 +45,19 @@
 
     // Access modes for handleIncomingUser.
     public static final int ALLOW_NON_FULL = 0;
+    /**
+     * Allows access to a caller with {@link android.Manifest.permission#INTERACT_ACROSS_USERS}
+     * if in the same profile group.
+     * Otherwise, {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required.
+     */
     public static final int ALLOW_NON_FULL_IN_PROFILE = 1;
     public static final int ALLOW_FULL_ONLY = 2;
+    /**
+     * Allows access to a caller with {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES}
+     * or {@link android.Manifest.permission#INTERACT_ACROSS_USERS} if in the same profile group.
+     * Otherwise, {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required.
+     */
+    public static final int ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE = 3;
 
     /**
      * Verify that calling app has access to the given provider.
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 86f52af..fcdb7cc 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -94,8 +94,11 @@
     void updateNotificationChannelGroupForPackage(String pkg, int uid, in NotificationChannelGroup group);
     void updateNotificationChannelForPackage(String pkg, int uid, in NotificationChannel channel);
     NotificationChannel getNotificationChannel(String callingPkg, int userId, String pkg, String channelId);
+    NotificationChannel getConversationNotificationChannel(String callingPkg, int userId, String pkg, String channelId, String conversationId);
+    void createConversationNotificationChannelForPackage(String pkg, int uid, in NotificationChannel parentChannel, String conversationId);
     NotificationChannel getNotificationChannelForPackage(String pkg, int uid, String channelId, boolean includeDeleted);
     void deleteNotificationChannel(String pkg, String channelId);
+    void deleteConversationNotificationChannels(String pkg, int uid, String conversationId);
     ParceledListSlice getNotificationChannels(String callingPkg, String targetPkg, int userId);
     ParceledListSlice getNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted);
     int getNumNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted);
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 3eee1ae..a33c2c1 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -23,6 +23,7 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ShortcutInfo;
 import android.media.AudioAttributes;
 import android.net.Uri;
 import android.os.Parcel;
@@ -57,6 +58,22 @@
     public static final String DEFAULT_CHANNEL_ID = "miscellaneous";
 
     /**
+     * The formatter used by the system to create an id for notification
+     * channels when it automatically creates conversation channels on behalf of an app. The format
+     * string takes two arguments, in this order: the
+     * {@link #getId()} of the original notification channel, and the
+     * {@link ShortcutInfo#getId() id} of the conversation.
+     */
+    public static final String CONVERSATION_CHANNEL_ID_FORMAT = "%1$s : %2$s";
+
+    /**
+     * TODO: STOPSHIP  remove
+     * Conversation id to use for apps that aren't providing them yet.
+     * @hide
+     */
+    public static final String PLACEHOLDER_CONVERSATION_ID = "placeholder_id";
+
+    /**
      * The maximum length for text fields in a NotificationChannel. Fields will be truncated at this
      * limit.
      */
@@ -85,6 +102,8 @@
     private static final String ATT_BLOCKABLE_SYSTEM = "blockable_system";
     private static final String ATT_ALLOW_BUBBLE = "can_bubble";
     private static final String ATT_ORIG_IMP = "orig_imp";
+    private static final String ATT_PARENT_CHANNEL = "parent";
+    private static final String ATT_CONVERSATION_ID = "conv_id";
     private static final String DELIMITER = ",";
 
     /**
@@ -147,7 +166,7 @@
     private static final boolean DEFAULT_ALLOW_BUBBLE = true;
 
     @UnsupportedAppUsage
-    private final String mId;
+    private String mId;
     private String mName;
     private String mDesc;
     private int mImportance = DEFAULT_IMPORTANCE;
@@ -172,6 +191,8 @@
     private boolean mAllowBubbles = DEFAULT_ALLOW_BUBBLE;
     private boolean mImportanceLockedByOEM;
     private boolean mImportanceLockedDefaultApp;
+    private String mParentId = null;
+    private String mConversationId = null;
 
     /**
      * Creates a notification channel.
@@ -236,6 +257,8 @@
         mAllowBubbles = in.readBoolean();
         mImportanceLockedByOEM = in.readBoolean();
         mOriginalImportance = in.readInt();
+        mParentId = in.readString();
+        mConversationId = in.readString();
     }
 
     @Override
@@ -291,6 +314,8 @@
         dest.writeBoolean(mAllowBubbles);
         dest.writeBoolean(mImportanceLockedByOEM);
         dest.writeInt(mOriginalImportance);
+        dest.writeString(mParentId);
+        dest.writeString(mConversationId);
     }
 
     /**
@@ -363,6 +388,13 @@
     // Modifiable by apps on channel creation.
 
     /**
+     * @hide
+     */
+    public void setId(String id) {
+        mId = id;
+    }
+
+    /**
      * Sets what group this channel belongs to.
      *
      * Group information is only used for presentation, not for behavior.
@@ -502,6 +534,23 @@
     }
 
     /**
+     * Sets this channel as being person-centric. Different settings and functionality may be
+     * exposed for people-centric channels.
+     *
+     * @param parentChannelId The {@link #getId()} id} of the generic channel that notifications of
+     *                        this type would be posted to in absence of a specific conversation id.
+     *                        For example, if this channel represents 'Messages from Person A', the
+     *                        parent channel would be 'Messages.'
+     * @param conversationId The {@link ShortcutInfo#getId()} of the shortcut representing this
+     *                       channel's conversation.
+     */
+    public void setConversationId(@Nullable String parentChannelId,
+            @Nullable String conversationId) {
+        mParentId = parentChannelId;
+        mConversationId = conversationId;
+    }
+
+    /**
      * Returns the id of this channel.
      */
     public String getId() {
@@ -622,6 +671,22 @@
     }
 
     /**
+     * Returns the {@link #getId() id} of the parent notification channel to this channel, if it's
+     * a conversation related channel. See {@link #setConversationId(String, String)}.
+     */
+    public @Nullable String getParentChannelId() {
+        return mParentId;
+    }
+
+    /**
+     * Returns the {@link ShortcutInfo#getId() id} of the conversation backing this channel, if it's
+     * associated with a conversation. See {@link #setConversationId(String, String)}.
+     */
+    public @Nullable String getConversationId() {
+        return mConversationId;
+    }
+
+    /**
      * @hide
      */
     @SystemApi
@@ -761,6 +826,8 @@
         setBlockableSystem(safeBool(parser, ATT_BLOCKABLE_SYSTEM, false));
         setAllowBubbles(safeBool(parser, ATT_ALLOW_BUBBLE, DEFAULT_ALLOW_BUBBLE));
         setOriginalImportance(safeInt(parser, ATT_ORIG_IMP, DEFAULT_IMPORTANCE));
+        setConversationId(parser.getAttributeValue(ATT_PARENT_CHANNEL, null),
+                parser.getAttributeValue(ATT_CONVERSATION_ID, null));
     }
 
     @Nullable
@@ -885,6 +952,12 @@
         if (getOriginalImportance() != DEFAULT_IMPORTANCE) {
             out.attribute(null, ATT_ORIG_IMP, Integer.toString(getOriginalImportance()));
         }
+        if (getParentChannelId() != null) {
+            out.attribute(null, ATT_PARENT_CHANNEL, getParentChannelId());
+        }
+        if (getConversationId() != null) {
+            out.attribute(null, ATT_CONVERSATION_ID, getConversationId());
+        }
 
         // mImportanceLockedDefaultApp and mImportanceLockedByOEM have a different source of
         // truth and so aren't written to this xml file
@@ -1042,7 +1115,9 @@
                 && Objects.equals(getAudioAttributes(), that.getAudioAttributes())
                 && mImportanceLockedByOEM == that.mImportanceLockedByOEM
                 && mImportanceLockedDefaultApp == that.mImportanceLockedDefaultApp
-                && mOriginalImportance == that.mOriginalImportance;
+                && mOriginalImportance == that.mOriginalImportance
+                && Objects.equals(getParentChannelId(), that.getParentChannelId())
+                && Objects.equals(getConversationId(), that.getConversationId());
     }
 
     @Override
@@ -1052,7 +1127,8 @@
                 getUserLockedFields(),
                 isFgServiceShown(), mVibrationEnabled, mShowBadge, isDeleted(), getGroup(),
                 getAudioAttributes(), isBlockableSystem(), mAllowBubbles,
-                mImportanceLockedByOEM, mImportanceLockedDefaultApp, mOriginalImportance);
+                mImportanceLockedByOEM, mImportanceLockedDefaultApp, mOriginalImportance,
+                mParentId, mConversationId);
         result = 31 * result + Arrays.hashCode(mVibration);
         return result;
     }
@@ -1063,26 +1139,7 @@
         String output = "NotificationChannel{"
                 + "mId='" + mId + '\''
                 + ", mName=" + redactedName
-                + ", mDescription=" + (!TextUtils.isEmpty(mDesc) ? "hasDescription " : "")
-                + ", mImportance=" + mImportance
-                + ", mBypassDnd=" + mBypassDnd
-                + ", mLockscreenVisibility=" + mLockscreenVisibility
-                + ", mSound=" + mSound
-                + ", mLights=" + mLights
-                + ", mLightColor=" + mLightColor
-                + ", mVibration=" + Arrays.toString(mVibration)
-                + ", mUserLockedFields=" + Integer.toHexString(mUserLockedFields)
-                + ", mFgServiceShown=" + mFgServiceShown
-                + ", mVibrationEnabled=" + mVibrationEnabled
-                + ", mShowBadge=" + mShowBadge
-                + ", mDeleted=" + mDeleted
-                + ", mGroup='" + mGroup + '\''
-                + ", mAudioAttributes=" + mAudioAttributes
-                + ", mBlockableSystem=" + mBlockableSystem
-                + ", mAllowBubbles=" + mAllowBubbles
-                + ", mImportanceLockedByOEM=" + mImportanceLockedByOEM
-                + ", mImportanceLockedDefaultApp=" + mImportanceLockedDefaultApp
-                + ", mOriginalImp=" + mOriginalImportance
+                + getFieldsString()
                 + '}';
         pw.println(prefix + output);
     }
@@ -1092,7 +1149,12 @@
         return "NotificationChannel{"
                 + "mId='" + mId + '\''
                 + ", mName=" + mName
-                + ", mDescription=" + (!TextUtils.isEmpty(mDesc) ? "hasDescription " : "")
+                + getFieldsString()
+                + '}';
+    }
+
+    private String getFieldsString() {
+        return  ", mDescription=" + (!TextUtils.isEmpty(mDesc) ? "hasDescription " : "")
                 + ", mImportance=" + mImportance
                 + ", mBypassDnd=" + mBypassDnd
                 + ", mLockscreenVisibility=" + mLockscreenVisibility
@@ -1112,7 +1174,8 @@
                 + ", mImportanceLockedByOEM=" + mImportanceLockedByOEM
                 + ", mImportanceLockedDefaultApp=" + mImportanceLockedDefaultApp
                 + ", mOriginalImp=" + mOriginalImportance
-                + '}';
+                + ", mParent=" + mParentId
+                + ", mConversationId" + mConversationId;
     }
 
     /** @hide */
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index fdbb8bb..61c1098 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -843,6 +843,25 @@
     }
 
     /**
+     * Returns the notification channel settings for a given channel and conversation id.
+     *
+     * <p>The channel must belong to your package, or to a package you are an approved notification
+     * delegate for (see {@link #canNotifyAsPackage(String)}), or it will not be returned. To query
+     * a channel as a notification delegate, call this method from a context created for that
+     * package (see {@link Context#createPackageContext(String, int)}).</p>
+     */
+    public @Nullable NotificationChannel getNotificationChannel(@NonNull String channelId,
+            @NonNull String conversationId) {
+        INotificationManager service = getService();
+        try {
+            return service.getConversationNotificationChannel(mContext.getOpPackageName(),
+                    mContext.getUserId(), mContext.getPackageName(), channelId, conversationId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Returns all notification channels belonging to the calling package.
      *
      * <p>Approved notification delegates (see {@link #canNotifyAsPackage(String)}) can query
diff --git a/core/java/android/app/StatsManager.java b/core/java/android/app/StatsManager.java
index 8426374..dde6dda 100644
--- a/core/java/android/app/StatsManager.java
+++ b/core/java/android/app/StatsManager.java
@@ -26,7 +26,6 @@
 import android.os.IBinder;
 import android.os.IPullAtomCallback;
 import android.os.IPullAtomResultReceiver;
-import android.os.IStatsCompanionService;
 import android.os.IStatsManagerService;
 import android.os.IStatsPullerCallback;
 import android.os.IStatsd;
@@ -61,9 +60,6 @@
     private IStatsd mService;
 
     @GuardedBy("sLock")
-    private IStatsCompanionService mStatsCompanion;
-
-    @GuardedBy("sLock")
     private IStatsManagerService mStatsManagerService;
 
     /**
@@ -538,7 +534,7 @@
         }
         synchronized (sLock) {
             try {
-                IStatsCompanionService service = getIStatsCompanionServiceLocked();
+                IStatsManagerService service = getIStatsManagerServiceLocked();
                 PullAtomCallbackInternal rec =
                     new PullAtomCallbackInternal(atomTag, callback, executor);
                 service.registerPullAtomCallback(atomTag, coolDownNs, timeoutNs, additiveFields,
@@ -560,7 +556,7 @@
     public void unregisterPullAtomCallback(int atomTag) {
         synchronized (sLock) {
             try {
-                IStatsCompanionService service = getIStatsCompanionServiceLocked();
+                IStatsManagerService service = getIStatsManagerServiceLocked();
                 service.unregisterPullAtomCallback(atomTag);
             } catch (RemoteException e) {
                 throw new RuntimeException("Unable to unregister pull atom callback");
@@ -746,16 +742,6 @@
     }
 
     @GuardedBy("sLock")
-    private IStatsCompanionService getIStatsCompanionServiceLocked() {
-        if (mStatsCompanion != null) {
-            return mStatsCompanion;
-        }
-        mStatsCompanion = IStatsCompanionService.Stub.asInterface(
-                ServiceManager.getService("statscompanion"));
-        return mStatsCompanion;
-    }
-
-    @GuardedBy("sLock")
     private IStatsManagerService getIStatsManagerServiceLocked() {
         if (mStatsManagerService != null) {
             return mStatsManagerService;
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index f299d45..e6c89d9 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -17,9 +17,12 @@
 package android.app.admin;
 
 import android.annotation.UserIdInt;
+import android.content.ComponentName;
 import android.content.Intent;
+import android.os.UserHandle;
 
 import java.util.List;
+import java.util.Set;
 
 /**
  * Device policy manager local system service interface.
@@ -165,4 +168,23 @@
      * Do not call it directly. Use {@link DevicePolicyCache#getInstance()} instead.
      */
     protected abstract DeviceStateCache getDeviceStateCache();
+
+    /**
+     * Returns the combined set of the following:
+     * <ul>
+     * <li>The package names that the admin has previously set as allowed to request user consent
+     * for cross-profile communication, via {@link
+     * DevicePolicyManager#setCrossProfilePackages(ComponentName, Set)}.</li>
+     * <li>The default package names that are allowed to request user consent for cross-profile
+     * communication without being explicitly enabled by the admin , via {@link
+     * DevicePolicyManager#setDefaultCrossProfilePackages(ComponentName, UserHandle, Set)}.</li>
+     * </ul>
+     *
+     * @return the combined set of whitelisted package names set via
+     * {@link DevicePolicyManager#setCrossProfilePackages(ComponentName, Set)} and
+     * {@link DevicePolicyManager#setDefaultCrossProfilePackages(ComponentName, UserHandle, Set)}
+     *
+     * @hide
+     */
+    public abstract List<String> getAllCrossProfilePackages();
 }
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index b1b6f0d..cb1f055 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -2670,6 +2670,9 @@
         } else if (profile == BluetoothProfile.PAN) {
             BluetoothPan pan = new BluetoothPan(context, listener);
             return true;
+        } else if (profile == BluetoothProfile.PBAP) {
+            BluetoothPbap pbap = new BluetoothPbap(context, listener);
+            return true;
         } else if (profile == BluetoothProfile.HEALTH) {
             Log.e(TAG, "getProfileProxy(): BluetoothHealth is deprecated");
             return false;
@@ -2742,6 +2745,10 @@
                 BluetoothPan pan = (BluetoothPan) proxy;
                 pan.close();
                 break;
+            case BluetoothProfile.PBAP:
+                BluetoothPbap pbap = (BluetoothPbap) proxy;
+                pbap.close();
+                break;
             case BluetoothProfile.GATT:
                 BluetoothGatt gatt = (BluetoothGatt) proxy;
                 gatt.close();
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 5cb2907..1b40a18 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3240,15 +3240,40 @@
     }
 
     /**
-     * Same as {@link #bindService(Intent, ServiceConnection, int)}, but with an explicit userHandle
-     * argument for use by system server and other multi-user aware code.
-     * @hide
+     * Binds to a service in the given {@code user} in the same manner as
+     * {@link #bindService(Intent, ServiceConnection, int)}.
+     *
+     * <p>If the given {@code user} is in the same profile group and the target package is the
+     * same as the caller, {@code android.Manifest.permission.INTERACT_ACROSS_PROFILES} is
+     * sufficient. Otherwise, requires {@code android.Manifest.permission.INTERACT_ACROSS_USERS}
+     * for interacting with other users.
+     *
+     * @param service Identifies the service to connect to.  The Intent must
+     *      specify an explicit component name.
+     * @param conn Receives information as the service is started and stopped.
+     *      This must be a valid ServiceConnection object; it must not be null.
+     * @param flags Operation options for the binding.  May be 0,
+     *          {@link #BIND_AUTO_CREATE}, {@link #BIND_DEBUG_UNBIND},
+     *          {@link #BIND_NOT_FOREGROUND}, {@link #BIND_ABOVE_CLIENT},
+     *          {@link #BIND_ALLOW_OOM_MANAGEMENT}, {@link #BIND_WAIVE_PRIORITY}.
+     *          {@link #BIND_IMPORTANT}, or
+     *          {@link #BIND_ADJUST_WITH_ACTIVITY}.
+     * @return {@code true} if the system is in the process of bringing up a
+     *         service that your client has permission to bind to; {@code false}
+     *         if the system couldn't find the service. If this value is {@code true}, you
+     *         should later call {@link #unbindService} to release the
+     *         connection.
+     *
+     * @throws SecurityException if the client does not have the required permission to bind.
      */
-    @SystemApi
     @SuppressWarnings("unused")
-    @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
-    public boolean bindServiceAsUser(@RequiresPermission Intent service, ServiceConnection conn,
-            int flags, UserHandle user) {
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.INTERACT_ACROSS_USERS,
+            android.Manifest.permission.INTERACT_ACROSS_PROFILES
+    })
+    public boolean bindServiceAsUser(
+            @NonNull @RequiresPermission Intent service, @NonNull ServiceConnection conn, int flags,
+            @NonNull UserHandle user) {
         throw new RuntimeException("Not implemented. Must override in a subclass.");
     }
 
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 3bb0f92..c8f587f 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -6440,19 +6440,22 @@
      */
     public static final int FLAG_RECEIVER_NO_ABORT = 0x08000000;
     /**
-     * If set, when sending a broadcast <i>before boot has completed</i> only
+     * If set, when sending a broadcast <i>before the system has fully booted up
+     * (which is even before {@link #ACTION_LOCKED_BOOT_COMPLETED} has been sent)"</i> only
      * registered receivers will be called -- no BroadcastReceiver components
      * will be launched.  Sticky intent state will be recorded properly even
      * if no receivers wind up being called.  If {@link #FLAG_RECEIVER_REGISTERED_ONLY}
      * is specified in the broadcast intent, this flag is unnecessary.
      *
-     * <p>This flag is only for use by system sevices as a convenience to
-     * avoid having to implement a more complex mechanism around detection
+     * <p>This flag is only for use by system services (even services from mainline modules) as a
+     * convenience to avoid having to implement a more complex mechanism around detection
      * of boot completion.
      *
+     * <p>This is useful to system server mainline modules
+     *
      * @hide
      */
-    @UnsupportedAppUsage
+    @SystemApi
     public static final int FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT = 0x04000000;
     /**
      * Set when this broadcast is for a boot upgrade, a special mode that
diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java
index e897b91..9d57514 100644
--- a/core/java/android/content/pm/CrossProfileApps.java
+++ b/core/java/android/content/pm/CrossProfileApps.java
@@ -16,20 +16,25 @@
 package android.content.pm;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
+import android.net.Uri;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.Settings;
 
 import com.android.internal.R;
 import com.android.internal.util.UserIcons;
 
 import java.util.List;
+import java.util.Set;
 
 /**
  * Class for handling cross profile operations. Apps can use this class to interact with its
@@ -169,6 +174,86 @@
         }
     }
 
+    /**
+     * Returns whether the calling package can request to interact across profiles.
+     *
+     * <p>The package's current ability to interact across profiles can be checked with
+     * {@link #canInteractAcrossProfiles()}.
+     *
+     * <p>Specifically, returns whether the following are all true:
+     * <ul>
+     * <li>{@link #getTargetUserProfiles()} returns a non-empty list for the calling user.</li>
+     * <li>The calling app has requested</li>
+     * {@code android.Manifest.permission.INTERACT_ACROSS_PROFILES} in its manifest.
+     * <li>The calling package has either been whitelisted by default by the OEM or has been
+     * explicitly whitelisted by the admin via
+     * {@link android.app.admin.DevicePolicyManager#setCrossProfilePackages(ComponentName, Set)}.
+     * </li>
+     * </ul>
+     *
+     * @return true if the calling package can request to interact across profiles.
+     */
+    public boolean canRequestInteractAcrossProfiles() {
+        try {
+            return mService.canRequestInteractAcrossProfiles(mContext.getPackageName());
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns whether the calling package can interact across profiles.
+     *
+     * <p>The package's current ability to request to interact across profiles can be checked with
+     * {@link #canRequestInteractAcrossProfiles()}.
+     *
+     * <p>Specifically, returns whether the following are all true:
+     * <ul>
+     * <li>{@link #getTargetUserProfiles()} returns a non-empty list for the calling user.</li>
+     * <li>The user has previously consented to cross-profile communication for the calling
+     * package.</li>
+     * <li>The calling package has either been whitelisted by default by the OEM or has been
+     * explicitly whitelisted by the admin via
+     * {@link android.app.admin.DevicePolicyManager#setCrossProfilePackages(ComponentName, Set)}.
+     * </li>
+     * </ul>
+     *
+     * @return true if the calling package can interact across profiles.
+     * @throws SecurityException if {@code mContext.getPackageName()} does not belong to the
+     * calling UID.
+     */
+    public boolean canInteractAcrossProfiles() {
+        try {
+            return mService.canInteractAcrossProfiles(mContext.getPackageName());
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns an {@link Intent} to open the settings page that allows the user to decide whether
+     * the calling app can interact across profiles. The current state is given by
+     * {@link #canInteractAcrossProfiles()}.
+     *
+     * <p>Returns {@code null} if {@link #canRequestInteractAcrossProfiles()} is {@code false}.
+     *
+     * @return an {@link Intent} to open the settings page that allows the user to decide whether
+     * the app can interact across profiles
+     *
+     * @throws SecurityException if {@code mContext.getPackageName()} does not belong to the
+     * calling UID.
+     */
+    public @Nullable Intent createRequestInteractAcrossProfilesIntent() {
+        if (!canRequestInteractAcrossProfiles()) {
+            return null;
+        }
+        final Intent settingsIntent = new Intent();
+        settingsIntent.setAction(Settings.ACTION_MANAGE_CROSS_PROFILE_ACCESS);
+        final Uri packageUri = Uri.parse("package:" + mContext.getPackageName());
+        settingsIntent.setData(packageUri);
+        return settingsIntent;
+    }
+
     private void verifyCanAccessUser(UserHandle userHandle) {
         if (!getTargetUserProfiles().contains(userHandle)) {
             throw new SecurityException("Not allowed to access " + userHandle);
diff --git a/core/java/android/content/pm/ICrossProfileApps.aidl b/core/java/android/content/pm/ICrossProfileApps.aidl
index d2d66cb..c5db0cc 100644
--- a/core/java/android/content/pm/ICrossProfileApps.aidl
+++ b/core/java/android/content/pm/ICrossProfileApps.aidl
@@ -30,4 +30,6 @@
     void startActivityAsUser(in IApplicationThread caller, in String callingPackage,
             in ComponentName component, int userId, boolean launchMainActivity);
     List<UserHandle> getTargetUserProfiles(in String callingPackage);
+    boolean canInteractAcrossProfiles(in String callingPackage);
+    boolean canRequestInteractAcrossProfiles(in String callingPackage);
 }
\ No newline at end of file
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index b85c58a..4bfc40e 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2323,6 +2323,13 @@
     public static final String FEATURE_TELEPHONY_GSM = "android.hardware.telephony.gsm";
 
     /**
+     * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device
+     * has a telephony radio that support data.
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_TELEPHONY_DATA = "android.hardware.telephony.data";
+
+    /**
      * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
      * The device supports telephony carrier restriction mechanism.
      *
diff --git a/core/java/android/hardware/soundtrigger/ConversionUtil.java b/core/java/android/hardware/soundtrigger/ConversionUtil.java
index 8231c58..43f3787 100644
--- a/core/java/android/hardware/soundtrigger/ConversionUtil.java
+++ b/core/java/android/hardware/soundtrigger/ConversionUtil.java
@@ -16,6 +16,7 @@
 
 package android.hardware.soundtrigger;
 
+import android.annotation.Nullable;
 import android.hardware.soundtrigger.ModelParams;
 import android.media.AudioFormat;
 import android.media.audio.common.AudioConfig;
@@ -32,8 +33,6 @@
 import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
 import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
 
-import android.annotation.Nullable;
-
 import java.util.Arrays;
 import java.util.UUID;
 
@@ -48,6 +47,7 @@
                 properties.description,
                 properties.uuid,
                 properties.version,
+                properties.supportedModelArch,
                 properties.maxSoundModels,
                 properties.maxKeyPhrases,
                 properties.maxUsers,
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index 55505ba..d872009 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -101,6 +101,14 @@
         /** Voice detection engine version */
         public final int version;
 
+        /**
+         * String naming the architecture used for running the supported models.
+         * (eg. a platform running models on a DSP could implement this string to convey the DSP
+         * architecture used)
+         */
+        @NonNull
+        public final String supportedModelArch;
+
         /** Maximum number of active sound models */
         public final int maxSoundModels;
 
@@ -130,15 +138,17 @@
         public final boolean returnsTriggerInEvent;
 
         ModuleProperties(int id, @NonNull String implementor, @NonNull String description,
-                @NonNull String uuid, int version, int maxSoundModels, int maxKeyphrases,
-                int maxUsers, int recognitionModes, boolean supportsCaptureTransition,
-                int maxBufferMs, boolean supportsConcurrentCapture,
-                int powerConsumptionMw, boolean returnsTriggerInEvent) {
+                @NonNull String uuid, int version, @NonNull String supportedModelArch,
+                int maxSoundModels, int maxKeyphrases, int maxUsers, int recognitionModes,
+                boolean supportsCaptureTransition, int maxBufferMs,
+                boolean supportsConcurrentCapture, int powerConsumptionMw,
+                boolean returnsTriggerInEvent) {
             this.id = id;
             this.implementor = requireNonNull(implementor);
             this.description = requireNonNull(description);
             this.uuid = UUID.fromString(requireNonNull(uuid));
             this.version = version;
+            this.supportedModelArch = requireNonNull(supportedModelArch);
             this.maxSoundModels = maxSoundModels;
             this.maxKeyphrases = maxKeyphrases;
             this.maxUsers = maxUsers;
@@ -167,6 +177,7 @@
             String description = in.readString();
             String uuid = in.readString();
             int version = in.readInt();
+            String supportedModelArch = in.readString();
             int maxSoundModels = in.readInt();
             int maxKeyphrases = in.readInt();
             int maxUsers = in.readInt();
@@ -177,7 +188,7 @@
             int powerConsumptionMw = in.readInt();
             boolean returnsTriggerInEvent = in.readByte() == 1;
             return new ModuleProperties(id, implementor, description, uuid, version,
-                    maxSoundModels, maxKeyphrases, maxUsers, recognitionModes,
+                    supportedModelArch, maxSoundModels, maxKeyphrases, maxUsers, recognitionModes,
                     supportsCaptureTransition, maxBufferMs, supportsConcurrentCapture,
                     powerConsumptionMw, returnsTriggerInEvent);
         }
@@ -189,6 +200,7 @@
             dest.writeString(description);
             dest.writeString(uuid.toString());
             dest.writeInt(version);
+            dest.writeString(supportedModelArch);
             dest.writeInt(maxSoundModels);
             dest.writeInt(maxKeyphrases);
             dest.writeInt(maxUsers);
@@ -208,7 +220,8 @@
         @Override
         public String toString() {
             return "ModuleProperties [id=" + id + ", implementor=" + implementor + ", description="
-                    + description + ", uuid=" + uuid + ", version=" + version + ", maxSoundModels="
+                    + description + ", uuid=" + uuid + ", version=" + version
+                    + " , supportedModelArch=" + supportedModelArch + ", maxSoundModels="
                     + maxSoundModels + ", maxKeyphrases=" + maxKeyphrases + ", maxUsers="
                     + maxUsers + ", recognitionModes=" + recognitionModes
                     + ", supportsCaptureTransition=" + supportsCaptureTransition + ", maxBufferMs="
@@ -588,19 +601,19 @@
         }
     }
 
-    /*****************************************************************************
+    /**
      * A ModelParamRange is a representation of supported parameter range for a
      * given loaded model.
-     ****************************************************************************/
+     */
     public static final class ModelParamRange implements Parcelable {
 
         /**
-         * start of supported range inclusive
+         * The inclusive start of supported range.
          */
         public final int start;
 
         /**
-         * end of supported range inclusive
+         * The inclusive end of supported range.
          */
         public final int end;
 
@@ -609,31 +622,65 @@
             this.end = end;
         }
 
+        /** @hide */
         private ModelParamRange(@NonNull Parcel in) {
             this.start = in.readInt();
             this.end = in.readInt();
         }
 
         @NonNull
-        public static final Creator<ModelParamRange> CREATOR = new Creator<ModelParamRange>() {
-            @Override
-            @NonNull
-            public ModelParamRange createFromParcel(@NonNull Parcel in) {
-                return new ModelParamRange(in);
-            }
+        public static final Creator<ModelParamRange> CREATOR =
+                new Creator<ModelParamRange>() {
+                    @Override
+                    @NonNull
+                    public ModelParamRange createFromParcel(@NonNull Parcel in) {
+                        return new ModelParamRange(in);
+                    }
 
-            @Override
-            @NonNull
-            public ModelParamRange[] newArray(int size) {
-                return new ModelParamRange[size];
-            }
-        };
+                    @Override
+                    @NonNull
+                    public ModelParamRange[] newArray(int size) {
+                        return new ModelParamRange[size];
+                    }
+                };
 
+        /** @hide */
         @Override
         public int describeContents() {
             return 0;
         }
 
+        /** @hide */
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + (start);
+            result = prime * result + (end);
+            return result;
+        }
+
+        @Override
+        public boolean equals(@Nullable Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj == null) {
+                return false;
+            }
+            if (getClass() != obj.getClass()) {
+                return false;
+            }
+            ModelParamRange other = (ModelParamRange) obj;
+            if (start != other.start) {
+                return false;
+            }
+            if (end != other.end) {
+                return false;
+            }
+            return true;
+        }
+
         @Override
         public void writeToParcel(@NonNull Parcel dest, int flags) {
             dest.writeInt(start);
diff --git a/core/java/android/net/DhcpInfo.java b/core/java/android/net/DhcpInfo.java
index 98bab44..912df67 100644
--- a/core/java/android/net/DhcpInfo.java
+++ b/core/java/android/net/DhcpInfo.java
@@ -16,8 +16,8 @@
 
 package android.net;
 
-import android.os.Parcelable;
 import android.os.Parcel;
+import android.os.Parcelable;
 
 /**
  * A simple object for retrieving the results of a DHCP request.
@@ -67,12 +67,12 @@
         buf.append(NetworkUtils.intToInetAddress(addr).getHostAddress());
     }
 
-    /** Implement the Parcelable interface {@hide} */
+    /** Implement the Parcelable interface */
     public int describeContents() {
         return 0;
     }
 
-    /** Implement the Parcelable interface {@hide} */
+    /** Implement the Parcelable interface */
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(ipAddress);
         dest.writeInt(gateway);
@@ -83,7 +83,7 @@
         dest.writeInt(leaseDuration);
     }
 
-    /** Implement the Parcelable interface {@hide} */
+    /** Implement the Parcelable interface */
     public static final @android.annotation.NonNull Creator<DhcpInfo> CREATOR =
         new Creator<DhcpInfo>() {
             public DhcpInfo createFromParcel(Parcel in) {
diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java
index 45d0c73..09ec6c3 100644
--- a/core/java/android/net/IpSecManager.java
+++ b/core/java/android/net/IpSecManager.java
@@ -17,7 +17,6 @@
 
 import static com.android.internal.util.Preconditions.checkNotNull;
 
-import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.RequiresFeature;
 import android.annotation.RequiresPermission;
@@ -26,6 +25,7 @@
 import android.annotation.TestApi;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.net.annotations.PolicyDirection;
 import android.os.Binder;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
@@ -41,8 +41,6 @@
 
 import java.io.FileDescriptor;
 import java.io.IOException;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.net.DatagramSocket;
 import java.net.InetAddress;
 import java.net.Socket;
@@ -78,11 +76,6 @@
      */
     public static final int DIRECTION_OUT = 1;
 
-    /** @hide */
-    @IntDef(value = {DIRECTION_IN, DIRECTION_OUT})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface PolicyDirection {}
-
     /**
      * The Security Parameter Index (SPI) 0 indicates an unknown or invalid index.
      *
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index 33c39d4..739e817 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -587,15 +587,14 @@
     }
 
     /**
-     * Removes the NET_CAPABILITY_NOT_RESTRICTED capability if all the capabilities it provides are
-     * typically provided by restricted networks.
+     * Deduces that all the capabilities it provides are typically provided by restricted networks
+     * or not.
      *
-     * TODO: consider:
-     * - Renaming it to guessRestrictedCapability and make it set the
-     *   restricted capability bit in addition to clearing it.
+     * @return {@code true} if the network should be restricted.
      * @hide
      */
-    public void maybeMarkCapabilitiesRestricted() {
+    @SystemApi
+    public boolean deduceRestrictedCapability() {
         // Check if we have any capability that forces the network to be restricted.
         final boolean forceRestrictedCapability =
                 (mNetworkCapabilities & FORCE_RESTRICTED_CAPABILITIES) != 0;
@@ -609,8 +608,17 @@
         final boolean hasRestrictedCapabilities =
                 (mNetworkCapabilities & RESTRICTED_CAPABILITIES) != 0;
 
-        if (forceRestrictedCapability
-                || (hasRestrictedCapabilities && !hasUnrestrictedCapabilities)) {
+        return forceRestrictedCapability
+                || (hasRestrictedCapabilities && !hasUnrestrictedCapabilities);
+    }
+
+    /**
+     * Removes the NET_CAPABILITY_NOT_RESTRICTED capability if deducing the network is restricted.
+     *
+     * @hide
+     */
+    public void maybeMarkCapabilitiesRestricted() {
+        if (deduceRestrictedCapability()) {
             removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
         }
     }
diff --git a/core/java/android/net/annotations/PolicyDirection.java b/core/java/android/net/annotations/PolicyDirection.java
new file mode 100644
index 0000000..febd9b4
--- /dev/null
+++ b/core/java/android/net/annotations/PolicyDirection.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2019 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 android.net.annotations;
+
+import android.annotation.IntDef;
+import android.net.IpSecManager;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * IPsec traffic direction.
+ *
+ * <p>Mainline modules cannot reference hidden @IntDef. Moving this annotation to a separate class
+ * to allow others to statically include it.
+ *
+ * @hide
+ */
+@IntDef(value = {IpSecManager.DIRECTION_IN, IpSecManager.DIRECTION_OUT})
+@Retention(RetentionPolicy.SOURCE)
+public @interface PolicyDirection {}
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 3ef86ed..5d80ab6 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -68,10 +68,9 @@
     public static final int LOG_UID = 1007;
 
     /**
-     * Defines the UID/GID for the WIFI supplicant process.
-     * @hide
+     * Defines the UID/GID for the WIFI native processes like wificond, supplicant, hostapd,
+     * vendor HAL, etc.
      */
-    @UnsupportedAppUsage
     public static final int WIFI_UID = 1010;
 
     /**
@@ -758,11 +757,12 @@
 
     /**
      * Set the priority of a thread, based on Linux priorities.
-     * 
-     * @param tid The identifier of the thread/process to change.
+     *
+     * @param tid The identifier of the thread/process to change. It should be
+     * the native thread id but not the managed id of {@link java.lang.Thread}.
      * @param priority A Linux priority level, from -20 for highest scheduling
      * priority to 19 for lowest scheduling priority.
-     * 
+     *
      * @throws IllegalArgumentException Throws IllegalArgumentException if
      * <var>tid</var> does not exist.
      * @throws SecurityException Throws SecurityException if your process does
diff --git a/core/java/android/os/UpdateEngine.java b/core/java/android/os/UpdateEngine.java
index 16f7b39..73e1adf 100644
--- a/core/java/android/os/UpdateEngine.java
+++ b/core/java/android/os/UpdateEngine.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
+import android.content.res.AssetFileDescriptor;
 import android.os.IUpdateEngine;
 import android.os.IUpdateEngineCallback;
 import android.os.RemoteException;
@@ -349,16 +350,17 @@
     }
 
     /**
-     * Applies the payload passed as ParcelFileDescriptor {@code pfd} instead of
-     * using the {@code file://} scheme.
+     * Applies the payload passed as AssetFileDescriptor {@code assetFd}
+     * instead of using the {@code file://} scheme.
      *
      * <p>See {@link #applyPayload(String)} for {@code offset}, {@code size} and
      * {@code headerKeyValuePairs} parameters.
      */
-    public void applyPayload(@NonNull ParcelFileDescriptor pfd, long offset, long size,
+    public void applyPayload(@NonNull AssetFileDescriptor assetFd,
             @NonNull String[] headerKeyValuePairs) {
         try {
-            mUpdateEngine.applyPayloadFd(pfd, offset, size, headerKeyValuePairs);
+            mUpdateEngine.applyPayloadFd(assetFd.getParcelFileDescriptor(),
+                    assetFd.getStartOffset(), assetFd.getLength(), headerKeyValuePairs);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 9ee4cc3..a31c3d1 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -389,6 +389,21 @@
             "android.settings.MANAGE_UNKNOWN_APP_SOURCES";
 
     /**
+     * Activity Action: Show settings to allow configuration of cross-profile access for apps
+     *
+     * Input: Optionally, the Intent's data URI can specify the application package name to
+     * directly invoke the management GUI specific to the package name. For example
+     * "package:com.my.app".
+     * <p>
+     * Output: Nothing.
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_MANAGE_CROSS_PROFILE_ACCESS =
+            "android.settings.MANAGE_CROSS_PROFILE_ACCESS";
+
+    /**
      * Activity Action: Show the "Open by Default" page in a particular application's details page.
      * <p>
      * In some cases, a matching Activity may not exist, so ensure you safeguard against this.
@@ -14404,6 +14419,42 @@
     };
 
     /**
+     * Activity Action: Show screen for controlling which apps have access to manage external
+     * storage.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you safeguard against this.
+     * <p>
+     * If you want to control a specific app's access to manage external storage, use
+     * {@link #ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION} instead.
+     * <p>
+     * Output: Nothing.
+     * @see #ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION =
+            "android.settings.MANAGE_ALL_FILES_ACCESS_PERMISSION";
+
+    /**
+     * Activity Action: Show screen for controlling if the app specified in the data URI of the
+     * intent can manage external storage.
+     * <p>
+     * Launching the corresponding activity requires the permission
+     * {@link Manifest.permission#MANAGE_EXTERNAL_STORAGE}.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you safeguard against this.
+     * <p>
+     * Input: The Intent's data URI MUST specify the application package name whose ability of
+     * managing external storage you want to control.
+     * For example "package:com.my.app".
+     * <p>
+     * Output: Nothing.
+     * @see #ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION =
+            "android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION";
+
+    /**
      * Performs a strict and comprehensive check of whether a calling package is allowed to
      * write/modify system settings, as the condition differs for pre-M, M+, and
      * privileged/preinstalled apps. If the provided uid does not match the
@@ -14429,8 +14480,9 @@
      * current time.
      * @hide
      */
-    public static boolean checkAndNoteWriteSettingsOperation(Context context, int uid,
-            String callingPackage, boolean throwException) {
+    @SystemApi
+    public static boolean checkAndNoteWriteSettingsOperation(@NonNull Context context, int uid,
+            @NonNull String callingPackage, boolean throwException) {
         return isCallingPackageAllowedToPerformAppOpsProtectedOperation(context, uid,
                 callingPackage, throwException, AppOpsManager.OP_WRITE_SETTINGS,
                 PM_WRITE_SETTINGS, true);
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 67925bf..d7c6d0f 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -168,6 +168,22 @@
     public static final int RECOGNITION_MODE_USER_IDENTIFICATION
             = SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = { "MODEL_PARAM_" }, value = {
+            MODEL_PARAM_THRESHOLD_FACTOR,
+    })
+    public @interface ModelParams {}
+
+    /**
+     * Controls the sensitivity threshold adjustment factor for a given model.
+     * Negative value corresponds to less sensitive model (high threshold) and
+     * a positive value corresponds to a more sensitive model (low threshold).
+     * Default value is 0.
+     */
+    public static final int MODEL_PARAM_THRESHOLD_FACTOR =
+            android.hardware.soundtrigger.ModelParams.THRESHOLD_FACTOR;
+
     static final String TAG = "AlwaysOnHotwordDetector";
     static final boolean DBG = false;
 
@@ -198,6 +214,53 @@
     private int mAvailability = STATE_NOT_READY;
 
     /**
+     *  A ModelParamRange is a representation of supported parameter range for a
+     *  given loaded model.
+     */
+    public static final class ModelParamRange {
+        private final SoundTrigger.ModelParamRange mModelParamRange;
+
+        /** @hide */
+        ModelParamRange(SoundTrigger.ModelParamRange modelParamRange) {
+            mModelParamRange = modelParamRange;
+        }
+
+        /**
+         * The inclusive start of supported range.
+         *
+         * @return start of range
+         */
+        public int start() {
+            return mModelParamRange.start;
+        }
+
+        /**
+         * The inclusive end of supported range.
+         *
+         * @return end of range
+         */
+        public int end() {
+            return mModelParamRange.end;
+        }
+
+        @Override
+        @NonNull
+        public String toString() {
+            return mModelParamRange.toString();
+        }
+
+        @Override
+        public boolean equals(@Nullable Object obj) {
+            return mModelParamRange.equals(obj);
+        }
+
+        @Override
+        public int hashCode() {
+            return mModelParamRange.hashCode();
+        }
+    }
+
+    /**
      * Additional payload for {@link Callback#onDetected}.
      */
     public static class EventPayload {
@@ -445,6 +508,83 @@
     }
 
     /**
+     * Set a model specific {@link ModelParams} with the given value. This
+     * parameter will keep its value for the duration the model is loaded regardless of starting and
+     * stopping recognition. Once the model is unloaded, the value will be lost.
+     * {@link AlwaysOnHotwordDetector#queryParameter} should be checked first before calling this
+     * method.
+     *
+     * @param modelParam   {@link ModelParams}
+     * @param value        Value to set
+     * @return - {@link SoundTrigger#STATUS_OK} in case of success
+     *         - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached
+     *         - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter
+     *         - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or
+     *           if API is not supported by HAL
+     */
+    public int setParameter(@ModelParams int modelParam, int value) {
+        if (DBG) {
+            Slog.d(TAG, "setParameter(" + modelParam + ", " + value + ")");
+        }
+
+        synchronized (mLock) {
+            if (mAvailability == STATE_INVALID) {
+                throw new IllegalStateException("setParameter called on an invalid detector");
+            }
+
+            return setParameterLocked(modelParam, value);
+        }
+    }
+
+    /**
+     * Get a model specific {@link ModelParams}. This parameter will keep its value
+     * for the duration the model is loaded regardless of starting and stopping recognition.
+     * Once the model is unloaded, the value will be lost. If the value is not set, a default
+     * value is returned. See {@link ModelParams} for parameter default values.
+     * {@link AlwaysOnHotwordDetector#queryParameter} should be checked first before
+     * calling this method.
+     *
+     * @param modelParam   {@link ModelParams}
+     * @return value of parameter
+     */
+    public int getParameter(@ModelParams int modelParam) {
+        if (DBG) {
+            Slog.d(TAG, "getParameter(" + modelParam + ")");
+        }
+
+        synchronized (mLock) {
+            if (mAvailability == STATE_INVALID) {
+                throw new IllegalStateException("getParameter called on an invalid detector");
+            }
+
+            return getParameterLocked(modelParam);
+        }
+    }
+
+    /**
+     * Determine if parameter control is supported for the given model handle.
+     * This method should be checked prior to calling {@link AlwaysOnHotwordDetector#setParameter}
+     * or {@link AlwaysOnHotwordDetector#getParameter}.
+     *
+     * @param modelParam {@link ModelParams}
+     * @return supported range of parameter, null if not supported
+     */
+    @Nullable
+    public ModelParamRange queryParameter(@ModelParams int modelParam) {
+        if (DBG) {
+            Slog.d(TAG, "queryParameter(" + modelParam + ")");
+        }
+
+        synchronized (mLock) {
+            if (mAvailability == STATE_INVALID) {
+                throw new IllegalStateException("queryParameter called on an invalid detector");
+            }
+
+            return queryParameterLocked(modelParam);
+        }
+    }
+
+    /**
      * Creates an intent to start the enrollment for the associated keyphrase.
      * This intent must be invoked using {@link Context#startForegroundService(Intent)}.
      * Starting re-enrollment is only valid if the keyphrase is un-enrolled,
@@ -601,6 +741,47 @@
         return code;
     }
 
+    private int setParameterLocked(@ModelParams int modelParam, int value) {
+        try {
+            int code = mModelManagementService.setParameter(mVoiceInteractionService,
+                    mKeyphraseMetadata.id, modelParam, value);
+
+            if (code != STATUS_OK) {
+                Slog.w(TAG, "setParameter failed with error code " + code);
+            }
+
+            return code;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    private int getParameterLocked(@ModelParams int modelParam) {
+        try {
+            return mModelManagementService.getParameter(mVoiceInteractionService,
+                    mKeyphraseMetadata.id, modelParam);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Nullable
+    private ModelParamRange queryParameterLocked(@ModelParams int modelParam) {
+        try {
+            SoundTrigger.ModelParamRange modelParamRange =
+                    mModelManagementService.queryParameter(mVoiceInteractionService,
+                            mKeyphraseMetadata.id, modelParam);
+
+            if (modelParamRange == null) {
+                return null;
+            }
+
+            return new ModelParamRange(modelParamRange);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     private void notifyStateChangedLocked() {
         Message message = Message.obtain(mHandler, MSG_AVAILABILITY_CHANGED);
         message.arg1 = mAvailability;
diff --git a/core/java/android/text/SpannableStringInternal.java b/core/java/android/text/SpannableStringInternal.java
index b85ef76..4c9328a 100644
--- a/core/java/android/text/SpannableStringInternal.java
+++ b/core/java/android/text/SpannableStringInternal.java
@@ -40,9 +40,10 @@
 
         if (source instanceof Spanned) {
             if (source instanceof SpannableStringInternal) {
-                copySpans((SpannableStringInternal) source, start, end, ignoreNoCopySpan);
+                copySpansFromInternal(
+                        (SpannableStringInternal) source, start, end, ignoreNoCopySpan);
             } else {
-                copySpans((Spanned) source, start, end, ignoreNoCopySpan);
+                copySpansFromSpanned((Spanned) source, start, end, ignoreNoCopySpan);
             }
         }
     }
@@ -65,7 +66,7 @@
      * @param end End index in the source object.
      * @param ignoreNoCopySpan whether to copy NoCopySpans in the {@code source}
      */
-    private void copySpans(Spanned src, int start, int end, boolean ignoreNoCopySpan) {
+    private void copySpansFromSpanned(Spanned src, int start, int end, boolean ignoreNoCopySpan) {
         Object[] spans = src.getSpans(start, end, Object.class);
 
         for (int i = 0; i < spans.length; i++) {
@@ -94,7 +95,7 @@
      * @param end End index in the source object.
      * @param ignoreNoCopySpan copy NoCopySpan for backward compatible reasons.
      */
-    private void copySpans(SpannableStringInternal src, int start, int end,
+    private void copySpansFromInternal(SpannableStringInternal src, int start, int end,
             boolean ignoreNoCopySpan) {
         int count = 0;
         final int[] srcData = src.mSpanData;
@@ -555,12 +556,12 @@
      */
     @UnsupportedAppUsage
     private void copySpans(Spanned src, int start, int end) {
-        copySpans(src, start, end, false);
+        copySpansFromSpanned(src, start, end, false);
     }
 
     @UnsupportedAppUsage
     private void copySpans(SpannableStringInternal src, int start, int end) {
-        copySpans(src, start, end, false);
+        copySpansFromInternal(src, start, end, false);
     }
 
 
diff --git a/core/java/android/view/InsetsAnimationControlCallbacks.java b/core/java/android/view/InsetsAnimationControlCallbacks.java
index 6fdadc6..27edb0b 100644
--- a/core/java/android/view/InsetsAnimationControlCallbacks.java
+++ b/core/java/android/view/InsetsAnimationControlCallbacks.java
@@ -16,17 +16,28 @@
 
 package android.view;
 
+import android.view.InsetsController.LayoutInsetsDuringAnimation;
+import android.view.WindowInsetsAnimationCallback.AnimationBounds;
+import android.view.WindowInsetsAnimationCallback.InsetsAnimation;
+
 /**
  * Provide an interface to let InsetsAnimationControlImpl call back into its owner.
  * @hide
  */
 public interface InsetsAnimationControlCallbacks {
+
     /**
-     * Dispatch the animation started event to all listeners.
-     * @param animation
+     * Executes the necessary code to start the animation in the correct order, including:
+     * <ul>
+     *     <li>Dispatch {@link WindowInsetsAnimationCallback#onPrepare}</li>
+     *     <li>Update insets state and run layout according to {@code layoutDuringAnimation}</li>
+     *     <li>Dispatch {@link WindowInsetsAnimationCallback#onStart}</li>
+     *     <li>Dispatch {@link WindowInsetsAnimationControlListener#onReady}</li>
+     * </ul>
      */
-    void dispatchAnimationStarted(WindowInsetsAnimationCallback.InsetsAnimation animation,
-            WindowInsetsAnimationCallback.AnimationBounds bounds);
+    void startAnimation(InsetsAnimationControlImpl controller,
+            WindowInsetsAnimationControlListener listener, int types, InsetsAnimation animation,
+            AnimationBounds bounds, @LayoutInsetsDuringAnimation int layoutDuringAnimation);
 
     /**
      * Schedule the apply by posting the animation callback.
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index a3245b9..6589e75 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -16,6 +16,8 @@
 
 package android.view;
 
+import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_HIDDEN;
+import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
 import static android.view.InsetsState.ISIDE_BOTTOM;
 import static android.view.InsetsState.ISIDE_FLOATING;
 import static android.view.InsetsState.ISIDE_LEFT;
@@ -30,6 +32,7 @@
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.util.SparseSetArray;
+import android.view.InsetsController.LayoutInsetsDuringAnimation;
 import android.view.InsetsState.InternalInsetsSide;
 import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
 import android.view.WindowInsets.Type.InsetsType;
@@ -80,7 +83,8 @@
     public InsetsAnimationControlImpl(SparseArray<InsetsSourceControl> controls, Rect frame,
             InsetsState state, WindowInsetsAnimationControlListener listener,
             @InsetsType int types,
-            InsetsAnimationControlCallbacks controller, long durationMs, boolean fade) {
+            InsetsAnimationControlCallbacks controller, long durationMs, boolean fade,
+            @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation) {
         mControls = controls;
         mListener = listener;
         mTypes = types;
@@ -95,14 +99,11 @@
         mFrame = new Rect(frame);
         buildTypeSourcesMap(mTypeSideMap, mSideSourceMap, mControls);
 
-        // TODO: Check for controllability first and wait for IME if needed.
-        listener.onReady(this, types);
-
         mAnimation = new WindowInsetsAnimationCallback.InsetsAnimation(mTypes,
                 InsetsController.INTERPOLATOR, durationMs);
         mAnimation.setAlpha(getCurrentAlpha());
-        mController.dispatchAnimationStarted(mAnimation,
-                new AnimationBounds(mHiddenInsets, mShownInsets));
+        mController.startAnimation(this, listener, types, mAnimation,
+                new AnimationBounds(mHiddenInsets, mShownInsets), layoutInsetsDuringAnimation);
     }
 
     @Override
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 2a7a4e3..775490c 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -37,16 +37,20 @@
 import android.view.InsetsSourceConsumer.ShowResult;
 import android.view.InsetsState.InternalInsetsType;
 import android.view.SurfaceControl.Transaction;
+import android.view.ViewTreeObserver.OnPreDrawListener;
 import android.view.WindowInsets.Type;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsAnimationCallback.AnimationBounds;
 import android.view.WindowInsetsAnimationCallback.InsetsAnimation;
+import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
 import android.view.animation.Interpolator;
 import android.view.animation.PathInterpolator;
 
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 
 /**
@@ -67,6 +71,37 @@
     private @interface AnimationDirection{}
 
     /**
+     * Layout mode during insets animation: The views should be laid out as if the changing inset
+     * types are fully shown. Before starting the animation, {@link View#onApplyWindowInsets} will
+     * be called as if the changing insets types are shown, which will result in the views being
+     * laid out as if the insets are fully shown.
+     */
+    static final int LAYOUT_INSETS_DURING_ANIMATION_SHOWN = 0;
+
+    /**
+     * Layout mode during insets animation: The views should be laid out as if the changing inset
+     * types are fully hidden. Before starting the animation, {@link View#onApplyWindowInsets} will
+     * be called as if the changing insets types are hidden, which will result in the views being
+     * laid out as if the insets are fully hidden.
+     */
+    static final int LAYOUT_INSETS_DURING_ANIMATION_HIDDEN = 1;
+
+    /**
+     * Determines the behavior of how the views should be laid out during an insets animation that
+     * is controlled by the application by calling {@link #controlWindowInsetsAnimation}.
+     * <p>
+     * When the animation is system-initiated, the layout mode is always chosen such that the
+     * pre-animation layout will represent the opposite of the starting state, i.e. when insets
+     * are appearing, {@link #LAYOUT_INSETS_DURING_ANIMATION_SHOWN} will be used. When insets
+     * are disappearing, {@link #LAYOUT_INSETS_DURING_ANIMATION_HIDDEN} will be used.
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {LAYOUT_INSETS_DURING_ANIMATION_SHOWN,
+            LAYOUT_INSETS_DURING_ANIMATION_HIDDEN})
+    @interface LayoutInsetsDuringAnimation {
+    }
+
+    /**
      * Translation animation evaluator.
      */
     private static TypeEvaluator<Insets> sEvaluator = (fraction, startValue, endValue) -> Insets.of(
@@ -109,11 +144,7 @@
         @Override
         public void onReady(WindowInsetsAnimationController controller, int types) {
             mController = controller;
-            if (mShow) {
-                showDirectly(types);
-            } else {
-                hideDirectly(types);
-            }
+
             mAnimationDirection = mShow ? DIRECTION_SHOW : DIRECTION_HIDE;
             mAnimator = ObjectAnimator.ofObject(
                     controller,
@@ -131,7 +162,9 @@
                     onAnimationFinish();
                 }
             });
+            mStartingAnimation = true;
             mAnimator.start();
+            mStartingAnimation = false;
         }
 
         @Override
@@ -185,6 +218,7 @@
     private int mPendingTypesToShow;
 
     private int mLastLegacySoftInputMode;
+    private boolean mStartingAnimation;
 
     private SyncRtSurfaceTransactionApplier mApplier;
 
@@ -266,6 +300,14 @@
     }
 
     /**
+     * @see InsetsState#calculateVisibleInsets(Rect, Rect, int)
+     */
+    public Rect calculateVisibleInsets(Rect legacyVisibleInsets,
+            @SoftInputModeFlags int softInputMode) {
+        return mState.calculateVisibleInsets(mFrame, legacyVisibleInsets, softInputMode);
+    }
+
+    /**
      * Called when the server has dispatched us a new set of inset controls.
      */
     public void onControlsChanged(InsetsSourceControl[] activeControls) {
@@ -312,7 +354,7 @@
                 // Only one animator (with multiple InsetsType) can run at a time.
                 // previous one should be cancelled for simplicity.
                 cancelExistingAnimation();
-            } else if (consumer.isVisible()
+            } else if (consumer.isRequestedVisible()
                     && (mAnimationDirection == DIRECTION_NONE
                     || mAnimationDirection == DIRECTION_HIDE)) {
                 // no-op: already shown or animating in (because window visibility is
@@ -338,7 +380,7 @@
             InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i));
             if (mAnimationDirection == DIRECTION_SHOW) {
                 cancelExistingAnimation();
-            } else if (!consumer.isVisible()
+            } else if (!consumer.isRequestedVisible()
                     && (mAnimationDirection == DIRECTION_NONE
                     || mAnimationDirection == DIRECTION_HIDE)) {
                 // no-op: already hidden or animating out.
@@ -363,12 +405,14 @@
             listener.onCancelled();
             return;
         }
-        controlAnimationUnchecked(types, listener, mFrame, fromIme, durationMs, false /* fade */);
+        controlAnimationUnchecked(types, listener, mFrame, fromIme, durationMs, false /* fade */,
+                getLayoutInsetsDuringAnimationMode(types));
     }
 
     private void controlAnimationUnchecked(@InsetsType int types,
             WindowInsetsAnimationControlListener listener, Rect frame, boolean fromIme,
-            long durationMs, boolean fade) {
+            long durationMs, boolean fade,
+            @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation) {
         if (types == 0) {
             // nothing to animate.
             return;
@@ -398,7 +442,8 @@
         }
 
         final InsetsAnimationControlImpl controller = new InsetsAnimationControlImpl(controls,
-                frame, mState, listener, typesReady, this, durationMs, fade);
+                frame, mState, listener, typesReady, this, durationMs, fade,
+                layoutInsetsDuringAnimation);
         mAnimationControls.add(controller);
     }
 
@@ -412,7 +457,7 @@
         boolean isReady = true;
         for (int i = internalTypes.size() - 1; i >= 0; i--) {
             InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i));
-            boolean setVisible = !consumer.isVisible();
+            boolean setVisible = !consumer.isRequestedVisible();
             if (setVisible) {
                 // Show request
                 switch(consumer.requestShow(fromIme)) {
@@ -454,6 +499,29 @@
         return typesReady;
     }
 
+    private @LayoutInsetsDuringAnimation int getLayoutInsetsDuringAnimationMode(
+            @InsetsType int types) {
+
+        final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
+
+        // Generally, we want to layout the opposite of the current state. This is to make animation
+        // callbacks easy to use: The can capture the layout values and then treat that as end-state
+        // during the animation.
+        //
+        // However, if controlling multiple sources, we want to treat it as shown if any of the
+        // types is currently hidden.
+        for (int i = internalTypes.size() - 1; i >= 0; i--) {
+            InsetsSourceConsumer consumer = mSourceConsumers.get(internalTypes.valueAt(i));
+            if (consumer == null) {
+                continue;
+            }
+            if (!consumer.isRequestedVisible()) {
+                return LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
+            }
+        }
+        return LAYOUT_INSETS_DURING_ANIMATION_HIDDEN;
+    }
+
     private void cancelExistingControllers(@InsetsType int types) {
         for (int i = mAnimationControls.size() - 1; i >= 0; i--) {
             InsetsAnimationControlImpl control = mAnimationControls.get(i);
@@ -597,7 +665,9 @@
         // and hidden state insets are correct.
         controlAnimationUnchecked(
                 types, listener, mState.getDisplayFrame(), fromIme, listener.getDurationMs(),
-                true /* fade */);
+                true /* fade */, show
+                        ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN
+                        : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN);
     }
 
     private void hideDirectly(@InsetsType int types) {
@@ -629,18 +699,40 @@
 
     @VisibleForTesting
     @Override
-    public void dispatchAnimationStarted(InsetsAnimation animation, AnimationBounds bounds) {
-        mViewRoot.mView.dispatchWindowInsetsAnimationStarted(animation, bounds);
+    public void startAnimation(InsetsAnimationControlImpl controller,
+            WindowInsetsAnimationControlListener listener, int types, InsetsAnimation animation,
+            AnimationBounds bounds, int layoutDuringAnimation) {
+        if (layoutDuringAnimation == LAYOUT_INSETS_DURING_ANIMATION_SHOWN) {
+            showDirectly(types);
+        } else {
+            hideDirectly(types);
+        }
+        mViewRoot.mView.dispatchWindowInsetsAnimationPrepare(animation);
+        mViewRoot.mView.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
+            @Override
+            public boolean onPreDraw() {
+                mViewRoot.mView.getViewTreeObserver().removeOnPreDrawListener(this);
+                mViewRoot.mView.dispatchWindowInsetsAnimationStart(animation, bounds);
+                listener.onReady(controller, types);
+                return true;
+            }
+        });
+        mViewRoot.mView.invalidate();
     }
 
     @VisibleForTesting
     public void dispatchAnimationFinished(InsetsAnimation animation) {
-        mViewRoot.mView.dispatchWindowInsetsAnimationFinished(animation);
+        mViewRoot.mView.dispatchWindowInsetsAnimationFinish(animation);
     }
 
     @VisibleForTesting
     @Override
     public void scheduleApplyChangeInsets() {
+        if (mStartingAnimation) {
+            mAnimCallback.run();
+            mAnimCallbackScheduled = false;
+            return;
+        }
         if (!mAnimCallbackScheduled) {
             mViewRoot.mChoreographer.postCallback(Choreographer.CALLBACK_INSETS_ANIMATION,
                     mAnimCallback, null /* token*/);
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index 324d562..67ccfd6 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import android.annotation.Nullable;
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.os.Parcel;
@@ -23,6 +24,7 @@
 import android.view.InsetsState.InternalInsetsType;
 
 import java.io.PrintWriter;
+import java.util.Objects;
 
 /**
  * Represents the state of a single window generating insets for clients.
@@ -34,6 +36,7 @@
 
     /** Frame of the source in screen coordinate space */
     private final Rect mFrame;
+    private @Nullable Rect mVisibleFrame;
     private boolean mVisible;
 
     private final Rect mTmpFrame = new Rect();
@@ -54,6 +57,10 @@
         mFrame.set(frame);
     }
 
+    public void setVisibleFrame(@Nullable Rect visibleFrame) {
+        mVisibleFrame = visibleFrame != null ? new Rect(visibleFrame) : visibleFrame;
+    }
+
     public void setVisible(boolean visible) {
         mVisible = visible;
     }
@@ -66,6 +73,10 @@
         return mFrame;
     }
 
+    public @Nullable Rect getVisibleFrame() {
+        return mVisibleFrame;
+    }
+
     public boolean isVisible() {
         return mVisible;
     }
@@ -79,10 +90,22 @@
      *         source.
      */
     public Insets calculateInsets(Rect relativeFrame, boolean ignoreVisibility) {
+        return calculateInsets(relativeFrame, mFrame, ignoreVisibility);
+    }
+
+    /**
+     * Like {@link #calculateInsets(Rect, boolean)}, but will return visible insets.
+     */
+    public Insets calculateVisibleInsets(Rect relativeFrame) {
+        return calculateInsets(relativeFrame, mVisibleFrame != null ? mVisibleFrame : mFrame,
+                false /* ignoreVisibility */);
+    }
+
+    private Insets calculateInsets(Rect relativeFrame, Rect frame, boolean ignoreVisibility) {
         if (!ignoreVisibility && !mVisible) {
             return Insets.NONE;
         }
-        if (!mTmpFrame.setIntersect(mFrame, relativeFrame)) {
+        if (!mTmpFrame.setIntersect(frame, relativeFrame)) {
             return Insets.NONE;
         }
 
@@ -110,6 +133,9 @@
         pw.print(prefix);
         pw.print("InsetsSource type="); pw.print(InsetsState.typeToString(mType));
         pw.print(" frame="); pw.print(mFrame.toShortString());
+        if (mVisibleFrame != null) {
+            pw.print(" visibleFrmae="); pw.print(mVisibleFrame.toShortString());
+        }
         pw.print(" visible="); pw.print(mVisible);
         pw.println();
     }
@@ -123,6 +149,7 @@
 
         if (mType != that.mType) return false;
         if (mVisible != that.mVisible) return false;
+        if (!Objects.equals(mVisibleFrame, that.mVisibleFrame)) return false;
         return mFrame.equals(that.mFrame);
     }
 
@@ -137,6 +164,7 @@
     public InsetsSource(Parcel in) {
         mType = in.readInt();
         mFrame = in.readParcelable(null /* loader */);
+        mVisibleFrame = in.readParcelable(null /* loader */);
         mVisible = in.readBoolean();
     }
 
@@ -149,6 +177,7 @@
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(mType);
         dest.writeParcelable(mFrame, 0 /* flags*/);
+        dest.writeParcelable(mVisibleFrame, 0 /* flags */);
         dest.writeBoolean(mVisible);
     }
 
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index c6d9898..b2a5d91 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -53,7 +53,7 @@
     }
 
     protected final InsetsController mController;
-    protected boolean mVisible;
+    protected boolean mRequestedVisible;
     private final Supplier<Transaction> mTransactionSupplier;
     private final @InternalInsetsType int mType;
     private final InsetsState mState;
@@ -66,7 +66,7 @@
         mState = state;
         mTransactionSupplier = transactionSupplier;
         mController = controller;
-        mVisible = InsetsState.getDefaultVisibility(type);
+        mRequestedVisible = InsetsState.getDefaultVisibility(type);
     }
 
     public void setControl(@Nullable InsetsSourceControl control) {
@@ -94,12 +94,12 @@
 
     @VisibleForTesting
     public void show() {
-        setVisible(true);
+        setRequestedVisible(true);
     }
 
     @VisibleForTesting
     public void hide() {
-        setVisible(false);
+        setRequestedVisible(false);
     }
 
     /**
@@ -126,16 +126,16 @@
         if (mSourceControl == null) {
             return false;
         }
-        if (mState.getSource(mType).isVisible() == mVisible) {
+        if (mState.getSource(mType).isVisible() == mRequestedVisible) {
             return false;
         }
-        mState.getSource(mType).setVisible(mVisible);
+        mState.getSource(mType).setVisible(mRequestedVisible);
         return true;
     }
 
     @VisibleForTesting
-    public boolean isVisible() {
-        return mVisible;
+    public boolean isRequestedVisible() {
+        return mRequestedVisible;
     }
 
     /**
@@ -157,11 +157,15 @@
         // no-op for types that always return ShowResult#SHOW_IMMEDIATELY.
     }
 
-    private void setVisible(boolean visible) {
-        if (mVisible == visible) {
+    /**
+     * Sets requested visibility from the client, regardless of whether we are able to control it at
+     * the moment.
+     */
+    private void setRequestedVisible(boolean requestedVisible) {
+        if (mRequestedVisible == requestedVisible) {
             return;
         }
-        mVisible = visible;
+        mRequestedVisible = requestedVisible;
         applyLocalVisibilityOverride();
         mController.notifyVisibilityChanged();
     }
@@ -173,7 +177,7 @@
         }
 
         final Transaction t = mTransactionSupplier.get();
-        if (mVisible) {
+        if (mRequestedVisible) {
             t.show(mSourceControl.getLeash());
         } else {
             t.hide(mSourceControl.getLeash());
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index ae1e579..e33ca70 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -19,12 +19,14 @@
 import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
 import static android.view.ViewRootImpl.NEW_INSETS_MODE_IME;
 import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
+import static android.view.ViewRootImpl.sNewInsetsMode;
 import static android.view.WindowInsets.Type.IME;
 import static android.view.WindowInsets.Type.MANDATORY_SYSTEM_GESTURES;
 import static android.view.WindowInsets.Type.SIZE;
 import static android.view.WindowInsets.Type.SYSTEM_GESTURES;
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.indexOf;
+import static android.view.WindowInsets.Type.isVisibleInsetsType;
 import static android.view.WindowInsets.Type.systemBars;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
@@ -41,6 +43,7 @@
 import android.view.WindowInsets.Type;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowManager.LayoutParams;
+import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -186,6 +189,32 @@
                         : systemBars());
     }
 
+    public Rect calculateVisibleInsets(Rect frame, Rect legacyVisibleInsets,
+            @SoftInputModeFlags int softInputMode) {
+        if (sNewInsetsMode == NEW_INSETS_MODE_NONE) {
+            return legacyVisibleInsets;
+        }
+
+        Insets insets = Insets.NONE;
+        for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
+            InsetsSource source = mSources.get(type);
+            if (source == null) {
+                continue;
+            }
+            if (sNewInsetsMode != NEW_INSETS_MODE_FULL && type != ITYPE_IME) {
+                continue;
+            }
+
+            // Ignore everything that's not a system bar or IME.
+            int publicType = InsetsState.toPublicType(type);
+            if (!isVisibleInsetsType(publicType, softInputMode)) {
+                continue;
+            }
+            insets = Insets.max(source.calculateVisibleInsets(frame), insets);
+        }
+        return insets.toRect();
+    }
+
     private void processSource(InsetsSource source, Rect relativeFrame, boolean ignoreVisibility,
             Insets[] typeInsetsMap, @Nullable @InternalInsetsSide SparseIntArray typeSideMap,
             @Nullable boolean[] typeVisibilityMap) {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 0db80e2..13d609b 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -11117,7 +11117,21 @@
     }
 
     /**
-     * Dispatches {@link WindowInsetsAnimationCallback#onStarted(InsetsAnimation, AnimationBounds)}
+     * Dispatches {@link WindowInsetsAnimationCallback#onPrepare(InsetsAnimation)}
+     * when Window Insets animation is being prepared.
+     * @param animation current animation
+     *
+     * @see WindowInsetsAnimationCallback#onPrepare(InsetsAnimation)
+     */
+    public void dispatchWindowInsetsAnimationPrepare(
+            @NonNull InsetsAnimation animation) {
+        if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationCallback != null) {
+            mListenerInfo.mWindowInsetsAnimationCallback.onPrepare(animation);
+        }
+    }
+
+    /**
+     * Dispatches {@link WindowInsetsAnimationCallback#onStart(InsetsAnimation, AnimationBounds)}
      * when Window Insets animation is started.
      * @param animation current animation
      * @param bounds the upper and lower {@link AnimationBounds} that provides range of
@@ -11125,10 +11139,10 @@
      * @return the upper and lower {@link AnimationBounds}.
      */
     @NonNull
-    public AnimationBounds dispatchWindowInsetsAnimationStarted(
+    public AnimationBounds dispatchWindowInsetsAnimationStart(
             @NonNull InsetsAnimation animation, @NonNull AnimationBounds bounds) {
         if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationCallback != null) {
-            return mListenerInfo.mWindowInsetsAnimationCallback.onStarted(animation, bounds);
+            return mListenerInfo.mWindowInsetsAnimationCallback.onStart(animation, bounds);
         }
         return bounds;
     }
@@ -11149,13 +11163,13 @@
     }
 
     /**
-     * Dispatches {@link WindowInsetsAnimationCallback#onFinished(InsetsAnimation)}
+     * Dispatches {@link WindowInsetsAnimationCallback#onFinish(InsetsAnimation)}
      * when Window Insets animation finishes.
      * @param animation The current ongoing {@link InsetsAnimation}.
      */
-    public void dispatchWindowInsetsAnimationFinished(@NonNull InsetsAnimation animation) {
+    public void dispatchWindowInsetsAnimationFinish(@NonNull InsetsAnimation animation) {
         if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationCallback != null) {
-            mListenerInfo.mWindowInsetsAnimationCallback.onFinished(animation);
+            mListenerInfo.mWindowInsetsAnimationCallback.onFinish(animation);
         }
     }
 
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 5fb7177..047d7da 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -7199,13 +7199,23 @@
     }
 
     @Override
-    @NonNull
-    public AnimationBounds dispatchWindowInsetsAnimationStarted(
-            @NonNull InsetsAnimation animation, @NonNull AnimationBounds bounds) {
-        super.dispatchWindowInsetsAnimationStarted(animation, bounds);
+    public void dispatchWindowInsetsAnimationPrepare(
+            @NonNull InsetsAnimation animation) {
+        super.dispatchWindowInsetsAnimationPrepare(animation);
         final int count = getChildCount();
         for (int i = 0; i < count; i++) {
-            getChildAt(i).dispatchWindowInsetsAnimationStarted(animation, bounds);
+            getChildAt(i).dispatchWindowInsetsAnimationPrepare(animation);
+        }
+    }
+
+    @Override
+    @NonNull
+    public AnimationBounds dispatchWindowInsetsAnimationStart(
+            @NonNull InsetsAnimation animation, @NonNull AnimationBounds bounds) {
+        super.dispatchWindowInsetsAnimationStart(animation, bounds);
+        final int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            getChildAt(i).dispatchWindowInsetsAnimationStart(animation, bounds);
         }
         return bounds;
     }
@@ -7222,11 +7232,11 @@
     }
 
     @Override
-    public void dispatchWindowInsetsAnimationFinished(@NonNull InsetsAnimation animation) {
-        super.dispatchWindowInsetsAnimationFinished(animation);
+    public void dispatchWindowInsetsAnimationFinish(@NonNull InsetsAnimation animation) {
+        super.dispatchWindowInsetsAnimationFinish(animation);
         final int count = getChildCount();
         for (int i = 0; i < count; i++) {
-            getChildAt(i).dispatchWindowInsetsAnimationFinished(animation);
+            getChildAt(i).dispatchWindowInsetsAnimationFinish(animation);
         }
     }
 
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index bf8dc65..ca8ba4c 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1464,6 +1464,7 @@
             return;
         }
         mApplyInsetsRequested = true;
+        requestLayout();
 
         // If this changes during traversal, no need to schedule another one as it will dispatch it
         // during the current traversal.
@@ -2102,6 +2103,12 @@
         Trace.traceEnd(Trace.TRACE_TAG_VIEW);
     }
 
+    private void updateVisibleInsets() {
+        Rect visibleInsets = mInsetsController.calculateVisibleInsets(mPendingVisibleInsets,
+                mWindowAttributes.softInputMode);
+        mAttachInfo.mVisibleInsets.set(visibleInsets);
+    }
+
     InsetsController getInsetsController() {
         return mInsetsController;
     }
@@ -2250,7 +2257,7 @@
                     insetsChanged = true;
                 }
                 if (!mPendingVisibleInsets.equals(mAttachInfo.mVisibleInsets)) {
-                    mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
+                    updateVisibleInsets();
                     if (DEBUG_LAYOUT) Log.v(mTag, "Visible insets changing to: "
                             + mAttachInfo.mVisibleInsets);
                 }
@@ -2316,6 +2323,7 @@
 
         if (mApplyInsetsRequested) {
             mApplyInsetsRequested = false;
+            updateVisibleInsets();
             dispatchApplyInsets(host);
             if (mLayoutRequested) {
                 // Short-circuit catching a new layout request here, so
@@ -2500,7 +2508,7 @@
                     contentInsetsChanged = true;
                 }
                 if (visibleInsetsChanged) {
-                    mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
+                    updateVisibleInsets();
                     if (DEBUG_LAYOUT) Log.v(mTag, "Visible insets changing to: "
                             + mAttachInfo.mVisibleInsets);
                 }
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 9df131d..9291b56 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -29,6 +29,8 @@
 import static android.view.WindowInsets.Type.all;
 import static android.view.WindowInsets.Type.indexOf;
 import static android.view.WindowInsets.Type.systemBars;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
 
 import android.annotation.IntDef;
 import android.annotation.IntRange;
@@ -40,6 +42,7 @@
 import android.graphics.Rect;
 import android.util.SparseArray;
 import android.view.WindowInsets.Type.InsetsType;
+import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethod;
 
@@ -1289,6 +1292,17 @@
         public static @InsetsType int all() {
             return 0xFFFFFFFF;
         }
+
+        /**
+         * Checks whether the specified type is considered to be part of visible insets.
+         * @hide
+         */
+        public static boolean isVisibleInsetsType(int type,
+                @SoftInputModeFlags int softInputModeFlags) {
+            int softInputMode = softInputModeFlags & SOFT_INPUT_MASK_ADJUST;
+            return (type & Type.systemBars()) != 0
+                    || (softInputMode != SOFT_INPUT_ADJUST_NOTHING && (type & Type.ime()) != 0);
+        }
     }
 
     /**
diff --git a/core/java/android/view/WindowInsetsAnimationCallback.java b/core/java/android/view/WindowInsetsAnimationCallback.java
index 5e71f27..e84c3e3 100644
--- a/core/java/android/view/WindowInsetsAnimationCallback.java
+++ b/core/java/android/view/WindowInsetsAnimationCallback.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.graphics.Insets;
+import android.view.WindowInsets.Type;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.animation.Interpolator;
 
@@ -30,7 +31,47 @@
 public interface WindowInsetsAnimationCallback {
 
     /**
-     * Called when an inset animation gets started.
+     * Called when an insets animation is about to start and before the views have been laid out in
+     * the end state of the animation. The ordering of events during an insets animation is the
+     * following:
+     * <p>
+     * <ul>
+     *     <li>Application calls {@link WindowInsetsController#hideInputMethod()},
+     *     {@link WindowInsetsController#showInputMethod()},
+     *     {@link WindowInsetsController#controlInputMethodAnimation(long, WindowInsetsAnimationControlListener)}</li>
+     *     <li>onPrepare is called on the view hierarchy listeners</li>
+     *     <li>{@link View#onApplyWindowInsets} will be called with the end state of the
+     *     animation</li>
+     *     <li>View hierarchy gets laid out according to the changes the application has requested
+     *     due to the new insets being dispatched</li>
+     *     <li>{@link #onStart} is called <em>before</em> the view
+     *     hierarchy gets drawn in the new laid out state</li>
+     *     <li>{@link #onProgress} is called immediately after with the animation start state</li>
+     *     <li>The frame gets drawn.</li>
+     * </ul>
+     * <p>
+     * This ordering allows the application to inspect the end state after the animation has
+     * finished, and then revert to the starting state of the animation in the first
+     * {@link #onProgress} callback by using post-layout view properties like {@link View#setX} and
+     * related methods.
+     * <p>
+     * Note: If the animation is application controlled by using
+     * {@link WindowInsetsController#controlInputMethodAnimation}, the end state of the animation
+     * is undefined as the application may decide on the end state only by passing in the
+     * {@code shown} parameter when calling {@link WindowInsetsAnimationController#finish}. In this
+     * situation, the system will dispatch the insets in the opposite visibility state before the
+     * animation starts. Example: When controlling the input method with
+     * {@link WindowInsetsController#controlInputMethodAnimation} and the input method is currently
+     * showing, {@link View#onApplyWindowInsets} will receive a {@link WindowInsets} instance for
+     * which {@link WindowInsets#isVisible} will return {@code false} for {@link Type#ime}.
+     *
+     * @param animation The animation that is about to start.
+     */
+    default void onPrepare(@NonNull InsetsAnimation animation) {
+    }
+
+    /**
+     * Called when an insets animation gets started.
      * <p>
      * Note that, like {@link #onProgress}, dispatch of the animation start event is hierarchical:
      * It will starts at the root of the view hierarchy and then traverse it and invoke the callback
@@ -45,7 +86,7 @@
      *         subtree of the hierarchy.
      */
     @NonNull
-    default AnimationBounds onStarted(
+    default AnimationBounds onStart(
             @NonNull InsetsAnimation animation, @NonNull AnimationBounds bounds) {
         return bounds;
     }
@@ -72,12 +113,12 @@
     WindowInsets onProgress(@NonNull WindowInsets insets);
 
     /**
-     * Called when an inset animation has finished.
+     * Called when an insets animation has finished.
      *
      * @param animation The animation that has finished running. This will be the same instance as
-     *                  passed into {@link #onStarted}
+     *                  passed into {@link #onStart}
      */
-    default void onFinished(@NonNull InsetsAnimation animation) {
+    default void onFinish(@NonNull InsetsAnimation animation) {
     }
 
     /**
@@ -253,14 +294,14 @@
 
         /**
          * Insets both the lower and upper bound by the specified insets. This is to be used in
-         * {@link WindowInsetsAnimationCallback#onStarted} to indicate that a part of the insets has
+         * {@link WindowInsetsAnimationCallback#onStart} to indicate that a part of the insets has
          * been used to offset or clip its children, and the children shouldn't worry about that
          * part anymore.
          *
          * @param insets The amount to inset.
          * @return A copy of this instance inset in the given directions.
          * @see WindowInsets#inset
-         * @see WindowInsetsAnimationCallback#onStarted
+         * @see WindowInsetsAnimationCallback#onStart
          */
         @NonNull
         public AnimationBounds inset(@NonNull Insets insets) {
diff --git a/core/java/android/view/WindowInsetsAnimationControlListener.java b/core/java/android/view/WindowInsetsAnimationControlListener.java
index 8a226c1..f91254d 100644
--- a/core/java/android/view/WindowInsetsAnimationControlListener.java
+++ b/core/java/android/view/WindowInsetsAnimationControlListener.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import android.annotation.Hide;
 import android.annotation.NonNull;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.inputmethod.EditorInfo;
@@ -26,6 +27,12 @@
 public interface WindowInsetsAnimationControlListener {
 
     /**
+     * @hide
+     */
+    default void onPrepare(int types) {
+    }
+
+    /**
      * Called when the animation is ready to be controlled. This may be delayed when the IME needs
      * to redraw because of an {@link EditorInfo} change, or when the window is starting up.
      *
diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java
index 6de56be..9d7f292 100644
--- a/core/java/android/view/WindowInsetsController.java
+++ b/core/java/android/view/WindowInsetsController.java
@@ -149,7 +149,8 @@
      *
      * @param types The {@link InsetsType}s the application has requested to control.
      * @param durationMillis duration of animation in
-     *                       {@link java.util.concurrent.TimeUnit#MILLISECONDS}
+     *                       {@link java.util.concurrent.TimeUnit#MILLISECONDS}, or -1 if the
+     *                       animation doesn't have a predetermined duration.
      * @param listener The {@link WindowInsetsAnimationControlListener} that gets called when the
      *                 windows are ready to be controlled, among other callbacks.
      * @hide
@@ -162,7 +163,8 @@
      * modifying the position of the IME when it's causing insets.
      *
      * @param durationMillis duration of the animation in
-     *                       {@link java.util.concurrent.TimeUnit#MILLISECONDS}
+     *                       {@link java.util.concurrent.TimeUnit#MILLISECONDS}, or -1 if the
+     *                       animation doesn't have a predetermined duration.
      * @param listener The {@link WindowInsetsAnimationControlListener} that gets called when the
      *                 IME are ready to be controlled, among other callbacks.
      */
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index ff31bcc..2e5a4b5 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -31,6 +31,7 @@
 import android.annotation.SystemService;
 import android.annotation.TestApi;
 import android.annotation.UserIdInt;
+import android.app.RemoteAction;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.Context;
@@ -1209,6 +1210,61 @@
     }
 
     /**
+     * Register the provided {@link RemoteAction} with the given actionId
+     *
+     * @param action The remote action to be registered with the given actionId as system action.
+     * @param actionId The id uniquely identify the system action.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
+    public void registerSystemAction(@NonNull RemoteAction action, int actionId) {
+        final IAccessibilityManager service;
+        synchronized (mLock) {
+            service = getServiceLocked();
+            if (service == null) {
+                return;
+            }
+        }
+        try {
+            service.registerSystemAction(action, actionId);
+
+            if (DEBUG) {
+                Log.i(LOG_TAG, "System action " + action.getTitle() + " is registered.");
+            }
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error registering system action " + action.getTitle() + " ", re);
+        }
+    }
+
+   /**
+     * Unregister a system action with the given actionId
+     *
+     * @param actionId The id uniquely identify the system action to be unregistered.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
+    public void unregisterSystemAction(int actionId) {
+        final IAccessibilityManager service;
+        synchronized (mLock) {
+            service = getServiceLocked();
+            if (service == null) {
+                return;
+            }
+        }
+        try {
+            service.unregisterSystemAction(actionId);
+
+            if (DEBUG) {
+                Log.i(LOG_TAG, "System action with actionId " + actionId + " is unregistered.");
+            }
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error unregistering system action with actionId " + actionId + " ", re);
+        }
+    }
+
+    /**
      * Notifies that the accessibility button in the system's navigation area has been clicked
      *
      * @param displayId The logical display id.
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 36515b3..392db57 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -16,6 +16,7 @@
 
 package android.view.accessibility;
 
+import android.app.RemoteAction;
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.IAccessibilityServiceConnection;
 import android.accessibilityservice.IAccessibilityServiceClient;
@@ -82,4 +83,7 @@
     int getAccessibilityWindowId(IBinder windowToken);
 
     long getRecommendedTimeoutMillis();
+
+    oneway void registerSystemAction(in RemoteAction action, int actionId);
+    oneway void unregisterSystemAction(int actionId);
 }
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 67ce8d2..f3007a7 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -93,9 +93,12 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.CancellationException;
-import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -429,7 +432,10 @@
      * in a background thread. Later, if there is an actual startInput it will wait on
      * main thread till the background thread completes.
      */
-    private CompletableFuture<Void> mWindowFocusGainFuture;
+    private Future<?> mWindowFocusGainFuture;
+
+    private ExecutorService mStartInputWorker = Executors.newSingleThreadExecutor(
+            new ImeThreadFactory("StartInputWorker"));
 
     /**
      * The instance that has previously been sent to the input method.
@@ -790,6 +796,19 @@
         }
     }
 
+    private static class ImeThreadFactory implements ThreadFactory {
+        private final String mThreadName;
+
+        ImeThreadFactory(String name) {
+            mThreadName = name;
+        }
+
+        @Override
+        public Thread newThread(Runnable r) {
+            return new Thread(r, mThreadName);
+        }
+    }
+
     final IInputMethodClient.Stub mClient = new IInputMethodClient.Stub() {
         @Override
         protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
@@ -1978,7 +1997,7 @@
         if (mWindowFocusGainFuture != null) {
             mWindowFocusGainFuture.cancel(false/* mayInterruptIfRunning */);
         }
-        mWindowFocusGainFuture = CompletableFuture.runAsync(() -> {
+        mWindowFocusGainFuture = mStartInputWorker.submit(() -> {
             if (checkFocusNoStartInput(forceNewFocus1)) {
                 // We need to restart input on the current focus view.  This
                 // should be done in conjunction with telling the system service
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index a0cf534..20af76b 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -5921,8 +5921,6 @@
         // The offsets of that last touch down event. Remembered to start selection there.
         private int mMinTouchOffset, mMaxTouchOffset;
 
-        private boolean mGestureStayedInTapRegion;
-
         // Where the user first starts the drag motion.
         private int mStartOffset = -1;
 
@@ -6029,8 +6027,7 @@
                                 eventX, eventY);
 
                         // Double tap detection
-                        if (mGestureStayedInTapRegion
-                                && mTouchState.isMultiTapInSameArea()
+                        if (mTouchState.isMultiTapInSameArea()
                                 && (isMouse || isPositionOnText(eventX, eventY))) {
                             if (TextView.DEBUG_CURSOR) {
                                 logCursor("SelectionModifierCursorController: onTouchEvent",
@@ -6043,7 +6040,6 @@
                             }
                             mDiscardNextActionUp = true;
                         }
-                        mGestureStayedInTapRegion = true;
                         mHaventMovedEnoughToStartDrag = true;
                     }
                     break;
@@ -6059,25 +6055,8 @@
                     break;
 
                 case MotionEvent.ACTION_MOVE:
-                    final ViewConfiguration viewConfig = ViewConfiguration.get(
-                            mTextView.getContext());
-
-                    if (mGestureStayedInTapRegion || mHaventMovedEnoughToStartDrag) {
-                        final float deltaX = eventX - mTouchState.getLastDownX();
-                        final float deltaY = eventY - mTouchState.getLastDownY();
-                        final float distanceSquared = deltaX * deltaX + deltaY * deltaY;
-
-                        if (mGestureStayedInTapRegion) {
-                            int doubleTapTouchSlop = viewConfig.getScaledDoubleTapTouchSlop();
-                            mGestureStayedInTapRegion =
-                                    distanceSquared <= doubleTapTouchSlop * doubleTapTouchSlop;
-                        }
-                        if (mHaventMovedEnoughToStartDrag) {
-                            // We don't start dragging until the user has moved enough.
-                            int touchSlop = viewConfig.getScaledTouchSlop();
-                            mHaventMovedEnoughToStartDrag =
-                                    distanceSquared <= touchSlop * touchSlop;
-                        }
+                    if (mHaventMovedEnoughToStartDrag) {
+                        mHaventMovedEnoughToStartDrag = !mTouchState.isMovedEnoughForDrag();
                     }
 
                     if (isMouse && !isDragAcceleratorActive()) {
diff --git a/core/java/android/widget/EditorTouchState.java b/core/java/android/widget/EditorTouchState.java
index d53099d..6277afe 100644
--- a/core/java/android/widget/EditorTouchState.java
+++ b/core/java/android/widget/EditorTouchState.java
@@ -31,13 +31,15 @@
 import java.lang.annotation.RetentionPolicy;
 
 /**
- * Helper class used by {@link Editor} to track state for touch events.
+ * Helper class used by {@link Editor} to track state for touch events. Ideally the logic here
+ * should be replaced with {@link android.view.GestureDetector}.
  *
  * @hide
  */
 @VisibleForTesting(visibility = PACKAGE)
 public class EditorTouchState {
     private float mLastDownX, mLastDownY;
+    private long mLastDownMillis;
     private float mLastUpX, mLastUpY;
     private long mLastUpMillis;
 
@@ -106,9 +108,18 @@
         final int action = event.getActionMasked();
         if (action == MotionEvent.ACTION_DOWN) {
             final boolean isMouse = event.isFromSource(InputDevice.SOURCE_MOUSE);
+
+            // We check both the time between the last up and current down event, as well as the
+            // time between the first down and up events. The latter check is necessary to handle
+            // the case when the user taps, drags/holds for some time, and then lifts up and
+            // quickly taps in the same area. This scenario should not be treated as a double-tap.
+            // This follows the behavior in GestureDetector.
             final long millisSinceLastUp = event.getEventTime() - mLastUpMillis;
+            final long millisBetweenLastDownAndLastUp = mLastUpMillis - mLastDownMillis;
+
             // Detect double tap and triple click.
             if (millisSinceLastUp <= ViewConfiguration.getDoubleTapTimeout()
+                    && millisBetweenLastDownAndLastUp <= ViewConfiguration.getDoubleTapTimeout()
                     && (mMultiTapStatus == MultiTapStatus.FIRST_TAP
                     || (mMultiTapStatus == MultiTapStatus.DOUBLE_TAP && isMouse))) {
                 if (mMultiTapStatus == MultiTapStatus.FIRST_TAP) {
@@ -133,6 +144,7 @@
             }
             mLastDownX = event.getX();
             mLastDownY = event.getY();
+            mLastDownMillis = event.getEventTime();
             mMovedEnoughForDrag = false;
             mIsDragCloseToVertical = false;
         } else if (action == MotionEvent.ACTION_UP) {
diff --git a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
index 0a0d05c..de204ba 100644
--- a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
+++ b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
@@ -16,6 +16,7 @@
 package com.android.internal.app;
 
 import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
 import static android.view.accessibility.AccessibilityManager.ShortcutType;
 
 import android.accessibilityservice.AccessibilityServiceInfo;
@@ -24,13 +25,17 @@
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.AlertDialog;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.UserHandle;
 import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.ArraySet;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -49,7 +54,10 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
+import java.util.StringJoiner;
 
 /**
  * Activity used to display and persist a service or feature target for the Accessibility button.
@@ -59,12 +67,46 @@
     private static final String MAGNIFICATION_COMPONENT_ID =
             "com.android.server.accessibility.MagnificationController";
 
+    private static final char SERVICES_SEPARATOR = ':';
+    private static final TextUtils.SimpleStringSplitter sStringColonSplitter =
+            new TextUtils.SimpleStringSplitter(SERVICES_SEPARATOR);
+    private static final int ACCESSIBILITY_BUTTON_USER_TYPE = convertToUserType(
+            ACCESSIBILITY_BUTTON);
+    private static final int ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE = convertToUserType(
+            ACCESSIBILITY_SHORTCUT_KEY);
+
     private int mShortcutType;
     private List<AccessibilityButtonTarget> mTargets = new ArrayList<>();
     private AlertDialog mAlertDialog;
     private TargetAdapter mTargetAdapter;
 
     /**
+     * Annotation for different user shortcut type UI type.
+     *
+     * {@code DEFAULT} for displaying default value.
+     * {@code SOFTWARE} for displaying specifying the accessibility services or features which
+     * choose accessibility button in the navigation bar as preferred shortcut.
+     * {@code HARDWARE} for displaying specifying the accessibility services or features which
+     * choose accessibility shortcut as preferred shortcut.
+     * {@code TRIPLETAP} for displaying specifying magnification to be toggled via quickly
+     * tapping screen 3 times as preferred shortcut.
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            UserShortcutType.DEFAULT,
+            UserShortcutType.SOFTWARE,
+            UserShortcutType.HARDWARE,
+            UserShortcutType.TRIPLETAP,
+    })
+    /** Denotes the user shortcut type. */
+    public @interface UserShortcutType {
+        int DEFAULT = 0;
+        int SOFTWARE = 1; // 1 << 0
+        int HARDWARE = 2; // 1 << 1
+        int TRIPLETAP = 4; // 1 << 2
+    }
+
+    /**
      * Annotation for different accessibilityService fragment UI type.
      *
      * {@code LEGACY} for displaying appearance aligned with sdk version Q accessibility service
@@ -395,19 +437,63 @@
     }
 
     private void onTargetDeleted(AdapterView<?> parent, View view, int position, long id) {
-        // TODO(b/146967898): disable service when deleting the target and the target only have
-        //  last one shortcut item, only remove it from shortcut list otherwise.
-        if ((mShortcutType == ACCESSIBILITY_BUTTON) && (mTargets.get(position).mFragmentType
-                != AccessibilityServiceFragmentType.LEGACY)) {
-            mTargets.remove(position);
-            mTargetAdapter.notifyDataSetChanged();
+        final AccessibilityButtonTarget target = mTargets.get(position);
+        final ComponentName targetComponentName =
+                ComponentName.unflattenFromString(target.getId());
+
+        switch (target.getFragmentType()) {
+            case AccessibilityServiceFragmentType.INVISIBLE:
+                onInvisibleTargetDeleted(targetComponentName);
+                break;
+            case AccessibilityServiceFragmentType.INTUITIVE:
+                onIntuitiveTargetDeleted(targetComponentName);
+                break;
+            case AccessibilityServiceFragmentType.LEGACY:
+            case AccessibilityServiceFragmentType.BOUNCE:
+                // Do nothing
+                break;
+            default:
+                throw new IllegalStateException("Unexpected fragment type");
         }
 
+        mTargets.remove(position);
+        mTargetAdapter.notifyDataSetChanged();
+
         if (mTargets.isEmpty()) {
             mAlertDialog.dismiss();
         }
     }
 
+    private void onInvisibleTargetDeleted(ComponentName componentName) {
+        if (mShortcutType == ACCESSIBILITY_BUTTON) {
+            optOutValueFromSettings(this, ACCESSIBILITY_BUTTON_USER_TYPE, componentName);
+
+            if (!hasValueInSettings(this,
+                    ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE, componentName)) {
+                setAccessibilityServiceState(this, componentName, /* enabled= */ false);
+            }
+        } else if (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY) {
+            optOutValueFromSettings(this, ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE, componentName);
+
+            if (!hasValueInSettings(this,
+                    ACCESSIBILITY_BUTTON_USER_TYPE, componentName)) {
+                setAccessibilityServiceState(this, componentName, /* enabled= */ false);
+            }
+        } else {
+            throw new IllegalArgumentException("Unsupported shortcut type:" + mShortcutType);
+        }
+    }
+
+    private void onIntuitiveTargetDeleted(ComponentName componentName) {
+        if (mShortcutType == ACCESSIBILITY_BUTTON) {
+            optOutValueFromSettings(this, ACCESSIBILITY_BUTTON_USER_TYPE, componentName);
+        } else if (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY) {
+            optOutValueFromSettings(this, ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE, componentName);
+        } else {
+            throw new IllegalArgumentException("Unsupported shortcut type:" + mShortcutType);
+        }
+    }
+
     private void onCancelButtonClicked() {
         mTargetAdapter.setShortcutMenuMode(ShortcutMenuMode.LAUNCH);
         mTargetAdapter.notifyDataSetChanged();
@@ -437,4 +523,166 @@
         mAlertDialog.getListView().setOnItemClickListener(
                 isEditMenuMode ? this::onTargetDeleted : this::onTargetSelected);
     }
+
+    /**
+     * @return the set of enabled accessibility services for {@param userId}. If there are no
+     * services, it returns the unmodifiable {@link Collections#emptySet()}.
+     */
+    private Set<ComponentName> getEnabledServicesFromSettings(Context context, int userId) {
+        final String enabledServicesSetting = Settings.Secure.getStringForUser(
+                context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+                userId);
+        if (TextUtils.isEmpty(enabledServicesSetting)) {
+            return Collections.emptySet();
+        }
+
+        final Set<ComponentName> enabledServices = new HashSet<>();
+        final TextUtils.StringSplitter colonSplitter =
+                new TextUtils.SimpleStringSplitter(SERVICES_SEPARATOR);
+        colonSplitter.setString(enabledServicesSetting);
+
+        for (String componentNameString : colonSplitter) {
+            final ComponentName enabledService = ComponentName.unflattenFromString(
+                    componentNameString);
+            if (enabledService != null) {
+                enabledServices.add(enabledService);
+            }
+        }
+
+        return enabledServices;
+    }
+
+    /**
+     * Changes an accessibility component's state.
+     */
+    private void setAccessibilityServiceState(Context context, ComponentName componentName,
+            boolean enabled) {
+        setAccessibilityServiceState(context, componentName, enabled, UserHandle.myUserId());
+    }
+
+    /**
+     * Changes an accessibility component's state for {@param userId}.
+     */
+    private void setAccessibilityServiceState(Context context, ComponentName componentName,
+            boolean enabled, int userId) {
+        Set<ComponentName> enabledServices = getEnabledServicesFromSettings(
+                context, userId);
+
+        if (enabledServices.isEmpty()) {
+            enabledServices = new ArraySet<>(/* capacity= */ 1);
+        }
+
+        if (enabled) {
+            enabledServices.add(componentName);
+        } else {
+            enabledServices.remove(componentName);
+        }
+
+        final StringBuilder enabledServicesBuilder = new StringBuilder();
+        for (ComponentName enabledService : enabledServices) {
+            enabledServicesBuilder.append(enabledService.flattenToString());
+            enabledServicesBuilder.append(
+                    SERVICES_SEPARATOR);
+        }
+
+        final int enabledServicesBuilderLength = enabledServicesBuilder.length();
+        if (enabledServicesBuilderLength > 0) {
+            enabledServicesBuilder.deleteCharAt(enabledServicesBuilderLength - 1);
+        }
+
+        Settings.Secure.putStringForUser(context.getContentResolver(),
+                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+                enabledServicesBuilder.toString(), userId);
+    }
+
+    /**
+     * Opts out component name into colon-separated {@code shortcutType} key's string in Settings.
+     *
+     * @param context The current context.
+     * @param shortcutType The preferred shortcut type user selected.
+     * @param componentName The component name that need to be opted out from Settings.
+     */
+    private void optOutValueFromSettings(
+            Context context, int shortcutType, ComponentName componentName) {
+        final StringJoiner joiner = new StringJoiner(String.valueOf(SERVICES_SEPARATOR));
+        final String targetsKey = convertToKey(shortcutType);
+        final String targetsValue = Settings.Secure.getString(context.getContentResolver(),
+                targetsKey);
+
+        if (TextUtils.isEmpty(targetsValue)) {
+            return;
+        }
+
+        sStringColonSplitter.setString(targetsValue);
+        while (sStringColonSplitter.hasNext()) {
+            final String name = sStringColonSplitter.next();
+            if (TextUtils.isEmpty(name) || (componentName.flattenToString()).equals(name)) {
+                continue;
+            }
+            joiner.add(name);
+        }
+
+        Settings.Secure.putString(context.getContentResolver(), targetsKey, joiner.toString());
+    }
+
+    /**
+     * Returns if component name existed in Settings.
+     *
+     * @param context The current context.
+     * @param shortcutType The preferred shortcut type user selected.
+     * @param componentName The component name that need to be checked existed in Settings.
+     * @return {@code true} if componentName existed in Settings.
+     */
+    private boolean hasValueInSettings(Context context, @UserShortcutType int shortcutType,
+            @NonNull ComponentName componentName) {
+        final String targetKey = convertToKey(shortcutType);
+        final String targetString = Settings.Secure.getString(context.getContentResolver(),
+                targetKey);
+
+        if (TextUtils.isEmpty(targetString)) {
+            return false;
+        }
+
+        sStringColonSplitter.setString(targetString);
+        while (sStringColonSplitter.hasNext()) {
+            final String name = sStringColonSplitter.next();
+            if ((componentName.flattenToString()).equals(name)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Converts {@link UserShortcutType} to key in Settings.
+     *
+     * @param type The shortcut type.
+     * @return Mapping key in Settings.
+     */
+    private String convertToKey(@UserShortcutType int type) {
+        switch (type) {
+            case UserShortcutType.SOFTWARE:
+                return Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT;
+            case UserShortcutType.HARDWARE:
+                return Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
+            case UserShortcutType.TRIPLETAP:
+                return Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED;
+            default:
+                throw new IllegalArgumentException(
+                        "Unsupported user shortcut type: " + type);
+        }
+    }
+
+    private static @UserShortcutType int convertToUserType(@ShortcutType int type) {
+        switch (type) {
+            case ACCESSIBILITY_BUTTON:
+                return UserShortcutType.SOFTWARE;
+            case ACCESSIBILITY_SHORTCUT_KEY:
+                return UserShortcutType.HARDWARE;
+            default:
+                throw new IllegalArgumentException(
+                        "Unsupported shortcut type:" + type);
+        }
+    }
 }
diff --git a/core/java/com/android/internal/app/ISoundTriggerService.aidl b/core/java/com/android/internal/app/ISoundTriggerService.aidl
index d94294f..74bfb96 100644
--- a/core/java/com/android/internal/app/ISoundTriggerService.aidl
+++ b/core/java/com/android/internal/app/ISoundTriggerService.aidl
@@ -61,10 +61,6 @@
     int setParameter(in ParcelUuid soundModelId, in ModelParams modelParam,
         int value);
 
-    /**
-     * @throws UnsupportedOperationException if hal or model do not support this API.
-     * @throws IllegalArgumentException if invalid model handle or parameter is passed.
-     */
     int getParameter(in ParcelUuid soundModelId, in ModelParams modelParam);
 
     @nullable SoundTrigger.ModelParamRange queryParameter(in ParcelUuid soundModelId,
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index f462f5d..be2d1d6 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -26,6 +26,7 @@
 import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.app.IVoiceInteractionSessionListener;
 import android.hardware.soundtrigger.IRecognitionStatusCallback;
+import android.hardware.soundtrigger.ModelParams;
 import android.hardware.soundtrigger.SoundTrigger;
 import android.service.voice.IVoiceInteractionService;
 import android.service.voice.IVoiceInteractionSession;
@@ -91,6 +92,49 @@
      */
     int stopRecognition(in IVoiceInteractionService service, int keyphraseId,
             in IRecognitionStatusCallback callback);
+    /**
+     * Set a model specific ModelParams with the given value. This
+     * parameter will keep its value for the duration the model is loaded regardless of starting and
+     * stopping recognition. Once the model is unloaded, the value will be lost.
+     * queryParameter should be checked first before calling this method.
+     *
+     * @param service The current VoiceInteractionService.
+     * @param keyphraseId The unique identifier for the keyphrase.
+     * @param modelParam   ModelParams
+     * @param value        Value to set
+     * @return - {@link SoundTrigger#STATUS_OK} in case of success
+     *         - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached
+     *         - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter
+     *         - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or
+     *           if API is not supported by HAL
+     */
+    int setParameter(in IVoiceInteractionService service, int keyphraseId,
+            in ModelParams modelParam, int value);
+    /**
+     * Get a model specific ModelParams. This parameter will keep its value
+     * for the duration the model is loaded regardless of starting and stopping recognition.
+     * Once the model is unloaded, the value will be lost. If the value is not set, a default
+     * value is returned. See ModelParams for parameter default values.
+     * queryParameter should be checked first before calling this method.
+     *
+     * @param service The current VoiceInteractionService.
+     * @param keyphraseId The unique identifier for the keyphrase.
+     * @param modelParam   ModelParams
+     * @return value of parameter
+     */
+    int getParameter(in IVoiceInteractionService service, int keyphraseId,
+            in ModelParams modelParam);
+    /**
+     * Determine if parameter control is supported for the given model handle.
+     * This method should be checked prior to calling setParameter or getParameter.
+     *
+     * @param service The current VoiceInteractionService.
+     * @param keyphraseId The unique identifier for the keyphrase.
+     * @param modelParam ModelParams
+     * @return supported range of parameter, null if not supported
+     */
+    @nullable SoundTrigger.ModelParamRange queryParameter(in IVoiceInteractionService service,
+            int keyphraseId, in ModelParams modelParam);
 
     /**
      * @return the component name for the currently active voice interaction service
diff --git a/core/java/com/android/internal/util/Preconditions.java b/core/java/com/android/internal/util/Preconditions.java
index 5bc96d8..408a7a8 100644
--- a/core/java/com/android/internal/util/Preconditions.java
+++ b/core/java/com/android/internal/util/Preconditions.java
@@ -126,7 +126,9 @@
      * @param reference an object reference
      * @return the non-null reference that was validated
      * @throws NullPointerException if {@code reference} is null
+     * @deprecated - use {@link java.util.Objects.requireNonNull} instead.
      */
+    @Deprecated
     @UnsupportedAppUsage
     public static @NonNull <T> T checkNotNull(final T reference) {
         if (reference == null) {
@@ -144,7 +146,9 @@
      *     be converted to a string using {@link String#valueOf(Object)}
      * @return the non-null reference that was validated
      * @throws NullPointerException if {@code reference} is null
+     * @deprecated - use {@link java.util.Objects.requireNonNull} instead.
      */
+    @Deprecated
     @UnsupportedAppUsage
     public static @NonNull <T> T checkNotNull(final T reference, final Object errorMessage) {
         if (reference == null) {
@@ -154,26 +158,6 @@
     }
 
     /**
-     * Ensures that an object reference passed as a parameter to the calling
-     * method is not null.
-     *
-     * @param reference an object reference
-     * @param messageTemplate a printf-style message template to use if the check fails; will
-     *     be converted to a string using {@link String#format(String, Object...)}
-     * @param messageArgs arguments for {@code messageTemplate}
-     * @return the non-null reference that was validated
-     * @throws NullPointerException if {@code reference} is null
-     */
-    public static @NonNull <T> T checkNotNull(final T reference,
-            final String messageTemplate,
-            final Object... messageArgs) {
-        if (reference == null) {
-            throw new NullPointerException(String.format(messageTemplate, messageArgs));
-        }
-        return reference;
-    }
-
-    /**
      * Ensures the truth of an expression involving the state of the calling
      * instance, but not involving any parameters to the calling method.
      *
diff --git a/core/proto/android/service/graphicsstats.proto b/core/proto/android/service/graphicsstats.proto
index 557075c..2de5b7f 100644
--- a/core/proto/android/service/graphicsstats.proto
+++ b/core/proto/android/service/graphicsstats.proto
@@ -32,6 +32,10 @@
 }
 
 message GraphicsStatsProto {
+    enum PipelineType {
+        GL = 0;
+        VULKAN = 1;
+    }
     option (android.msg_privacy).dest = DEST_AUTOMATIC;
 
     // The package name of the app
@@ -54,6 +58,9 @@
 
     // The gpu frame time histogram for the package
     repeated GraphicsStatsHistogramBucketProto gpu_histogram = 7;
+
+    // HWUI renders pipeline type: GL or Vulkan
+    optional PipelineType pipeline = 8;
 }
 
 message GraphicsStatsJankSummaryProto {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index a808ed8..d887032 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -370,6 +370,7 @@
     <protected-broadcast android:name="android.net.wifi.STATE_CHANGE" />
     <protected-broadcast android:name="android.net.wifi.LINK_CONFIGURATION_CHANGED" />
     <protected-broadcast android:name="android.net.wifi.CONFIGURED_NETWORKS_CHANGE" />
+    <protected-broadcast android:name="android.net.wifi.action.NETWORK_SETTINGS_RESET" />
     <protected-broadcast android:name="android.net.wifi.action.PASSPOINT_DEAUTH_IMMINENT" />
     <protected-broadcast android:name="android.net.wifi.action.PASSPOINT_ICON" />
     <protected-broadcast android:name="android.net.wifi.action.PASSPOINT_OSU_PROVIDERS_LIST" />
@@ -933,11 +934,10 @@
 
     <!-- Allows an application a broad access to external storage in scoped storage.
          Intended to be used by few apps that need to manage files on behalf of the users.
-         <p>Protection level: signature|appop
-         <p>This protection level is temporary and will most likely be changed to |preinstalled -->
+         <p>Protection level: signature|appop|preinstalled -->
     <permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
         android:permissionGroup="android.permission-group.UNDEFINED"
-        android:protectionLevel="signature|appop" />
+        android:protectionLevel="signature|appop|preinstalled" />
 
     <!-- ====================================================================== -->
     <!-- Permissions for accessing the device location                          -->
@@ -1655,6 +1655,7 @@
     <!-- Allows holder to request bluetooth/wifi scan bypassing global "use location" setting and
          location permissions.
          <p>Not for use by third-party or privileged applications.
+         @SystemApi
          @hide
     -->
     <permission android:name="android.permission.RADIO_SCAN_WITHOUT_LOCATION"
diff --git a/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java b/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java
index 68b9b00..f4709ff 100644
--- a/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java
+++ b/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java
@@ -33,14 +33,15 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.support.test.uiautomator.UiDevice;
 import android.test.InstrumentationTestCase;
 import android.util.Log;
 
-import libcore.io.Streams;
-
 import com.google.mockwebserver.MockResponse;
 import com.google.mockwebserver.MockWebServer;
 
+import libcore.io.Streams;
+
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
 import java.io.File;
@@ -63,6 +64,7 @@
     private static final String TAG = "DownloadManagerBaseTest";
     protected DownloadManager mDownloadManager = null;
     private MockWebServer mServer = null;
+    private UiDevice mUiDevice = null;
     protected String mFileType = "text/plain";
     protected Context mContext = null;
     protected MultipleDownloadsCompletedReceiver mReceiver = null;
@@ -234,6 +236,7 @@
     @Override
     public void setUp() throws Exception {
         mContext = getInstrumentation().getContext();
+        mUiDevice = UiDevice.getInstance(getInstrumentation());
         mDownloadManager = (DownloadManager)mContext.getSystemService(Context.DOWNLOAD_SERVICE);
         mServer = new MockWebServer();
         mServer.play();
@@ -512,7 +515,7 @@
         Log.i(LOG_TAG, "Setting WiFi State to: " + enable);
         WifiManager manager = (WifiManager)mContext.getSystemService(Context.WIFI_SERVICE);
 
-        manager.setWifiEnabled(enable);
+        mUiDevice.executeShellCommand("svc wifi " + (enable ? "enable" : "disable"));
 
         String timeoutMessage = "Timed out waiting for Wifi to be "
             + (enable ? "enabled!" : "disabled!");
diff --git a/core/tests/coretests/src/android/net/NetworkKeyTest.java b/core/tests/coretests/src/android/net/NetworkKeyTest.java
index c6c0b46..b13bcd1 100644
--- a/core/tests/coretests/src/android/net/NetworkKeyTest.java
+++ b/core/tests/coretests/src/android/net/NetworkKeyTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import android.net.wifi.ScanResult;
@@ -107,7 +108,7 @@
 
     @Test
     public void createFromScanResult_nullSsid() {
-        ScanResult scanResult = new ScanResult();
+        ScanResult scanResult = mock(ScanResult.class);
         scanResult.BSSID = VALID_BSSID;
 
         assertNull(NetworkKey.createFromScanResult(scanResult));
@@ -115,7 +116,7 @@
 
     @Test
     public void createFromScanResult_emptySsid() {
-        ScanResult scanResult = new ScanResult();
+        ScanResult scanResult = mock(ScanResult.class);
         scanResult.SSID = "";
         scanResult.BSSID = VALID_BSSID;
 
@@ -124,7 +125,7 @@
 
     @Test
     public void createFromScanResult_noneSsid() {
-        ScanResult scanResult = new ScanResult();
+        ScanResult scanResult = mock(ScanResult.class);
         scanResult.SSID = WifiManager.UNKNOWN_SSID;
         scanResult.BSSID = VALID_BSSID;
 
@@ -133,7 +134,7 @@
 
     @Test
     public void createFromScanResult_nullBssid() {
-        ScanResult scanResult = new ScanResult();
+        ScanResult scanResult = mock(ScanResult.class);
         scanResult.SSID = VALID_UNQUOTED_SSID;
 
         assertNull(NetworkKey.createFromScanResult(scanResult));
@@ -141,7 +142,7 @@
 
     @Test
     public void createFromScanResult_emptyBssid() {
-        ScanResult scanResult = new ScanResult();
+        ScanResult scanResult = mock(ScanResult.class);
         scanResult.SSID = VALID_UNQUOTED_SSID;
         scanResult.BSSID = "";
 
@@ -150,7 +151,7 @@
 
     @Test
     public void createFromScanResult_invalidBssid() {
-        ScanResult scanResult = new ScanResult();
+        ScanResult scanResult = mock(ScanResult.class);
         scanResult.SSID = VALID_UNQUOTED_SSID;
         scanResult.BSSID = INVALID_BSSID;
 
@@ -159,7 +160,7 @@
 
     @Test
     public void createFromScanResult_validSsid() {
-        ScanResult scanResult = new ScanResult();
+        ScanResult scanResult = mock(ScanResult.class);
         scanResult.SSID = VALID_UNQUOTED_SSID;
         scanResult.BSSID = VALID_BSSID;
 
diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
index 0e19ca8..d0fd92a 100644
--- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
@@ -90,12 +90,12 @@
             mImeConsumer.onWindowFocusGained();
             mImeConsumer.applyImeVisibility(true);
             mController.cancelExistingAnimation();
-            assertTrue(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
 
             // test if setVisibility can hide IME
             mImeConsumer.applyImeVisibility(false);
             mController.cancelExistingAnimation();
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
         });
     }
 
diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index 179929f..fa61a0a 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -16,11 +16,11 @@
 
 package android.view;
 
+import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
 import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
 import static android.view.WindowInsets.Type.systemBars;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -39,8 +39,6 @@
 import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
 import android.view.test.InsetsModeSession;
 
-import androidx.test.runner.AndroidJUnit4;
-
 import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.BeforeClass;
@@ -52,6 +50,8 @@
 
 import java.util.List;
 
+import androidx.test.runner.AndroidJUnit4;
+
 /**
  * Tests for {@link InsetsAnimationControlImpl}.
  *
@@ -116,7 +116,7 @@
         mController = new InsetsAnimationControlImpl(controls,
                 new Rect(0, 0, 500, 500), mInsetsState, mMockListener, systemBars(),
                 mMockController, 10 /* durationMs */,
-                false /* fade */);
+                false /* fade */, LAYOUT_INSETS_DURING_ANIMATION_SHOWN);
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index a89fc1e..1db96b1 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -68,6 +68,7 @@
     private InsetsController mController;
     private SurfaceSession mSession = new SurfaceSession();
     private SurfaceControl mLeash;
+    private ViewRootImpl mViewRoot;
 
     @Before
     public void setup() {
@@ -77,13 +78,13 @@
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             Context context = InstrumentationRegistry.getTargetContext();
             // cannot mock ViewRootImpl since it's final.
-            ViewRootImpl viewRootImpl = new ViewRootImpl(context, context.getDisplay());
+            mViewRoot = new ViewRootImpl(context, context.getDisplay());
             try {
-                viewRootImpl.setView(new TextView(context), new LayoutParams(), null);
+                mViewRoot.setView(new TextView(context), new LayoutParams(), null);
             } catch (BadTokenException e) {
                 // activity isn't running, we will ignore BadTokenException.
             }
-            mController = new InsetsController(viewRootImpl);
+            mController = new InsetsController(mViewRoot);
             final Rect rect = new Rect(5, 5, 5, 5);
             mController.calculateInsets(
                     false,
@@ -117,16 +118,22 @@
 
     @Test
     public void testControlsRevoked_duringAnim() {
-        InsetsSourceControl control =
-                new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, new Point());
-        mController.onControlsChanged(new InsetsSourceControl[] { control });
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            InsetsSourceControl control =
+                    new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, new Point());
+            mController.onControlsChanged(new InsetsSourceControl[] { control });
 
-        WindowInsetsAnimationControlListener mockListener =
-                mock(WindowInsetsAnimationControlListener.class);
-        mController.controlWindowInsetsAnimation(statusBars(), 10 /* durationMs */, mockListener);
-        verify(mockListener).onReady(any(), anyInt());
-        mController.onControlsChanged(new InsetsSourceControl[0]);
-        verify(mockListener).onCancelled();
+            WindowInsetsAnimationControlListener mockListener =
+                    mock(WindowInsetsAnimationControlListener.class);
+            mController.controlWindowInsetsAnimation(statusBars(), 10 /* durationMs */,
+                    mockListener);
+
+            // Ready gets deferred until next predraw
+            mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
+            verify(mockListener).onReady(any(), anyInt());
+            mController.onControlsChanged(new InsetsSourceControl[0]);
+            verify(mockListener).onCancelled();
+        });
     }
 
     @Test
@@ -154,16 +161,16 @@
             mController.show(Type.all());
             // quickly jump to final state by cancelling it.
             mController.cancelExistingAnimation();
-            assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertTrue(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
 
             mController.applyImeVisibility(false /* setVisible */);
             mController.hide(Type.all());
             mController.cancelExistingAnimation();
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
             mController.getSourceConsumer(ITYPE_IME).onWindowFocusLost();
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -180,10 +187,10 @@
             mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained();
             mController.applyImeVisibility(true);
             mController.cancelExistingAnimation();
-            assertTrue(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
             mController.applyImeVisibility(false);
             mController.cancelExistingAnimation();
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
             mController.getSourceConsumer(ITYPE_IME).onWindowFocusLost();
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -201,16 +208,16 @@
             // test show select types.
             mController.show(types);
             mController.cancelExistingAnimation();
-            assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
 
             // test hide all
             mController.hide(types);
             mController.cancelExistingAnimation();
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
@@ -227,29 +234,29 @@
             // test show select types.
             mController.show(types);
             mController.cancelExistingAnimation();
-            assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
 
             // test hide all
             mController.hide(Type.all());
             mController.cancelExistingAnimation();
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
 
             // test single show
             mController.show(Type.navigationBars());
             mController.cancelExistingAnimation();
-            assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
 
             // test single hide
             mController.hide(Type.navigationBars());
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
 
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -267,31 +274,31 @@
             mController.show(Type.navigationBars());
             mController.show(Type.systemBars());
             mController.cancelExistingAnimation();
-            assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
 
             mController.hide(Type.navigationBars());
             mController.hide(Type.systemBars());
             mController.cancelExistingAnimation();
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
 
             int types = Type.navigationBars() | Type.systemBars();
             // show two at a time and hide one by one.
             mController.show(types);
             mController.hide(Type.navigationBars());
             mController.cancelExistingAnimation();
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
 
             mController.hide(Type.systemBars());
             mController.cancelExistingAnimation();
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
@@ -309,15 +316,15 @@
             mController.show(types);
             mController.hide(Type.navigationBars());
             mController.cancelExistingAnimation();
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
 
             mController.hide(Type.systemBars());
             mController.cancelExistingAnimation();
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
@@ -336,12 +343,16 @@
 
             ArgumentCaptor<WindowInsetsAnimationController> controllerCaptor =
                     ArgumentCaptor.forClass(WindowInsetsAnimationController.class);
+
+            // Ready gets deferred until next predraw
+            mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
+
             verify(mockListener).onReady(controllerCaptor.capture(), anyInt());
             controllerCaptor.getValue().finish(false /* shown */);
         });
         waitUntilNextFrame();
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-            assertFalse(mController.getSourceConsumer(ITYPE_STATUS_BAR).isVisible());
+            assertFalse(mController.getSourceConsumer(ITYPE_STATUS_BAR).isRequestedVisible());
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
index 7af833b..492c036 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
@@ -96,7 +96,7 @@
     @Test
     public void testHide() {
         mConsumer.hide();
-        assertFalse("Consumer should not be visible", mConsumer.isVisible());
+        assertFalse("Consumer should not be visible", mConsumer.isRequestedVisible());
         verify(mSpyInsetsSource).setVisible(eq(false));
     }
 
@@ -106,7 +106,7 @@
         // Insets source starts out visible
         mConsumer.hide();
         mConsumer.show();
-        assertTrue("Consumer should be visible", mConsumer.isVisible());
+        assertTrue("Consumer should be visible", mConsumer.isRequestedVisible());
         verify(mSpyInsetsSource).setVisible(eq(false));
         verify(mSpyInsetsSource).setVisible(eq(true));
     }
diff --git a/core/tests/coretests/src/android/view/InsetsSourceTest.java b/core/tests/coretests/src/android/view/InsetsSourceTest.java
index d7f50ba..e3b08bb 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceTest.java
@@ -108,5 +108,30 @@
         assertEquals(Insets.of(0, 100, 0, 0), insets);
     }
 
+    @Test
+    public void testCalculateVisibleInsets_default() {
+        mSource.setFrame(new Rect(0, 0, 500, 100));
+        Insets insets = mSource.calculateVisibleInsets(new Rect(100, 0, 500, 500));
+        assertEquals(Insets.of(0, 100, 0, 0), insets);
+    }
+
+    @Test
+    public void testCalculateVisibleInsets_override() {
+        mSource.setFrame(new Rect(0, 0, 500, 100));
+        mSource.setVisibleFrame(new Rect(0, 0, 500, 200));
+        Insets insets = mSource.calculateVisibleInsets(new Rect(100, 0, 500, 500));
+        assertEquals(Insets.of(0, 200, 0, 0), insets);
+    }
+
+    @Test
+    public void testCalculateVisibleInsets_invisible() {
+        mSource.setFrame(new Rect(0, 0, 500, 100));
+        mSource.setVisibleFrame(new Rect(0, 0, 500, 200));
+        mSource.setVisible(false);
+        Insets insets = mSource.calculateVisibleInsets(new Rect(100, 0, 500, 500));
+        assertEquals(Insets.of(0, 0, 0, 0), insets);
+    }
+
+
     // Parcel and equals already tested via InsetsStateTest
 }
diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java
index fa2ffcca..4b76fee 100644
--- a/core/tests/coretests/src/android/view/InsetsStateTest.java
+++ b/core/tests/coretests/src/android/view/InsetsStateTest.java
@@ -18,12 +18,14 @@
 
 import static android.view.InsetsState.ISIDE_BOTTOM;
 import static android.view.InsetsState.ISIDE_TOP;
+import static android.view.InsetsState.ITYPE_BOTTOM_GESTURES;
 import static android.view.InsetsState.ITYPE_CAPTION_BAR;
 import static android.view.InsetsState.ITYPE_IME;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 
 import static org.junit.Assert.assertEquals;
@@ -209,6 +211,42 @@
         assertFalse(InsetsState.getDefaultVisibility(ITYPE_IME));
     }
 
+    @Test
+    public void testCalculateVisibleInsets() throws Exception {
+        try (final InsetsModeSession session =
+                     new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) {
+            mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
+            mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
+            mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300));
+            mState.getSource(ITYPE_IME).setVisible(true);
+
+            // Make sure bottom gestures are ignored
+            mState.getSource(ITYPE_BOTTOM_GESTURES).setFrame(new Rect(0, 100, 100, 300));
+            mState.getSource(ITYPE_BOTTOM_GESTURES).setVisible(true);
+            Rect visibleInsets = mState.calculateVisibleInsets(
+                    new Rect(0, 0, 100, 300), new Rect(), SOFT_INPUT_ADJUST_PAN);
+            assertEquals(new Rect(0, 100, 0, 100), visibleInsets);
+        }
+    }
+
+    @Test
+    public void testCalculateVisibleInsets_adjustNothing() throws Exception {
+        try (final InsetsModeSession session =
+                     new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) {
+            mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
+            mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
+            mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300));
+            mState.getSource(ITYPE_IME).setVisible(true);
+
+            // Make sure bottom gestures are ignored
+            mState.getSource(ITYPE_BOTTOM_GESTURES).setFrame(new Rect(0, 100, 100, 300));
+            mState.getSource(ITYPE_BOTTOM_GESTURES).setVisible(true);
+            Rect visibleInsets = mState.calculateVisibleInsets(
+                    new Rect(0, 0, 100, 300), new Rect(), SOFT_INPUT_ADJUST_NOTHING);
+            assertEquals(new Rect(0, 100, 0, 0), visibleInsets);
+        }
+    }
+
     private void assertEqualsAndHashCode() {
         assertEquals(mState, mState2);
         assertEquals(mState.hashCode(), mState2.hashCode());
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
similarity index 78%
rename from services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerTest.java
rename to core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
index b9b6d55..e23c51e 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.accessibility;
+package android.view.accessibility;
 
 import static junit.framework.TestCase.assertFalse;
 import static junit.framework.TestCase.assertSame;
@@ -29,16 +29,17 @@
 
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.app.Instrumentation;
+import android.app.PendingIntent;
+import android.app.RemoteAction;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
 import android.os.UserHandle;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.IAccessibilityManager;
-import android.view.accessibility.IAccessibilityManagerClient;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.util.IntPair;
+import com.android.server.accessibility.test.MessageCapturingHandler;
 
 import org.junit.After;
 import org.junit.Before;
@@ -57,6 +58,16 @@
 public class AccessibilityManagerTest {
     private static final boolean WITH_A11Y_ENABLED = true;
     private static final boolean WITH_A11Y_DISABLED = false;
+    private static final String LABEL = "label";
+    private static final String INTENT_ACTION = "TESTACTION";
+    private static final String DESCRIPTION = "description";
+    private static final PendingIntent TEST_PENDING_INTENT = PendingIntent.getBroadcast(
+            InstrumentationRegistry.getTargetContext(), 0, new Intent(INTENT_ACTION), 0);
+    private static final RemoteAction TEST_ACTION = new RemoteAction(
+            Icon.createWithContentUri("content://test"),
+            LABEL,
+            DESCRIPTION,
+            TEST_PENDING_INTENT);
 
     @Mock private IAccessibilityManager mMockService;
     private MessageCapturingHandler mHandler;
@@ -122,6 +133,29 @@
     }
 
     @Test
+    public void testRegisterSystemAction() throws Exception {
+        AccessibilityManager manager = createManager(WITH_A11Y_ENABLED);
+        RemoteAction action = new RemoteAction(
+                Icon.createWithContentUri("content://test"),
+                LABEL,
+                DESCRIPTION,
+                TEST_PENDING_INTENT);
+        final int actionId = 0;
+        manager.registerSystemAction(TEST_ACTION, actionId);
+
+        verify(mMockService).registerSystemAction(TEST_ACTION, actionId);
+    }
+
+    @Test
+    public void testUnregisterSystemAction() throws Exception {
+        AccessibilityManager manager = createManager(WITH_A11Y_ENABLED);
+        final int actionId = 0;
+        manager.unregisterSystemAction(actionId);
+
+        verify(mMockService).unregisterSystemAction(actionId);
+    }
+
+    @Test
     public void testIsEnabled() throws Exception {
         // Create manager with a11y enabled
         AccessibilityManager manager = createManager(WITH_A11Y_ENABLED);
diff --git a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
index f497db2..89c2374 100644
--- a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
+++ b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
@@ -226,6 +226,61 @@
     }
 
     @Test
+    public void testEditor_onTouchEvent_quickTapAfterDrag() throws Throwable {
+        String text = "Hi world!";
+        onView(withId(R.id.textview)).perform(replaceText(text));
+        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
+
+        TextView tv = mActivity.findViewById(R.id.textview);
+        Editor editor = tv.getEditorForTesting();
+
+        // Simulate a tap-and-drag gesture.
+        long event1Time = 1001;
+        MotionEvent event1 = downEvent(event1Time, event1Time, 5f, 10f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event1));
+        assertFalse(editor.getInsertionController().isCursorBeingModified());
+        assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+        long event2Time = 1002;
+        MotionEvent event2 = moveEvent(event1Time, event2Time, 50f, 10f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event2));
+        assertTrue(editor.getInsertionController().isCursorBeingModified());
+        assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+        long event3Time = 1003;
+        MotionEvent event3 = moveEvent(event1Time, event3Time, 100f, 10f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event3));
+        assertTrue(editor.getInsertionController().isCursorBeingModified());
+        assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+        long event4Time = 2004;
+        MotionEvent event4 = upEvent(event1Time, event4Time, 100f, 10f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event4));
+        assertFalse(editor.getInsertionController().isCursorBeingModified());
+        assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+        // Simulate a quick tap after the drag, near the location where the drag ended.
+        long event5Time = 2005;
+        MotionEvent event5 = downEvent(event5Time, event5Time, 90f, 10f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event5));
+        assertFalse(editor.getInsertionController().isCursorBeingModified());
+        assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+        long event6Time = 2006;
+        MotionEvent event6 = upEvent(event5Time, event6Time, 90f, 10f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event6));
+        assertFalse(editor.getInsertionController().isCursorBeingModified());
+        assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+        // Simulate another quick tap in the same location; now selection should be triggered.
+        long event7Time = 2007;
+        MotionEvent event7 = downEvent(event7Time, event7Time, 90f, 10f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event7));
+        assertFalse(editor.getInsertionController().isCursorBeingModified());
+        assertTrue(editor.getSelectionController().isCursorBeingModified());
+    }
+
+    @Test
     public void testEditor_onTouchEvent_cursorDrag() throws Throwable {
         String text = "testEditor_onTouchEvent_cursorDrag";
         onView(withId(R.id.textview)).perform(replaceText(text));
@@ -237,29 +292,25 @@
         // Simulate a tap-and-drag gesture. This should trigger a cursor drag.
         long event1Time = 1001;
         MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
-        mActivity.runOnUiThread(() -> editor.onTouchEvent(event1));
-        mInstrumentation.waitForIdleSync();
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event1));
         assertFalse(editor.getInsertionController().isCursorBeingModified());
         assertFalse(editor.getSelectionController().isCursorBeingModified());
 
         long event2Time = 1002;
         MotionEvent event2 = moveEvent(event1Time, event2Time, 21f, 30f);
-        mActivity.runOnUiThread(() -> editor.onTouchEvent(event2));
-        mInstrumentation.waitForIdleSync();
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event2));
         assertFalse(editor.getInsertionController().isCursorBeingModified());
         assertFalse(editor.getSelectionController().isCursorBeingModified());
 
         long event3Time = 1003;
-        MotionEvent event3 = moveEvent(event3Time, event3Time, 120f, 30f);
-        mActivity.runOnUiThread(() -> editor.onTouchEvent(event3));
-        mInstrumentation.waitForIdleSync();
+        MotionEvent event3 = moveEvent(event1Time, event3Time, 120f, 30f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event3));
         assertTrue(editor.getInsertionController().isCursorBeingModified());
         assertFalse(editor.getSelectionController().isCursorBeingModified());
 
         long event4Time = 1004;
-        MotionEvent event4 = upEvent(event3Time, event4Time, 120f, 30f);
-        mActivity.runOnUiThread(() -> editor.onTouchEvent(event4));
-        mInstrumentation.waitForIdleSync();
+        MotionEvent event4 = upEvent(event1Time, event4Time, 120f, 30f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event4));
         assertFalse(editor.getInsertionController().isCursorBeingModified());
         assertFalse(editor.getSelectionController().isCursorBeingModified());
     }
@@ -276,36 +327,31 @@
         // Simulate a double-tap followed by a drag. This should trigger a selection drag.
         long event1Time = 1001;
         MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
-        mActivity.runOnUiThread(() -> editor.onTouchEvent(event1));
-        mInstrumentation.waitForIdleSync();
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event1));
         assertFalse(editor.getInsertionController().isCursorBeingModified());
         assertFalse(editor.getSelectionController().isCursorBeingModified());
 
         long event2Time = 1002;
         MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
-        mActivity.runOnUiThread(() -> editor.onTouchEvent(event2));
-        mInstrumentation.waitForIdleSync();
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event2));
         assertFalse(editor.getInsertionController().isCursorBeingModified());
         assertFalse(editor.getSelectionController().isCursorBeingModified());
 
         long event3Time = 1003;
         MotionEvent event3 = downEvent(event3Time, event3Time, 20f, 30f);
-        mActivity.runOnUiThread(() -> editor.onTouchEvent(event3));
-        mInstrumentation.waitForIdleSync();
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event3));
         assertFalse(editor.getInsertionController().isCursorBeingModified());
         assertTrue(editor.getSelectionController().isCursorBeingModified());
 
         long event4Time = 1004;
         MotionEvent event4 = moveEvent(event3Time, event4Time, 120f, 30f);
-        mActivity.runOnUiThread(() -> editor.onTouchEvent(event4));
-        mInstrumentation.waitForIdleSync();
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event4));
         assertFalse(editor.getInsertionController().isCursorBeingModified());
         assertTrue(editor.getSelectionController().isCursorBeingModified());
 
         long event5Time = 1005;
         MotionEvent event5 = upEvent(event3Time, event5Time, 120f, 30f);
-        mActivity.runOnUiThread(() -> editor.onTouchEvent(event5));
-        mInstrumentation.waitForIdleSync();
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event5));
         assertFalse(editor.getInsertionController().isCursorBeingModified());
         assertFalse(editor.getSelectionController().isCursorBeingModified());
     }
diff --git a/core/tests/coretests/src/android/widget/EditorTouchStateTest.java b/core/tests/coretests/src/android/widget/EditorTouchStateTest.java
index 6adb1b8..215d0b8 100644
--- a/core/tests/coretests/src/android/widget/EditorTouchStateTest.java
+++ b/core/tests/coretests/src/android/widget/EditorTouchStateTest.java
@@ -120,6 +120,60 @@
     }
 
     @Test
+    public void testUpdate_doubleTap_delayAfterFirstDownEvent() throws Exception {
+        // Simulate an ACTION_DOWN event.
+        long event1Time = 1000;
+        MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
+        mTouchState.update(event1, mConfig);
+        assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+
+        // Simulate an ACTION_UP event with a delay that's longer than the double-tap timeout.
+        long event2Time = 1000 + ViewConfiguration.getDoubleTapTimeout() + 1;
+        MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
+        mTouchState.update(event2, mConfig);
+        assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
+
+        // Generate an ACTION_DOWN event whose time is within the double-tap timeout when
+        // calculated from the last ACTION_UP event time. Even though the time between the last up
+        // and this down event is within the double-tap timeout, this should not be considered a
+        // double-tap (since the first down event had a longer delay).
+        long event3Time = event2Time + 1;
+        MotionEvent event3 = downEvent(event3Time, event3Time, 22f, 33f);
+        mTouchState.update(event3, mConfig);
+        assertSingleTap(mTouchState, 22f, 33f, 20f, 30f, false);
+    }
+
+    @Test
+    public void testUpdate_quickTapAfterDrag() throws Exception {
+        // Simulate an ACTION_DOWN event.
+        long event1Time = 1000;
+        MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
+        mTouchState.update(event1, mConfig);
+        assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+
+        // Simulate an ACTION_MOVE event.
+        long event2Time = 1001;
+        MotionEvent event2 = moveEvent(event1Time, event2Time, 200f, 31f);
+        mTouchState.update(event2, mConfig);
+        assertSingleTap(mTouchState, 20f, 30f, 0, 0, true);
+
+        // Simulate an ACTION_UP event with a delay that's longer than the double-tap timeout.
+        long event3Time = 5000;
+        MotionEvent event3 = upEvent(event1Time, event3Time, 200f, 31f);
+        mTouchState.update(event3, mConfig);
+        assertSingleTap(mTouchState, 20f, 30f, 200f, 31f, false);
+
+        // Generate an ACTION_DOWN event whose time is within the double-tap timeout when
+        // calculated from the last ACTION_UP event time. Even though the time between the last up
+        // and this down event is within the double-tap timeout, this should not be considered a
+        // double-tap (since the first down event had a longer delay).
+        long event4Time = event3Time + 1;
+        MotionEvent event4 = downEvent(event4Time, event4Time, 200f, 31f);
+        mTouchState.update(event4, mConfig);
+        assertSingleTap(mTouchState, 200f, 31f, 200f, 31f, false);
+    }
+
+    @Test
     public void testUpdate_tripleClick_mouse() throws Exception {
         // Simulate an ACTION_DOWN event.
         long event1Time = 1000;
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index eb1d1ab..9930ea2 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -243,6 +243,7 @@
         <permission name="android.permission.MANAGE_USB"/>
         <permission name="android.permission.MODIFY_PHONE_STATE"/>
         <permission name="android.permission.READ_NETWORK_USAGE_HISTORY"/>
+        <permission name="android.permission.TETHER_PRIVILEGED"/>
         <permission name="android.permission.UPDATE_APP_OPS_STATS"/>
     </privapp-permissions>
 
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index d945fc4..a7f8cc4 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -215,6 +215,7 @@
         android: {
 
             srcs: [
+                "pipeline/skia/ATraceMemoryDump.cpp",
                 "pipeline/skia/GLFunctorDrawable.cpp",
                 "pipeline/skia/LayerDrawable.cpp",
                 "pipeline/skia/ShaderCache.cpp",
@@ -244,7 +245,6 @@
                 "DeviceInfo.cpp",
                 "FrameInfo.cpp",
                 "FrameInfoVisualizer.cpp",
-                "GpuMemoryTracker.cpp",
                 "HardwareBitmapUploader.cpp",
                 "HWUIProperties.sysprop",
                 "JankTracker.cpp",
@@ -325,7 +325,6 @@
         "tests/unit/DamageAccumulatorTests.cpp",
         "tests/unit/DeferredLayerUpdaterTests.cpp",
         "tests/unit/FatVectorTests.cpp",
-        "tests/unit/GpuMemoryTrackerTests.cpp",
         "tests/unit/GraphicsStatsServiceTests.cpp",
         "tests/unit/LayerUpdateQueueTests.cpp",
         "tests/unit/LinearAllocatorTests.cpp",
diff --git a/libs/hwui/GpuMemoryTracker.cpp b/libs/hwui/GpuMemoryTracker.cpp
deleted file mode 100644
index a9a7af8..0000000
--- a/libs/hwui/GpuMemoryTracker.cpp
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2016 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.
- */
-
-#include "utils/StringUtils.h"
-
-#include <GpuMemoryTracker.h>
-#include <cutils/compiler.h>
-#include <utils/Trace.h>
-#include <array>
-#include <sstream>
-#include <unordered_set>
-#include <vector>
-
-namespace android {
-namespace uirenderer {
-
-pthread_t gGpuThread = 0;
-
-#define NUM_TYPES static_cast<int>(GpuObjectType::TypeCount)
-
-const char* TYPE_NAMES[] = {
-        "Texture", "OffscreenBuffer", "Layer",
-};
-
-struct TypeStats {
-    int totalSize = 0;
-    int count = 0;
-};
-
-static std::array<TypeStats, NUM_TYPES> gObjectStats;
-static std::unordered_set<GpuMemoryTracker*> gObjectSet;
-
-void GpuMemoryTracker::notifySizeChanged(int newSize) {
-    int delta = newSize - mSize;
-    mSize = newSize;
-    gObjectStats[static_cast<int>(mType)].totalSize += delta;
-}
-
-void GpuMemoryTracker::startTrackingObject() {
-    auto result = gObjectSet.insert(this);
-    LOG_ALWAYS_FATAL_IF(!result.second,
-                        "startTrackingObject() on %p failed, already being tracked!", this);
-    gObjectStats[static_cast<int>(mType)].count++;
-}
-
-void GpuMemoryTracker::stopTrackingObject() {
-    size_t removed = gObjectSet.erase(this);
-    LOG_ALWAYS_FATAL_IF(removed != 1, "stopTrackingObject removed %zd, is %p not being tracked?",
-                        removed, this);
-    gObjectStats[static_cast<int>(mType)].count--;
-}
-
-void GpuMemoryTracker::onGpuContextCreated() {
-    LOG_ALWAYS_FATAL_IF(gGpuThread != 0,
-                        "We already have a gpu thread? "
-                        "current = %lu, gpu thread = %lu",
-                        pthread_self(), gGpuThread);
-    gGpuThread = pthread_self();
-}
-
-void GpuMemoryTracker::onGpuContextDestroyed() {
-    gGpuThread = 0;
-    if (CC_UNLIKELY(gObjectSet.size() > 0)) {
-        std::stringstream os;
-        dump(os);
-        ALOGE("%s", os.str().c_str());
-        LOG_ALWAYS_FATAL("Leaked %zd GPU objects!", gObjectSet.size());
-    }
-}
-
-void GpuMemoryTracker::dump() {
-    std::stringstream strout;
-    dump(strout);
-    ALOGD("%s", strout.str().c_str());
-}
-
-void GpuMemoryTracker::dump(std::ostream& stream) {
-    for (int type = 0; type < NUM_TYPES; type++) {
-        const TypeStats& stats = gObjectStats[type];
-        stream << TYPE_NAMES[type];
-        stream << " is using " << SizePrinter{stats.totalSize};
-        stream << ", count = " << stats.count;
-        stream << std::endl;
-    }
-}
-
-int GpuMemoryTracker::getInstanceCount(GpuObjectType type) {
-    return gObjectStats[static_cast<int>(type)].count;
-}
-
-int GpuMemoryTracker::getTotalSize(GpuObjectType type) {
-    return gObjectStats[static_cast<int>(type)].totalSize;
-}
-
-void GpuMemoryTracker::onFrameCompleted() {
-    if (ATRACE_ENABLED()) {
-        char buf[128];
-        for (int type = 0; type < NUM_TYPES; type++) {
-            snprintf(buf, 128, "hwui_%s", TYPE_NAMES[type]);
-            const TypeStats& stats = gObjectStats[type];
-            ATRACE_INT(buf, stats.totalSize);
-            snprintf(buf, 128, "hwui_%s_count", TYPE_NAMES[type]);
-            ATRACE_INT(buf, stats.count);
-        }
-    }
-}
-
-}  // namespace uirenderer
-}  // namespace android;
diff --git a/libs/hwui/GpuMemoryTracker.h b/libs/hwui/GpuMemoryTracker.h
deleted file mode 100644
index de3ca99..0000000
--- a/libs/hwui/GpuMemoryTracker.h
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2016 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.
- */
-#pragma once
-
-#include <pthread.h>
-#include <ostream>
-
-#include <log/log.h>
-
-namespace android {
-namespace uirenderer {
-
-extern pthread_t gGpuThread;
-
-#define ASSERT_GPU_THREAD()                                                                    \
-    LOG_ALWAYS_FATAL_IF(!pthread_equal(gGpuThread, pthread_self()),                            \
-                        "Error, %p of type %d (size=%d) used on wrong thread! cur thread %lu " \
-                        "!= gpu thread %lu",                                                   \
-                        this, static_cast<int>(mType), mSize, pthread_self(), gGpuThread)
-
-enum class GpuObjectType {
-    Texture = 0,
-    OffscreenBuffer,
-    Layer,
-
-    TypeCount,
-};
-
-class GpuMemoryTracker {
-public:
-    GpuObjectType objectType() { return mType; }
-    int objectSize() { return mSize; }
-
-    static void onGpuContextCreated();
-    static void onGpuContextDestroyed();
-    static void dump();
-    static void dump(std::ostream& stream);
-    static int getInstanceCount(GpuObjectType type);
-    static int getTotalSize(GpuObjectType type);
-    static void onFrameCompleted();
-
-protected:
-    explicit GpuMemoryTracker(GpuObjectType type) : mType(type) {
-        ASSERT_GPU_THREAD();
-        startTrackingObject();
-    }
-
-    ~GpuMemoryTracker() {
-        notifySizeChanged(0);
-        stopTrackingObject();
-    }
-
-    void notifySizeChanged(int newSize);
-
-private:
-    void startTrackingObject();
-    void stopTrackingObject();
-
-    int mSize = 0;
-    GpuObjectType mType;
-};
-
-}  // namespace uirenderer
-}  // namespace android;
diff --git a/libs/hwui/ProfileData.cpp b/libs/hwui/ProfileData.cpp
index 7921662..a8e36e3 100644
--- a/libs/hwui/ProfileData.cpp
+++ b/libs/hwui/ProfileData.cpp
@@ -15,6 +15,7 @@
  */
 
 #include "ProfileData.h"
+#include "Properties.h"
 
 #include <cinttypes>
 
@@ -102,6 +103,7 @@
         mGPUFrameCounts[i] >>= divider;
         mGPUFrameCounts[i] += other.mGPUFrameCounts[i];
     }
+    mPipelineType = other.mPipelineType;
 }
 
 void ProfileData::dump(int fd) const {
@@ -157,6 +159,7 @@
     mTotalFrameCount = 0;
     mJankFrameCount = 0;
     mStatStartTime = systemTime(SYSTEM_TIME_MONOTONIC);
+    mPipelineType = Properties::getRenderPipelineType();
 }
 
 void ProfileData::reportFrame(int64_t duration) {
diff --git a/libs/hwui/ProfileData.h b/libs/hwui/ProfileData.h
index ccbffc6..dd3ba66 100644
--- a/libs/hwui/ProfileData.h
+++ b/libs/hwui/ProfileData.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include "Properties.h"
 #include "utils/Macros.h"
 
 #include <utils/Timers.h>
@@ -65,6 +66,7 @@
     uint32_t jankFrameCount() const { return mJankFrameCount; }
     nsecs_t statsStartTime() const { return mStatStartTime; }
     uint32_t jankTypeCount(JankType type) const { return mJankTypeCounts[static_cast<int>(type)]; }
+    RenderPipelineType pipelineType() const { return mPipelineType; }
 
     struct HistogramEntry {
         uint32_t renderTimeMs;
@@ -103,6 +105,9 @@
     uint32_t mTotalFrameCount;
     uint32_t mJankFrameCount;
     nsecs_t mStatStartTime;
+
+    // true if HWUI renders with Vulkan pipeline
+    RenderPipelineType mPipelineType;
 };
 
 // For testing
diff --git a/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp b/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp
new file mode 100644
index 0000000..2c78b02
--- /dev/null
+++ b/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#include "ATraceMemoryDump.h"
+
+#include <utils/Trace.h>
+
+#include <cstring>
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+// When purgeable is INVALID_TIME it won't be logged at all.
+#define INVALID_TIME -1
+
+/**
+ * Skia invokes the following SkTraceMemoryDump functions:
+ * 1. dumpNumericValue (dumpName, units="bytes", valueName="size")
+ * 2. dumpStringValue (dumpName, valueName="type") [optional -> for example CPU memory does not
+ * invoke dumpStringValue]
+ * 3. dumpNumericValue (dumpName, units="bytes", valueName="purgeable_size") [optional]
+ * 4. setMemoryBacking(dumpName, backingType) [optional -> for example Vulkan GPU resources do not
+ * invoke setMemoryBacking]
+ *
+ * ATraceMemoryDump calculates memory category first by looking at the "type" string passed to
+ * dumpStringValue and then by looking at "backingType" passed to setMemoryBacking.
+ * Only GPU Texture memory is tracked separately and everything else is grouped as one
+ * "GPU Memory" category.
+ */
+static std::unordered_map<const char*, const char*> sResourceMap = {
+        {"malloc", "Graphics CPU Memory"},          // taken from setMemoryBacking(backingType)
+        {"gl_texture", "Graphics Texture Memory"},  // taken from setMemoryBacking(backingType)
+        {"Texture",
+         "Graphics Texture Memory"},  // taken from dumpStringValue(value, valueName="type")
+        // Uncomment categories below to split "GPU Memory" into more brackets for debugging.
+        /*{"vk_buffer", "vk_buffer"},
+        {"gl_renderbuffer", "gl_renderbuffer"},
+        {"gl_buffer", "gl_buffer"},
+        {"RenderTarget", "RenderTarget"},
+        {"Stencil", "Stencil"},
+        {"Path Data", "Path Data"},
+        {"Buffer Object", "Buffer Object"},
+        {"Surface", "Surface"},*/
+};
+
+ATraceMemoryDump::ATraceMemoryDump() {
+    mLastDumpName.reserve(100);
+    mCategory.reserve(100);
+}
+
+void ATraceMemoryDump::dumpNumericValue(const char* dumpName, const char* valueName,
+                                        const char* units, uint64_t value) {
+    if (!strcmp(units, "bytes")) {
+        recordAndResetCountersIfNeeded(dumpName);
+        if (!strcmp(valueName, "size")) {
+            mLastDumpValue = value;
+        } else if (!strcmp(valueName, "purgeable_size")) {
+            mLastPurgeableDumpValue = value;
+        }
+    }
+}
+
+void ATraceMemoryDump::dumpStringValue(const char* dumpName, const char* valueName,
+                                       const char* value) {
+    if (!strcmp(valueName, "type")) {
+        recordAndResetCountersIfNeeded(dumpName);
+        auto categoryIt = sResourceMap.find(value);
+        if (categoryIt != sResourceMap.end()) {
+            mCategory = categoryIt->second;
+        }
+    }
+}
+
+void ATraceMemoryDump::setMemoryBacking(const char* dumpName, const char* backingType,
+                                        const char* backingObjectId) {
+    recordAndResetCountersIfNeeded(dumpName);
+    auto categoryIt = sResourceMap.find(backingType);
+    if (categoryIt != sResourceMap.end()) {
+        mCategory = categoryIt->second;
+    }
+}
+
+/**
+ * startFrame is invoked before dumping anything. It resets counters from the previous frame.
+ * This is important, because if there is no new data for a given category trace would assume
+ * usage has not changed (instead of reporting 0).
+ */
+void ATraceMemoryDump::startFrame() {
+    resetCurrentCounter("");
+    for (auto& it : mCurrentValues) {
+        // Once a category is observed in at least one frame, it is always reported in subsequent
+        // frames (even if it is 0). Not logging a category to ATRACE would mean its value has not
+        // changed since the previous frame, which is not what we want.
+        it.second.time = 0;
+        // If purgeableTime is INVALID_TIME, then logTraces won't log it at all.
+        if (it.second.purgeableTime != INVALID_TIME) {
+            it.second.purgeableTime = 0;
+        }
+    }
+}
+
+/**
+ * logTraces reads from mCurrentValues and logs the counters with ATRACE.
+ */
+void ATraceMemoryDump::logTraces() {
+    // Accumulate data from last dumpName
+    recordAndResetCountersIfNeeded("");
+    for (auto& it : mCurrentValues) {
+        ATRACE_INT64(it.first.c_str(), it.second.time);
+        if (it.second.purgeableTime != INVALID_TIME) {
+            ATRACE_INT64((std::string("Purgeable ") + it.first).c_str(), it.second.purgeableTime);
+        }
+    }
+}
+
+/**
+ * recordAndResetCountersIfNeeded reads memory usage from mLastDumpValue/mLastPurgeableDumpValue and
+ * accumulates in mCurrentValues[category]. It makes provision to create a new category and track
+ * purgeable memory only if there is at least one observation.
+ * recordAndResetCountersIfNeeded won't do anything until all the information for a given dumpName
+ * is received.
+ */
+void ATraceMemoryDump::recordAndResetCountersIfNeeded(const char* dumpName) {
+    if (!mLastDumpName.compare(dumpName)) {
+        // Still waiting for more data for current dumpName.
+        return;
+    }
+
+    // First invocation will have an empty mLastDumpName.
+    if (!mLastDumpName.empty()) {
+        // A new dumpName observed -> store the data already collected.
+        auto memoryCounter = mCurrentValues.find(mCategory);
+        if (memoryCounter != mCurrentValues.end()) {
+            memoryCounter->second.time += mLastDumpValue;
+            if (mLastPurgeableDumpValue != INVALID_TIME) {
+                if (memoryCounter->second.purgeableTime == INVALID_TIME) {
+                    memoryCounter->second.purgeableTime = mLastPurgeableDumpValue;
+                } else {
+                    memoryCounter->second.purgeableTime += mLastPurgeableDumpValue;
+                }
+            }
+        } else {
+            mCurrentValues[mCategory] = {mLastDumpValue, mLastPurgeableDumpValue};
+        }
+    }
+
+    // Reset counters and default category for the newly observed "dumpName".
+    resetCurrentCounter(dumpName);
+}
+
+void ATraceMemoryDump::resetCurrentCounter(const char* dumpName) {
+    mLastDumpValue = 0;
+    mLastPurgeableDumpValue = INVALID_TIME;
+    mLastDumpName = dumpName;
+    // Categories not listed in sResourceMap are reported as "GPU memory"
+    mCategory = "GPU Memory";
+}
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/pipeline/skia/ATraceMemoryDump.h b/libs/hwui/pipeline/skia/ATraceMemoryDump.h
new file mode 100644
index 0000000..aa5c401
--- /dev/null
+++ b/libs/hwui/pipeline/skia/ATraceMemoryDump.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#pragma once
+
+#include <SkString.h>
+#include <SkTraceMemoryDump.h>
+
+#include <string>
+#include <unordered_map>
+#include <utility>
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+class ATraceMemoryDump : public SkTraceMemoryDump {
+public:
+    ATraceMemoryDump();
+    ~ATraceMemoryDump() override {}
+
+    void dumpNumericValue(const char* dumpName, const char* valueName, const char* units,
+                          uint64_t value) override;
+
+    void dumpStringValue(const char* dumpName, const char* valueName, const char* value) override;
+
+    LevelOfDetail getRequestedDetails() const override {
+        return SkTraceMemoryDump::kLight_LevelOfDetail;
+    }
+
+    bool shouldDumpWrappedObjects() const override { return false; }
+
+    void setMemoryBacking(const char* dumpName, const char* backingType,
+                          const char* backingObjectId) override;
+
+    void setDiscardableMemoryBacking(const char*, const SkDiscardableMemory&) override {}
+
+    void startFrame();
+
+    void logTraces();
+
+private:
+    std::string mLastDumpName;
+
+    uint64_t mLastDumpValue;
+
+    uint64_t mLastPurgeableDumpValue;
+
+    std::string mCategory;
+
+    struct TraceValue {
+        uint64_t time;
+        uint64_t purgeableTime;
+    };
+
+    // keys are define in sResourceMap
+    std::unordered_map<std::string, TraceValue> mCurrentValues;
+
+    void recordAndResetCountersIfNeeded(const char* dumpName);
+
+    void resetCurrentCounter(const char* dumpName);
+};
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
\ No newline at end of file
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 11dc013..35a885f 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -457,7 +457,10 @@
                                    const Rect& contentDrawBounds, SkCanvas* canvas,
                                    const SkMatrix& preTransform) {
     SkAutoCanvasRestore saver(canvas, true);
-    canvas->androidFramework_setDeviceClipRestriction(preTransform.mapRect(clip).roundOut());
+    auto clipRestriction = preTransform.mapRect(clip).roundOut();
+    canvas->androidFramework_setDeviceClipRestriction(clipRestriction);
+    canvas->drawAnnotation(SkRect::Make(clipRestriction), "AndroidDeviceClipRestriction",
+        nullptr);
     canvas->concat(preTransform);
 
     // STOPSHIP: Revert, temporary workaround to clear always F16 frame buffer for b/74976293
diff --git a/libs/hwui/protos/graphicsstats.proto b/libs/hwui/protos/graphicsstats.proto
index 0cd5c62..dd5676c 100644
--- a/libs/hwui/protos/graphicsstats.proto
+++ b/libs/hwui/protos/graphicsstats.proto
@@ -29,6 +29,11 @@
 }
 
 message GraphicsStatsProto {
+    enum PipelineType {
+        GL = 0;
+        VULKAN = 1;
+    }
+
     // The package name of the app
     optional string package_name = 1;
 
@@ -49,6 +54,9 @@
 
     // The gpu frame time histogram for the package
     repeated GraphicsStatsHistogramBucketProto gpu_histogram = 7;
+
+    // HWUI renders pipeline type: GL or Vulkan
+    optional PipelineType pipeline = 8;
 }
 
 message GraphicsStatsJankSummaryProto {
diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp
index fad9440..7e8c96d 100644
--- a/libs/hwui/renderstate/RenderState.cpp
+++ b/libs/hwui/renderstate/RenderState.cpp
@@ -16,7 +16,6 @@
 #include "renderstate/RenderState.h"
 
 #include "renderthread/RenderThread.h"
-#include "GpuMemoryTracker.h"
 
 namespace android {
 namespace uirenderer {
@@ -25,15 +24,10 @@
     mThreadId = pthread_self();
 }
 
-void RenderState::onContextCreated() {
-    GpuMemoryTracker::onGpuContextCreated();
-}
-
 void RenderState::onContextDestroyed() {
     for(auto callback : mContextCallbacks) {
         callback->onContextDestroyed();
     }
-    GpuMemoryTracker::onGpuContextDestroyed();
 }
 
 void RenderState::postDecStrong(VirtualLightRefBase* object) {
diff --git a/libs/hwui/renderstate/RenderState.h b/libs/hwui/renderstate/RenderState.h
index ff5d02f..e08d32a 100644
--- a/libs/hwui/renderstate/RenderState.h
+++ b/libs/hwui/renderstate/RenderState.h
@@ -62,7 +62,6 @@
     ~RenderState() {}
 
     // Context notifications are only to be triggered by renderthread::RenderThread
-    void onContextCreated();
     void onContextDestroyed();
 
     std::set<IGpuContextCallback*> mContextCallbacks;
diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp
index eaed46c..d177855 100644
--- a/libs/hwui/renderthread/CacheManager.cpp
+++ b/libs/hwui/renderthread/CacheManager.cpp
@@ -20,10 +20,12 @@
 #include "Layer.h"
 #include "Properties.h"
 #include "RenderThread.h"
+#include "pipeline/skia/ATraceMemoryDump.h"
 #include "pipeline/skia/ShaderCache.h"
 #include "pipeline/skia/SkiaMemoryTracer.h"
 #include "renderstate/RenderState.h"
 #include "thread/CommonPool.h"
+#include <utils/Trace.h>
 
 #include <GrContextOptions.h>
 #include <SkExecutor.h>
@@ -184,6 +186,18 @@
     gpuTracer.logTotals(log);
 }
 
+void CacheManager::onFrameCompleted() {
+    if (ATRACE_ENABLED()) {
+        static skiapipeline::ATraceMemoryDump tracer;
+        tracer.startFrame();
+        SkGraphics::DumpMemoryStatistics(&tracer);
+        if (mGrContext) {
+            mGrContext->dumpMemoryStatistics(&tracer);
+        }
+        tracer.logTraces();
+    }
+}
+
 } /* namespace renderthread */
 } /* namespace uirenderer */
 } /* namespace android */
diff --git a/libs/hwui/renderthread/CacheManager.h b/libs/hwui/renderthread/CacheManager.h
index 968251e..b009cc4 100644
--- a/libs/hwui/renderthread/CacheManager.h
+++ b/libs/hwui/renderthread/CacheManager.h
@@ -50,6 +50,7 @@
 
     size_t getCacheSize() const { return mMaxResourceBytes; }
     size_t getBackgroundCacheSize() const { return mBackgroundResourceBytes; }
+    void onFrameCompleted();
 
 private:
     friend class RenderThread;
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 8490221..5993e17 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -16,7 +16,6 @@
 
 #include "CanvasContext.h"
 
-#include <GpuMemoryTracker.h>
 #include <apex/window.h>
 #include <fcntl.h>
 #include <strings.h>
@@ -558,7 +557,7 @@
         mJankTracker.finishGpuDraw(*forthBehind);
     }
 
-    GpuMemoryTracker::onFrameCompleted();
+    mRenderThread.cacheManager().onFrameCompleted();
 }
 
 // Called by choreographer to do an RT-driven animation
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index d78f641..cae3e3b 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -270,7 +270,6 @@
     }
     mGrContext = std::move(context);
     if (mGrContext) {
-        mRenderState->onContextCreated();
         DeviceInfo::setMaxTextureSize(mGrContext->maxRenderTargetSize());
     }
 }
diff --git a/libs/hwui/service/GraphicsStatsService.cpp b/libs/hwui/service/GraphicsStatsService.cpp
index 12c5b83..c418617 100644
--- a/libs/hwui/service/GraphicsStatsService.cpp
+++ b/libs/hwui/service/GraphicsStatsService.cpp
@@ -16,24 +16,28 @@
 
 #include "GraphicsStatsService.h"
 
-#include "JankTracker.h"
-#include "protos/graphicsstats.pb.h"
-
-#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
-#include <log/log.h>
-
 #include <errno.h>
 #include <fcntl.h>
+#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
 #include <inttypes.h>
+#include <log/log.h>
 #include <sys/mman.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
 
+#include <algorithm>
+#include <map>
+#include <vector>
+
+#include "JankTracker.h"
+#include "protos/graphicsstats.pb.h"
+
 namespace android {
 namespace uirenderer {
 
 using namespace google::protobuf;
+using namespace uirenderer::protos;
 
 constexpr int32_t sCurrentFileVersion = 1;
 constexpr int32_t sHeaderSize = 4;
@@ -42,9 +46,9 @@
 constexpr int sHistogramSize = ProfileData::HistogramSize();
 constexpr int sGPUHistogramSize = ProfileData::GPUHistogramSize();
 
-static bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto,
-                                      const std::string& package, int64_t versionCode,
-                                      int64_t startTime, int64_t endTime, const ProfileData* data);
+static bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, const std::string& package,
+                                      int64_t versionCode, int64_t startTime, int64_t endTime,
+                                      const ProfileData* data);
 static void dumpAsTextToFd(protos::GraphicsStatsProto* proto, int outFd);
 
 class FileDescriptor {
@@ -57,7 +61,7 @@
         }
     }
     bool valid() { return mFd != -1; }
-    operator int() { return mFd; } // NOLINT(google-explicit-constructor)
+    operator int() { return mFd; }  // NOLINT(google-explicit-constructor)
 
 private:
     int mFd;
@@ -167,6 +171,8 @@
     }
     proto->set_package_name(package);
     proto->set_version_code(versionCode);
+    proto->set_pipeline(data->pipelineType() == RenderPipelineType::SkiaGL ?
+            GraphicsStatsProto_PipelineType_GL : GraphicsStatsProto_PipelineType_VULKAN);
     auto summary = proto->mutable_summary();
     summary->set_total_frames(summary->total_frames() + data->totalFrameCount());
     summary->set_janky_frames(summary->janky_frames() + data->jankFrameCount());
@@ -179,8 +185,8 @@
     summary->set_slow_bitmap_upload_count(summary->slow_bitmap_upload_count() +
                                           data->jankTypeCount(kSlowSync));
     summary->set_slow_draw_count(summary->slow_draw_count() + data->jankTypeCount(kSlowRT));
-    summary->set_missed_deadline_count(summary->missed_deadline_count()
-            + data->jankTypeCount(kMissedDeadline));
+    summary->set_missed_deadline_count(summary->missed_deadline_count() +
+                                       data->jankTypeCount(kMissedDeadline));
 
     bool creatingHistogram = false;
     if (proto->histogram_size() == 0) {
@@ -365,17 +371,69 @@
 
 class GraphicsStatsService::Dump {
 public:
-    Dump(int outFd, DumpType type) : mFd(outFd), mType(type) {}
+    Dump(int outFd, DumpType type) : mFd(outFd), mType(type) {
+        if (mFd == -1 && mType == DumpType::Protobuf) {
+            mType = DumpType::ProtobufStatsd;
+        }
+    }
     int fd() { return mFd; }
     DumpType type() { return mType; }
     protos::GraphicsStatsServiceDumpProto& proto() { return mProto; }
+    void mergeStat(const protos::GraphicsStatsProto& stat);
+    void updateProto();
 
 private:
+    // use package name and app version for a key
+    typedef std::pair<std::string, int64_t> DumpKey;
+
+    std::map<DumpKey, protos::GraphicsStatsProto> mStats;
     int mFd;
     DumpType mType;
     protos::GraphicsStatsServiceDumpProto mProto;
 };
 
+void GraphicsStatsService::Dump::mergeStat(const protos::GraphicsStatsProto& stat) {
+    auto dumpKey = std::make_pair(stat.package_name(), stat.version_code());
+    auto findIt = mStats.find(dumpKey);
+    if (findIt == mStats.end()) {
+        mStats[dumpKey] = stat;
+    } else {
+        auto summary = findIt->second.mutable_summary();
+        summary->set_total_frames(summary->total_frames() + stat.summary().total_frames());
+        summary->set_janky_frames(summary->janky_frames() + stat.summary().janky_frames());
+        summary->set_missed_vsync_count(summary->missed_vsync_count() +
+                                        stat.summary().missed_vsync_count());
+        summary->set_high_input_latency_count(summary->high_input_latency_count() +
+                                              stat.summary().high_input_latency_count());
+        summary->set_slow_ui_thread_count(summary->slow_ui_thread_count() +
+                                          stat.summary().slow_ui_thread_count());
+        summary->set_slow_bitmap_upload_count(summary->slow_bitmap_upload_count() +
+                                              stat.summary().slow_bitmap_upload_count());
+        summary->set_slow_draw_count(summary->slow_draw_count() + stat.summary().slow_draw_count());
+        summary->set_missed_deadline_count(summary->missed_deadline_count() +
+                                           stat.summary().missed_deadline_count());
+        for (int bucketIndex = 0; bucketIndex < findIt->second.histogram_size(); bucketIndex++) {
+            auto bucket = findIt->second.mutable_histogram(bucketIndex);
+            bucket->set_frame_count(bucket->frame_count() +
+                                    stat.histogram(bucketIndex).frame_count());
+        }
+        for (int bucketIndex = 0; bucketIndex < findIt->second.gpu_histogram_size();
+             bucketIndex++) {
+            auto bucket = findIt->second.mutable_gpu_histogram(bucketIndex);
+            bucket->set_frame_count(bucket->frame_count() +
+                                    stat.gpu_histogram(bucketIndex).frame_count());
+        }
+        findIt->second.set_stats_start(std::min(findIt->second.stats_start(), stat.stats_start()));
+        findIt->second.set_stats_end(std::max(findIt->second.stats_end(), stat.stats_end()));
+    }
+}
+
+void GraphicsStatsService::Dump::updateProto() {
+    for (auto& stat : mStats) {
+        mProto.add_stats()->CopyFrom(stat.second);
+    }
+}
+
 GraphicsStatsService::Dump* GraphicsStatsService::createDump(int outFd, DumpType type) {
     return new Dump(outFd, type);
 }
@@ -396,8 +454,9 @@
               path.empty() ? "<empty>" : path.c_str(), data);
         return;
     }
-
-    if (dump->type() == DumpType::Protobuf) {
+    if (dump->type() == DumpType::ProtobufStatsd) {
+        dump->mergeStat(statsProto);
+    } else if (dump->type() == DumpType::Protobuf) {
         dump->proto().add_stats()->CopyFrom(statsProto);
     } else {
         dumpAsTextToFd(&statsProto, dump->fd());
@@ -409,7 +468,9 @@
     if (!parseFromFile(path, &statsProto)) {
         return;
     }
-    if (dump->type() == DumpType::Protobuf) {
+    if (dump->type() == DumpType::ProtobufStatsd) {
+        dump->mergeStat(statsProto);
+    } else if (dump->type() == DumpType::Protobuf) {
         dump->proto().add_stats()->CopyFrom(statsProto);
     } else {
         dumpAsTextToFd(&statsProto, dump->fd());
@@ -424,5 +485,79 @@
     delete dump;
 }
 
+class MemOutputStreamLite : public io::ZeroCopyOutputStream {
+public:
+    explicit MemOutputStreamLite() : mCopyAdapter(), mImpl(&mCopyAdapter) {}
+    virtual ~MemOutputStreamLite() {}
+
+    virtual bool Next(void** data, int* size) override { return mImpl.Next(data, size); }
+
+    virtual void BackUp(int count) override { mImpl.BackUp(count); }
+
+    virtual int64 ByteCount() const override { return mImpl.ByteCount(); }
+
+    bool Flush() { return mImpl.Flush(); }
+
+    void copyData(const DumpMemoryFn& reader, void* param1, void* param2) {
+        int bufferOffset = 0;
+        int totalSize = mCopyAdapter.mBuffersSize - mCopyAdapter.mCurrentBufferUnusedSize;
+        int totalDataLeft = totalSize;
+        for (auto& it : mCopyAdapter.mBuffers) {
+            int bufferSize = std::min(totalDataLeft, (int)it.size());  // last buffer is not full
+            reader(it.data(), bufferOffset, bufferSize, totalSize, param1, param2);
+            bufferOffset += bufferSize;
+            totalDataLeft -= bufferSize;
+        }
+    }
+
+private:
+    struct MemAdapter : public io::CopyingOutputStream {
+        // Data is stored in an array of buffers.
+        // JNI SetByteArrayRegion assembles data in one continuous Java byte[] buffer.
+        std::vector<std::vector<unsigned char>> mBuffers;
+        int mBuffersSize = 0;                     // total bytes allocated in mBuffers
+        int mCurrentBufferUnusedSize = 0;         // unused bytes in the last buffer mBuffers.back()
+        unsigned char* mCurrentBuffer = nullptr;  // pointer to next free byte in mBuffers.back()
+
+        explicit MemAdapter() {}
+        virtual ~MemAdapter() {}
+
+        virtual bool Write(const void* buffer, int size) override {
+            while (size > 0) {
+                if (0 == mCurrentBufferUnusedSize) {
+                    mCurrentBufferUnusedSize =
+                            std::max(size, mBuffersSize ? 2 * mBuffersSize : 10000);
+                    mBuffers.emplace_back();
+                    mBuffers.back().resize(mCurrentBufferUnusedSize);
+                    mCurrentBuffer = mBuffers.back().data();
+                    mBuffersSize += mCurrentBufferUnusedSize;
+                }
+                int dataMoved = std::min(mCurrentBufferUnusedSize, size);
+                memcpy(mCurrentBuffer, buffer, dataMoved);
+                mCurrentBufferUnusedSize -= dataMoved;
+                mCurrentBuffer += dataMoved;
+                buffer = reinterpret_cast<const unsigned char*>(buffer) + dataMoved;
+                size -= dataMoved;
+            }
+            return true;
+        }
+    };
+
+    MemOutputStreamLite::MemAdapter mCopyAdapter;
+    io::CopyingOutputStreamAdaptor mImpl;
+};
+
+void GraphicsStatsService::finishDumpInMemory(Dump* dump, const DumpMemoryFn& reader, void* param1,
+                                              void* param2) {
+    MemOutputStreamLite stream;
+    dump->updateProto();
+    bool success = dump->proto().SerializeToZeroCopyStream(&stream) && stream.Flush();
+    delete dump;
+    if (!success) {
+        return;
+    }
+    stream.copyData(reader, param1, param2);
+}
+
 } /* namespace uirenderer */
 } /* namespace android */
diff --git a/libs/hwui/service/GraphicsStatsService.h b/libs/hwui/service/GraphicsStatsService.h
index 389f599..4bed9633 100644
--- a/libs/hwui/service/GraphicsStatsService.h
+++ b/libs/hwui/service/GraphicsStatsService.h
@@ -27,6 +27,9 @@
 class GraphicsStatsProto;
 }
 
+typedef void (*DumpMemoryFn)(void* buffer, int bufferOffset, int bufferSize, int totalSize,
+                             void* param1, void* param2);
+
 /*
  * The exported entry points used by GraphicsStatsService.java in f/b/services/core
  *
@@ -40,6 +43,7 @@
     enum class DumpType {
         Text,
         Protobuf,
+        ProtobufStatsd,
     };
 
     ANDROID_API static void saveBuffer(const std::string& path, const std::string& package,
@@ -52,6 +56,8 @@
                                       int64_t startTime, int64_t endTime, const ProfileData* data);
     ANDROID_API static void addToDump(Dump* dump, const std::string& path);
     ANDROID_API static void finishDump(Dump* dump);
+    ANDROID_API static void finishDumpInMemory(Dump* dump, const DumpMemoryFn& reader, void* param1,
+                                               void* param2);
 
     // Visible for testing
     static bool parseFromFile(const std::string& path, protos::GraphicsStatsProto* output);
diff --git a/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp b/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp
deleted file mode 100644
index dac888c..0000000
--- a/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2016 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.
- */
-
-#include <GpuMemoryTracker.h>
-#include <gtest/gtest.h>
-
-#include "renderthread/EglManager.h"
-#include "renderthread/RenderThread.h"
-#include "tests/common/TestUtils.h"
-
-#include <utils/StrongPointer.h>
-
-using namespace android;
-using namespace android::uirenderer;
-using namespace android::uirenderer::renderthread;
-
-class TestGPUObject : public GpuMemoryTracker {
-public:
-    TestGPUObject() : GpuMemoryTracker(GpuObjectType::Texture) {}
-
-    void changeSize(int newSize) { notifySizeChanged(newSize); }
-};
-
-// Other tests may have created a renderthread and EGL context.
-// This will destroy the EGLContext on RenderThread if it exists so that the
-// current thread can spoof being a GPU thread
-static void destroyEglContext() {
-    if (TestUtils::isRenderThreadRunning()) {
-        TestUtils::runOnRenderThread([](RenderThread& thread) { thread.destroyRenderingContext(); });
-    }
-}
-
-TEST(GpuMemoryTracker, sizeCheck) {
-    destroyEglContext();
-
-    GpuMemoryTracker::onGpuContextCreated();
-    ASSERT_EQ(0, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
-    ASSERT_EQ(0, GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture));
-    {
-        TestGPUObject myObj;
-        ASSERT_EQ(1, GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture));
-        myObj.changeSize(500);
-        ASSERT_EQ(500, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
-        myObj.changeSize(1000);
-        ASSERT_EQ(1000, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
-        myObj.changeSize(300);
-        ASSERT_EQ(300, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
-    }
-    ASSERT_EQ(0, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
-    ASSERT_EQ(0, GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture));
-    GpuMemoryTracker::onGpuContextDestroyed();
-}
diff --git a/location/java/android/location/Criteria.java b/location/java/android/location/Criteria.java
index 1370b10..26f73f7 100644
--- a/location/java/android/location/Criteria.java
+++ b/location/java/android/location/Criteria.java
@@ -16,9 +16,16 @@
 
 package android.location;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * A class indicating the application criteria for selecting a
  * location provider. Providers may be ordered according to accuracy,
@@ -26,6 +33,25 @@
  * cost.
  */
 public class Criteria implements Parcelable {
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({NO_REQUIREMENT, POWER_LOW, POWER_MEDIUM, POWER_HIGH})
+    public @interface PowerRequirement {
+    }
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({NO_REQUIREMENT, ACCURACY_LOW, ACCURACY_MEDIUM, ACCURACY_HIGH})
+    public @interface AccuracyRequirement {
+    }
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({NO_REQUIREMENT, ACCURACY_FINE, ACCURACY_COARSE})
+    public @interface LocationAccuracyRequirement {
+    }
+
     /**
      * A constant indicating that the application does not choose to
      * place requirement on a particular feature.
@@ -81,15 +107,15 @@
      */
     public static final int ACCURACY_HIGH = 3;
 
-    private int mHorizontalAccuracy    = NO_REQUIREMENT;
-    private int mVerticalAccuracy      = NO_REQUIREMENT;
-    private int mSpeedAccuracy         = NO_REQUIREMENT;
-    private int mBearingAccuracy       = NO_REQUIREMENT;
-    private int mPowerRequirement      = NO_REQUIREMENT;
-    private boolean mAltitudeRequired  = false;
-    private boolean mBearingRequired   = false;
-    private boolean mSpeedRequired     = false;
-    private boolean mCostAllowed       = false;
+    private int mHorizontalAccuracy = NO_REQUIREMENT;
+    private int mVerticalAccuracy = NO_REQUIREMENT;
+    private int mSpeedAccuracy = NO_REQUIREMENT;
+    private int mBearingAccuracy = NO_REQUIREMENT;
+    private int mPowerRequirement = NO_REQUIREMENT;
+    private boolean mAltitudeRequired = false;
+    private boolean mBearingRequired = false;
+    private boolean mSpeedRequired = false;
+    private boolean mCostAllowed = false;
 
     /**
      * Constructs a new Criteria object.  The new object will have no
@@ -97,7 +123,8 @@
      * require altitude, speed, or bearing; and will not allow monetary
      * cost.
      */
-    public Criteria() {}
+    public Criteria() {
+    }
 
     /**
      * Constructs a new Criteria object that is a copy of the given criteria.
@@ -115,125 +142,121 @@
     }
 
     /**
-     * Indicates the desired horizontal accuracy (latitude and longitude).
-     * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_MEDIUM},
-     * {@link #ACCURACY_HIGH} or {@link #NO_REQUIREMENT}.
-     * More accurate location may consume more power and may take longer.
+     * Indicates the desired horizontal accuracy (latitude and longitude). Accuracy may be
+     * {@link #ACCURACY_LOW}, {@link #ACCURACY_MEDIUM}, {@link #ACCURACY_HIGH} or
+     * {@link #NO_REQUIREMENT}. More accurate location may consume more power and may take longer.
      *
      * @throws IllegalArgumentException if accuracy is not one of the supported constants
      */
-    public void setHorizontalAccuracy(int accuracy) {
-        if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_HIGH) {
-            throw new IllegalArgumentException("accuracy=" + accuracy);
-        }
-        mHorizontalAccuracy = accuracy;
+    public void setHorizontalAccuracy(@AccuracyRequirement int accuracy) {
+        mHorizontalAccuracy = Preconditions.checkArgumentInRange(accuracy, NO_REQUIREMENT,
+                ACCURACY_HIGH, "accuracy");
     }
 
     /**
      * Returns a constant indicating the desired horizontal accuracy (latitude and longitude).
-     * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_MEDIUM},
-     * {@link #ACCURACY_HIGH} or {@link #NO_REQUIREMENT}.
+     *
+     * @see #setHorizontalAccuracy(int)
      */
+    @AccuracyRequirement
     public int getHorizontalAccuracy() {
         return mHorizontalAccuracy;
     }
 
     /**
-     * Indicates the desired vertical accuracy (altitude).
-     * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_MEDIUM},
-     * {@link #ACCURACY_HIGH} or {@link #NO_REQUIREMENT}.
-     * More accurate location may consume more power and may take longer.
+     * Indicates the desired vertical accuracy (altitude). Accuracy may be {@link #ACCURACY_LOW},
+     * {@link #ACCURACY_MEDIUM}, {@link #ACCURACY_HIGH} or {@link #NO_REQUIREMENT}. More accurate
+     * location may consume more power and may take longer.
      *
      * @throws IllegalArgumentException if accuracy is not one of the supported constants
      */
-    public void setVerticalAccuracy(int accuracy) {
-        if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_HIGH) {
-            throw new IllegalArgumentException("accuracy=" + accuracy);
-        }
-        mVerticalAccuracy = accuracy;
+    public void setVerticalAccuracy(@AccuracyRequirement int accuracy) {
+        mVerticalAccuracy = Preconditions.checkArgumentInRange(accuracy, NO_REQUIREMENT,
+                ACCURACY_HIGH, "accuracy");
     }
 
     /**
      * Returns a constant indicating the desired vertical accuracy (altitude).
-     * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_HIGH},
-     * or {@link #NO_REQUIREMENT}.
+     *
+     * @see #setVerticalAccuracy(int)
      */
+    @AccuracyRequirement
     public int getVerticalAccuracy() {
         return mVerticalAccuracy;
     }
 
     /**
-     * Indicates the desired speed accuracy.
-     * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_HIGH},
-     * or {@link #NO_REQUIREMENT}.
-     * More accurate location may consume more power and may take longer.
+     * Indicates the desired speed accuracy. Accuracy may be {@link #ACCURACY_LOW},
+     * {@link #ACCURACY_MEDIUM}, {@link #ACCURACY_HIGH}, or {@link #NO_REQUIREMENT}. More accurate
+     * location may consume more power and may take longer.
      *
      * @throws IllegalArgumentException if accuracy is not one of the supported constants
      */
-    public void setSpeedAccuracy(int accuracy) {
-        if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_HIGH) {
-            throw new IllegalArgumentException("accuracy=" + accuracy);
-        }
-        mSpeedAccuracy = accuracy;
+    public void setSpeedAccuracy(@AccuracyRequirement int accuracy) {
+        mSpeedAccuracy = Preconditions.checkArgumentInRange(accuracy, NO_REQUIREMENT, ACCURACY_HIGH,
+                "accuracy");
     }
 
     /**
-     * Returns a constant indicating the desired speed accuracy
-     * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_HIGH},
-     * or {@link #NO_REQUIREMENT}.
+     * Returns a constant indicating the desired speed accuracy.
+     *
+     * @see #setSpeedAccuracy(int)
      */
+    @AccuracyRequirement
     public int getSpeedAccuracy() {
         return mSpeedAccuracy;
     }
 
     /**
-     * Indicates the desired bearing accuracy.
-     * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_HIGH},
-     * or {@link #NO_REQUIREMENT}.
-     * More accurate location may consume more power and may take longer.
+     * Indicates the desired bearing accuracy. Accuracy may be {@link #ACCURACY_LOW},
+     * {@link #ACCURACY_MEDIUM}, {@link #ACCURACY_HIGH}, or {@link #NO_REQUIREMENT}. More accurate
+     * location may consume more power and may take longer.
      *
      * @throws IllegalArgumentException if accuracy is not one of the supported constants
      */
-    public void setBearingAccuracy(int accuracy) {
-        if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_HIGH) {
-            throw new IllegalArgumentException("accuracy=" + accuracy);
-        }
-        mBearingAccuracy = accuracy;
+    public void setBearingAccuracy(@AccuracyRequirement int accuracy) {
+        mBearingAccuracy = Preconditions.checkArgumentInRange(accuracy, NO_REQUIREMENT,
+                ACCURACY_HIGH, "accuracy");
     }
 
     /**
      * Returns a constant indicating the desired bearing accuracy.
-     * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_HIGH},
-     * or {@link #NO_REQUIREMENT}.
+     *
+     * @see #setBearingAccuracy(int)
      */
+    @AccuracyRequirement
     public int getBearingAccuracy() {
         return mBearingAccuracy;
     }
 
     /**
-     * Indicates the desired accuracy for latitude and longitude. Accuracy
-     * may be {@link #ACCURACY_FINE} if desired location
-     * is fine, else it can be {@link #ACCURACY_COARSE}.
-     * More accurate location may consume more power and may take longer.
+     * Indicates the desired accuracy for latitude and longitude. Accuracy may be
+     * {@link #ACCURACY_FINE} or {@link #ACCURACY_COARSE}. More accurate location may consume more
+     * power and may take longer.
      *
      * @throws IllegalArgumentException if accuracy is not one of the supported constants
      */
-    public void setAccuracy(int accuracy) {
-        if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_COARSE) {
-            throw new IllegalArgumentException("accuracy=" + accuracy);
-        }
-        if (accuracy == ACCURACY_FINE) {
-            mHorizontalAccuracy = ACCURACY_HIGH;
-        } else {
-            mHorizontalAccuracy = ACCURACY_LOW;
+    public void setAccuracy(@LocationAccuracyRequirement int accuracy) {
+        Preconditions.checkArgumentInRange(accuracy, NO_REQUIREMENT, ACCURACY_COARSE, "accuracy");
+        switch (accuracy) {
+            case NO_REQUIREMENT:
+                setHorizontalAccuracy(NO_REQUIREMENT);
+                break;
+            case ACCURACY_FINE:
+                setHorizontalAccuracy(ACCURACY_HIGH);
+                break;
+            case ACCURACY_COARSE:
+                setHorizontalAccuracy(ACCURACY_LOW);
+                break;
         }
     }
 
     /**
-     * Returns a constant indicating desired accuracy of location
-     * Accuracy may be {@link #ACCURACY_FINE} if desired location
-     * is fine, else it can be {@link #ACCURACY_COARSE}.
+     * Returns a constant indicating desired accuracy of location.
+     *
+     * @see #setAccuracy(int)
      */
+    @LocationAccuracyRequirement
     public int getAccuracy() {
         if (mHorizontalAccuracy >= ACCURACY_HIGH) {
             return ACCURACY_FINE;
@@ -243,21 +266,20 @@
     }
 
     /**
-     * Indicates the desired maximum power level.  The level parameter
-     * must be one of NO_REQUIREMENT, POWER_LOW, POWER_MEDIUM, or
-     * POWER_HIGH.
+     * Indicates the desired maximum power requirement. The power requirement parameter may be
+     * {@link #NO_REQUIREMENT}, {@link #POWER_LOW}, {@link #POWER_MEDIUM}, or {@link #POWER_HIGH}.
      */
-    public void setPowerRequirement(int level) {
-        if (level < NO_REQUIREMENT || level > POWER_HIGH) {
-            throw new IllegalArgumentException("level=" + level);
-        }
-        mPowerRequirement = level;
+    public void setPowerRequirement(@PowerRequirement int powerRequirement) {
+        mPowerRequirement = Preconditions.checkArgumentInRange(powerRequirement, NO_REQUIREMENT,
+                POWER_HIGH, "powerRequirement");
     }
 
     /**
-     * Returns a constant indicating the desired power requirement.  The
-     * returned
+     * Returns a constant indicating the desired maximum power requirement.
+     *
+     * @see #setPowerRequirement(int)
      */
+    @PowerRequirement
     public int getPowerRequirement() {
         return mPowerRequirement;
     }
@@ -277,8 +299,8 @@
     }
 
     /**
-     * Indicates whether the provider must provide altitude information.
-     * Not all fixes are guaranteed to contain such information.
+     * Indicates whether the provider must provide altitude information. Not all fixes are
+     * guaranteed to contain such information.
      */
     public void setAltitudeRequired(boolean altitudeRequired) {
         mAltitudeRequired = altitudeRequired;
@@ -286,15 +308,16 @@
 
     /**
      * Returns whether the provider must provide altitude information.
-     * Not all fixes are guaranteed to contain such information.
+     *
+     * @see #setAltitudeRequired(boolean)
      */
     public boolean isAltitudeRequired() {
         return mAltitudeRequired;
     }
 
     /**
-     * Indicates whether the provider must provide speed information.
-     * Not all fixes are guaranteed to contain such information.
+     * Indicates whether the provider must provide speed information. Not all fixes are guaranteed
+     * to contain such information.
      */
     public void setSpeedRequired(boolean speedRequired) {
         mSpeedRequired = speedRequired;
@@ -302,15 +325,16 @@
 
     /**
      * Returns whether the provider must provide speed information.
-     * Not all fixes are guaranteed to contain such information.
+     *
+     * @see #setSpeedRequired(boolean)
      */
     public boolean isSpeedRequired() {
         return mSpeedRequired;
     }
 
     /**
-     * Indicates whether the provider must provide bearing information.
-     * Not all fixes are guaranteed to contain such information.
+     * Indicates whether the provider must provide bearing information. Not all fixes are guaranteed
+     * to contain such information.
      */
     public void setBearingRequired(boolean bearingRequired) {
         mBearingRequired = bearingRequired;
@@ -318,34 +342,36 @@
 
     /**
      * Returns whether the provider must provide bearing information.
-     * Not all fixes are guaranteed to contain such information.
+     *
+     * @see #setBearingRequired(boolean)
      */
     public boolean isBearingRequired() {
         return mBearingRequired;
     }
 
-    public static final @android.annotation.NonNull Parcelable.Creator<Criteria> CREATOR =
-        new Parcelable.Creator<Criteria>() {
-        @Override
-        public Criteria createFromParcel(Parcel in) {
-            Criteria c = new Criteria();
-            c.mHorizontalAccuracy = in.readInt();
-            c.mVerticalAccuracy = in.readInt();
-            c.mSpeedAccuracy = in.readInt();
-            c.mBearingAccuracy = in.readInt();
-            c.mPowerRequirement = in.readInt();
-            c.mAltitudeRequired = in.readInt() != 0;
-            c.mBearingRequired = in.readInt() != 0;
-            c.mSpeedRequired = in.readInt() != 0;
-            c.mCostAllowed = in.readInt() != 0;
-            return c;
-        }
+    @NonNull
+    public static final Parcelable.Creator<Criteria> CREATOR =
+            new Parcelable.Creator<Criteria>() {
+                @Override
+                public Criteria createFromParcel(Parcel in) {
+                    Criteria c = new Criteria();
+                    c.mHorizontalAccuracy = in.readInt();
+                    c.mVerticalAccuracy = in.readInt();
+                    c.mSpeedAccuracy = in.readInt();
+                    c.mBearingAccuracy = in.readInt();
+                    c.mPowerRequirement = in.readInt();
+                    c.mAltitudeRequired = in.readInt() != 0;
+                    c.mBearingRequired = in.readInt() != 0;
+                    c.mSpeedRequired = in.readInt() != 0;
+                    c.mCostAllowed = in.readInt() != 0;
+                    return c;
+                }
 
-        @Override
-        public Criteria[] newArray(int size) {
-            return new Criteria[size];
-        }
-    };
+                @Override
+                public Criteria[] newArray(int size) {
+                    return new Criteria[size];
+                }
+            };
 
     @Override
     public int describeContents() {
@@ -365,42 +391,57 @@
         parcel.writeInt(mCostAllowed ? 1 : 0);
     }
 
-    private static String powerToString(int power) {
-        switch (power) {
-            case NO_REQUIREMENT:
-                return "NO_REQ";
-            case POWER_LOW:
-                return "LOW";
-            case POWER_MEDIUM:
-                return "MEDIUM";
-            case POWER_HIGH:
-                return "HIGH";
-            default:
-                return "???";
-        }
-    }
-
-    private static String accuracyToString(int accuracy) {
-        switch (accuracy) {
-            case NO_REQUIREMENT:
-                return "---";
-            case ACCURACY_HIGH:
-                return "HIGH";
-            case ACCURACY_MEDIUM:
-                return "MEDIUM";
-            case ACCURACY_LOW:
-                return "LOW";
-            default:
-                return "???";
-        }
-    }
-
     @Override
     public String toString() {
         StringBuilder s = new StringBuilder();
-        s.append("Criteria[power=").append(powerToString(mPowerRequirement));
-        s.append(" acc=").append(accuracyToString(mHorizontalAccuracy));
+        s.append("Criteria[");
+        s.append("power=").append(requirementToString(mPowerRequirement)).append(", ");
+        s.append("accuracy=").append(requirementToString(mHorizontalAccuracy));
+        if (mVerticalAccuracy != NO_REQUIREMENT) {
+            s.append(", verticalAccuracy=").append(requirementToString(mVerticalAccuracy));
+        }
+        if (mSpeedAccuracy != NO_REQUIREMENT) {
+            s.append(", speedAccuracy=").append(requirementToString(mSpeedAccuracy));
+        }
+        if (mBearingAccuracy != NO_REQUIREMENT) {
+            s.append(", bearingAccuracy=").append(requirementToString(mBearingAccuracy));
+        }
+        if (mAltitudeRequired || mBearingRequired || mSpeedRequired) {
+            s.append(", required=[");
+            if (mAltitudeRequired) {
+                s.append("altitude, ");
+            }
+            if (mBearingRequired) {
+                s.append("bearing, ");
+            }
+            if (mSpeedRequired) {
+                s.append("speed, ");
+            }
+            s.setLength(s.length() - 2);
+            s.append("]");
+        }
+        if (mCostAllowed) {
+            s.append(", costAllowed");
+        }
         s.append(']');
         return s.toString();
     }
+
+    private static String requirementToString(int power) {
+        switch (power) {
+            case NO_REQUIREMENT:
+                return "None";
+            //case ACCURACY_LOW:
+            case POWER_LOW:
+                return "Low";
+            //case ACCURACY_MEDIUM:
+            case POWER_MEDIUM:
+                return "Medium";
+            //case ACCURACY_HIGH:
+            case POWER_HIGH:
+                return "High";
+            default:
+                return "???";
+        }
+    }
 }
diff --git a/location/java/com/android/internal/location/ProviderProperties.java b/location/java/com/android/internal/location/ProviderProperties.java
index def96f0..68f9ec3 100644
--- a/location/java/com/android/internal/location/ProviderProperties.java
+++ b/location/java/com/android/internal/location/ProviderProperties.java
@@ -16,15 +16,36 @@
 
 package com.android.internal.location;
 
+import android.annotation.IntDef;
+import android.location.Criteria;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * A Parcelable containing (legacy) location provider properties.
  * This object is just used inside the framework and system services.
+ *
  * @hide
  */
 public final class ProviderProperties implements Parcelable {
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({Criteria.POWER_LOW, Criteria.POWER_MEDIUM, Criteria.POWER_HIGH})
+    public @interface PowerRequirement {
+    }
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({Criteria.ACCURACY_FINE, Criteria.ACCURACY_COARSE})
+    public @interface Accuracy {
+    }
+
     /**
      * True if provider requires access to a
      * data network (e.g., the Internet), false otherwise.
@@ -79,58 +100,58 @@
 
     /**
      * Power requirement for this provider.
-     *
-     * @return the power requirement for this provider, as one of the
-     * constants Criteria.POWER_*.
      */
+    @PowerRequirement
     public final int mPowerRequirement;
 
     /**
      * Constant describing the horizontal accuracy returned
      * by this provider.
-     *
-     * @return the horizontal accuracy for this provider, as one of the
-     * constants Criteria.ACCURACY_COARSE or Criteria.ACCURACY_FINE
      */
+    @Accuracy
     public final int mAccuracy;
 
-    public ProviderProperties(boolean mRequiresNetwork,
-            boolean mRequiresSatellite, boolean mRequiresCell, boolean mHasMonetaryCost,
-            boolean mSupportsAltitude, boolean mSupportsSpeed, boolean mSupportsBearing,
-            int mPowerRequirement, int mAccuracy) {
-        this.mRequiresNetwork = mRequiresNetwork;
-        this.mRequiresSatellite = mRequiresSatellite;
-        this.mRequiresCell = mRequiresCell;
-        this.mHasMonetaryCost = mHasMonetaryCost;
-        this.mSupportsAltitude = mSupportsAltitude;
-        this.mSupportsSpeed = mSupportsSpeed;
-        this.mSupportsBearing = mSupportsBearing;
-        this.mPowerRequirement = mPowerRequirement;
-        this.mAccuracy = mAccuracy;
+    public ProviderProperties(boolean requiresNetwork, boolean requiresSatellite,
+            boolean requiresCell, boolean hasMonetaryCost, boolean supportsAltitude,
+            boolean supportsSpeed, boolean supportsBearing, @PowerRequirement int powerRequirement,
+            @Accuracy int accuracy) {
+        mRequiresNetwork = requiresNetwork;
+        mRequiresSatellite = requiresSatellite;
+        mRequiresCell = requiresCell;
+        mHasMonetaryCost = hasMonetaryCost;
+        mSupportsAltitude = supportsAltitude;
+        mSupportsSpeed = supportsSpeed;
+        mSupportsBearing = supportsBearing;
+        mPowerRequirement = Preconditions.checkArgumentInRange(powerRequirement, Criteria.POWER_LOW,
+                Criteria.POWER_HIGH, "powerRequirement");
+        mAccuracy = Preconditions.checkArgumentInRange(accuracy, Criteria.ACCURACY_FINE,
+                Criteria.ACCURACY_COARSE, "accuracy");
     }
 
     public static final Parcelable.Creator<ProviderProperties> CREATOR =
             new Parcelable.Creator<ProviderProperties>() {
-        @Override
-        public ProviderProperties createFromParcel(Parcel in) {
-            boolean requiresNetwork = in.readInt() == 1;
-            boolean requiresSatellite = in.readInt() == 1;
-            boolean requiresCell = in.readInt() == 1;
-            boolean hasMonetaryCost = in.readInt() == 1;
-            boolean supportsAltitude = in.readInt() == 1;
-            boolean supportsSpeed = in.readInt() == 1;
-            boolean supportsBearing = in.readInt() == 1;
-            int powerRequirement = in.readInt();
-            int accuracy = in.readInt();
-            return new ProviderProperties(requiresNetwork, requiresSatellite,
-                    requiresCell, hasMonetaryCost, supportsAltitude, supportsSpeed, supportsBearing,
-                    powerRequirement, accuracy);
-        }
-        @Override
-        public ProviderProperties[] newArray(int size) {
-            return new ProviderProperties[size];
-        }
-    };
+                @Override
+                public ProviderProperties createFromParcel(Parcel in) {
+                    boolean requiresNetwork = in.readInt() == 1;
+                    boolean requiresSatellite = in.readInt() == 1;
+                    boolean requiresCell = in.readInt() == 1;
+                    boolean hasMonetaryCost = in.readInt() == 1;
+                    boolean supportsAltitude = in.readInt() == 1;
+                    boolean supportsSpeed = in.readInt() == 1;
+                    boolean supportsBearing = in.readInt() == 1;
+                    int powerRequirement = in.readInt();
+                    int accuracy = in.readInt();
+                    return new ProviderProperties(requiresNetwork, requiresSatellite,
+                            requiresCell, hasMonetaryCost, supportsAltitude, supportsSpeed,
+                            supportsBearing,
+                            powerRequirement, accuracy);
+                }
+
+                @Override
+                public ProviderProperties[] newArray(int size) {
+                    return new ProviderProperties[size];
+                }
+            };
 
     @Override
     public int describeContents() {
@@ -149,4 +170,67 @@
         parcel.writeInt(mPowerRequirement);
         parcel.writeInt(mAccuracy);
     }
+
+    @Override
+    public String toString() {
+        StringBuilder b = new StringBuilder("ProviderProperties[");
+        b.append("power=").append(powerToString(mPowerRequirement)).append(", ");
+        b.append("accuracy=").append(accuracyToString(mAccuracy));
+        if (mRequiresNetwork || mRequiresSatellite || mRequiresCell) {
+            b.append(", requires=");
+            if (mRequiresNetwork) {
+                b.append("network,");
+            }
+            if (mRequiresSatellite) {
+                b.append("satellite,");
+            }
+            if (mRequiresCell) {
+                b.append("cell,");
+            }
+            b.setLength(b.length() - 1);
+        }
+        if (mHasMonetaryCost) {
+            b.append(", hasMonetaryCost");
+        }
+        if (mSupportsBearing || mSupportsSpeed || mSupportsAltitude) {
+            b.append(", supports=[");
+            if (mSupportsBearing) {
+                b.append("bearing, ");
+            }
+            if (mSupportsSpeed) {
+                b.append("speed, ");
+            }
+            if (mSupportsAltitude) {
+                b.append("altitude, ");
+            }
+            b.setLength(b.length() - 2);
+            b.append("]");
+        }
+        b.append("]");
+        return b.toString();
+    }
+
+    private static String powerToString(@PowerRequirement int power) {
+        switch (power) {
+            case Criteria.POWER_LOW:
+                return "Low";
+            case Criteria.POWER_MEDIUM:
+                return "Medium";
+            case Criteria.POWER_HIGH:
+                return "High";
+            default:
+                return "???";
+        }
+    }
+
+    private static String accuracyToString(@Accuracy int accuracy) {
+        switch (accuracy) {
+            case Criteria.ACCURACY_COARSE:
+                return "Coarse";
+            case Criteria.ACCURACY_FINE:
+                return "Fine";
+            default:
+                return "???";
+        }
+    }
 }
diff --git a/location/java/com/android/internal/location/ProviderRequest.java b/location/java/com/android/internal/location/ProviderRequest.java
index c23f499..8d8df45 100644
--- a/location/java/com/android/internal/location/ProviderRequest.java
+++ b/location/java/com/android/internal/location/ProviderRequest.java
@@ -20,33 +20,42 @@
 import android.location.LocationRequest;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.WorkSource;
 import android.util.TimeUtils;
 
+import com.android.internal.util.Preconditions;
+
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 /** @hide */
 public final class ProviderRequest implements Parcelable {
+
+    public static final ProviderRequest EMPTY_REQUEST = new ProviderRequest(false, Long.MAX_VALUE,
+            false, false,
+            Collections.emptyList(), new WorkSource());
+
     /** Location reporting is requested (true) */
     @UnsupportedAppUsage
-    public boolean reportLocation = false;
+    public final boolean reportLocation;
 
     /** The smallest requested interval */
     @UnsupportedAppUsage
-    public long interval = Long.MAX_VALUE;
+    public final long interval;
+
+    /**
+     * Whether provider shall make stronger than normal tradeoffs to substantially restrict power
+     * use.
+     */
+    public final boolean lowPowerMode;
 
     /**
      * When this flag is true, providers should ignore all location settings, user consents, power
      * restrictions or any other restricting factors and always satisfy this request to the best of
      * their ability. This flag should only be used in event of an emergency.
      */
-    public boolean locationSettingsIgnored = false;
-
-    /**
-     * Whether provider shall make stronger than normal tradeoffs to substantially restrict power
-     * use.
-     */
-    public boolean lowPowerMode = false;
+    public final boolean locationSettingsIgnored;
 
     /**
      * A more detailed set of requests.
@@ -56,26 +65,37 @@
      * low power fast interval request.
      */
     @UnsupportedAppUsage
-    public final List<LocationRequest> locationRequests = new ArrayList<>();
+    public final List<LocationRequest> locationRequests;
 
-    @UnsupportedAppUsage
-    public ProviderRequest() {
+    public final WorkSource workSource;
+
+    private ProviderRequest(boolean reportLocation, long interval, boolean lowPowerMode,
+            boolean locationSettingsIgnored, List<LocationRequest> locationRequests,
+            WorkSource workSource) {
+        this.reportLocation = reportLocation;
+        this.interval = interval;
+        this.lowPowerMode = lowPowerMode;
+        this.locationSettingsIgnored = locationSettingsIgnored;
+        this.locationRequests = Preconditions.checkNotNull(locationRequests);
+        this.workSource = Preconditions.checkNotNull(workSource);
     }
 
     public static final Parcelable.Creator<ProviderRequest> CREATOR =
             new Parcelable.Creator<ProviderRequest>() {
                 @Override
                 public ProviderRequest createFromParcel(Parcel in) {
-                    ProviderRequest request = new ProviderRequest();
-                    request.reportLocation = in.readInt() == 1;
-                    request.interval = in.readLong();
-                    request.lowPowerMode = in.readBoolean();
-                    request.locationSettingsIgnored = in.readBoolean();
+                    boolean reportLocation = in.readInt() == 1;
+                    long interval = in.readLong();
+                    boolean lowPowerMode = in.readBoolean();
+                    boolean locationSettingsIgnored = in.readBoolean();
                     int count = in.readInt();
+                    ArrayList<LocationRequest> locationRequests = new ArrayList<>(count);
                     for (int i = 0; i < count; i++) {
-                        request.locationRequests.add(LocationRequest.CREATOR.createFromParcel(in));
+                        locationRequests.add(LocationRequest.CREATOR.createFromParcel(in));
                     }
-                    return request;
+                    WorkSource workSource = in.readParcelable(null);
+                    return new ProviderRequest(reportLocation, interval, lowPowerMode,
+                            locationSettingsIgnored, locationRequests, workSource);
                 }
 
                 @Override
@@ -106,14 +126,13 @@
         StringBuilder s = new StringBuilder();
         s.append("ProviderRequest[");
         if (reportLocation) {
-            s.append("ON");
-            s.append(" interval=");
+            s.append("interval=");
             TimeUtils.formatDuration(interval, s);
             if (lowPowerMode) {
-                s.append(" lowPowerMode");
+                s.append(", lowPowerMode");
             }
             if (locationSettingsIgnored) {
-                s.append(" locationSettingsIgnored");
+                s.append(", locationSettingsIgnored");
             }
         } else {
             s.append("OFF");
@@ -121,4 +140,67 @@
         s.append(']');
         return s.toString();
     }
+
+    /**
+     * A Builder for {@link ProviderRequest}s.
+     */
+    public static class Builder {
+        private long mInterval = Long.MAX_VALUE;
+        private boolean mLowPowerMode;
+        private boolean mLocationSettingsIgnored;
+        private List<LocationRequest> mLocationRequests = Collections.emptyList();
+        private WorkSource mWorkSource = new WorkSource();
+
+        public long getInterval() {
+            return mInterval;
+        }
+
+        public void setInterval(long interval) {
+            this.mInterval = interval;
+        }
+
+        public boolean isLowPowerMode() {
+            return mLowPowerMode;
+        }
+
+        public void setLowPowerMode(boolean lowPowerMode) {
+            this.mLowPowerMode = lowPowerMode;
+        }
+
+        public boolean isLocationSettingsIgnored() {
+            return mLocationSettingsIgnored;
+        }
+
+        public void setLocationSettingsIgnored(boolean locationSettingsIgnored) {
+            this.mLocationSettingsIgnored = locationSettingsIgnored;
+        }
+
+        public List<LocationRequest> getLocationRequests() {
+            return mLocationRequests;
+        }
+
+        public void setLocationRequests(List<LocationRequest> locationRequests) {
+            this.mLocationRequests = Preconditions.checkNotNull(locationRequests);
+        }
+
+        public WorkSource getWorkSource() {
+            return mWorkSource;
+        }
+
+        public void setWorkSource(WorkSource workSource) {
+            mWorkSource = Preconditions.checkNotNull(workSource);
+        }
+
+        /**
+         * Builds a ProviderRequest object with the set information.
+         */
+        public ProviderRequest build() {
+            if (mInterval == Long.MAX_VALUE) {
+                return EMPTY_REQUEST;
+            } else {
+                return new ProviderRequest(true, mInterval, mLowPowerMode,
+                        mLocationSettingsIgnored, mLocationRequests, mWorkSource);
+            }
+        }
+    }
 }
diff --git a/media/java/android/media/IMediaRoute2Provider.aidl b/media/java/android/media/IMediaRoute2Provider.aidl
index 51fa4ee..28bf84d 100644
--- a/media/java/android/media/IMediaRoute2Provider.aidl
+++ b/media/java/android/media/IMediaRoute2Provider.aidl
@@ -24,13 +24,12 @@
  */
 oneway interface IMediaRoute2Provider {
     void setClient(IMediaRoute2ProviderClient client);
-    void requestCreateSession(String packageName, String routeId,
-            String routeType, long requestId);
-    void releaseSession(int sessionId);
+    void requestCreateSession(String packageName, String routeId, String routeType, long requestId);
+    void releaseSession(String sessionId);
 
-    void selectRoute(int sessionId, String routeId);
-    void deselectRoute(int sessionId, String routeId);
-    void transferToRoute(int sessionId, String routeId);
+    void selectRoute(String sessionId, String routeId);
+    void deselectRoute(String sessionId, String routeId);
+    void transferToRoute(String sessionId, String routeId);
 
     void notifyControlRequestSent(String id, in Intent request);
     void requestSetVolume(String id, int volume);
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 13640a4..1ed53d9 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -16,6 +16,8 @@
 
 package android.media;
 
+import static android.media.MediaRouter2Utils.toUniqueId;
+
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -191,13 +193,6 @@
     }
 
     /**
-     * @hide
-     */
-    public static String toUniqueId(String providerId, String routeId) {
-        return providerId + ":" + routeId;
-    }
-
-    /**
      * Returns true if the route info has all of the required field.
      * A route info only obtained from {@link com.android.server.media.MediaRouterService}
      * is valid.
diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java
index 91cc448..24b65ba 100644
--- a/media/java/android/media/MediaRoute2ProviderService.java
+++ b/media/java/android/media/MediaRoute2ProviderService.java
@@ -29,6 +29,7 @@
 import android.os.Looper;
 import android.os.Process;
 import android.os.RemoteException;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
 
@@ -55,7 +56,7 @@
     private MediaRoute2ProviderInfo mProviderInfo;
 
     @GuardedBy("mSessionLock")
-    private ArrayMap<Integer, RouteSessionInfo> mSessionInfo = new ArrayMap<>();
+    private ArrayMap<String, RouteSessionInfo> mSessionInfo = new ArrayMap<>();
 
     public MediaRoute2ProviderService() {
         mHandler = new Handler(Looper.getMainLooper());
@@ -106,7 +107,10 @@
      *         null if the session is destroyed or id is not valid.
      */
     @Nullable
-    public final RouteSessionInfo getSessionInfo(int sessionId) {
+    public final RouteSessionInfo getSessionInfo(@NonNull String sessionId) {
+        if (TextUtils.isEmpty(sessionId)) {
+            throw new IllegalArgumentException("sessionId must not be empty");
+        }
         synchronized (mSessionLock) {
             return mSessionInfo.get(sessionId);
         }
@@ -134,7 +138,7 @@
      */
     public final void updateSessionInfo(@NonNull RouteSessionInfo sessionInfo) {
         Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
-        int sessionId = sessionInfo.getSessionId();
+        String sessionId = sessionInfo.getId();
         if (sessionInfo.getSelectedRoutes().isEmpty()) {
             releaseSession(sessionId);
             return;
@@ -160,7 +164,7 @@
     public final void notifySessionInfoChanged(@NonNull RouteSessionInfo sessionInfo) {
         Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
 
-        int sessionId = sessionInfo.getSessionId();
+        String sessionId = sessionInfo.getId();
         synchronized (mSessionLock) {
             if (mSessionInfo.containsKey(sessionId)) {
                 mSessionInfo.put(sessionId, sessionInfo);
@@ -185,7 +189,7 @@
      * controlled, pass a {@link Bundle} that contains how to control it.
      *
      * @param sessionInfo information of the new session.
-     *                    The {@link RouteSessionInfo#getSessionId() id} of the session must be
+     *                    The {@link RouteSessionInfo#getId() id} of the session must be
      *                    unique. Pass {@code null} to reject the request or inform clients that
      *                    session creation is failed.
      * @param requestId id of the previous request to create this session
@@ -194,13 +198,13 @@
     // TODO: Maybe better to create notifySessionCreationFailed?
     public final void notifySessionCreated(@Nullable RouteSessionInfo sessionInfo, long requestId) {
         if (sessionInfo != null) {
-            int sessionId = sessionInfo.getSessionId();
+            String sessionId = sessionInfo.getId();
             synchronized (mSessionLock) {
                 if (mSessionInfo.containsKey(sessionId)) {
                     Log.w(TAG, "Ignoring duplicate session id.");
                     return;
                 }
-                mSessionInfo.put(sessionInfo.getSessionId(), sessionInfo);
+                mSessionInfo.put(sessionInfo.getId(), sessionInfo);
             }
             schedulePublishState();
         }
@@ -220,9 +224,12 @@
      * {@link #onDestroySession} is called if the session is released.
      *
      * @param sessionId id of the session to be released
-     * @see #onDestroySession(int, RouteSessionInfo)
+     * @see #onDestroySession(String, RouteSessionInfo)
      */
-    public final void releaseSession(int sessionId) {
+    public final void releaseSession(@NonNull String sessionId) {
+        if (TextUtils.isEmpty(sessionId)) {
+            throw new IllegalArgumentException("sessionId must not be empty");
+        }
         //TODO: notify media router service of release.
         RouteSessionInfo sessionInfo;
         synchronized (mSessionLock) {
@@ -259,9 +266,10 @@
      *
      * @param sessionId id of the session being destroyed.
      * @param lastSessionInfo information of the session being destroyed.
-     * @see #releaseSession(int)
+     * @see #releaseSession(String)
      */
-    public abstract void onDestroySession(int sessionId, @NonNull RouteSessionInfo lastSessionInfo);
+    public abstract void onDestroySession(@NonNull String sessionId,
+            @NonNull RouteSessionInfo lastSessionInfo);
 
     //TODO: make a way to reject the request
     /**
@@ -274,7 +282,7 @@
      * @param routeId id of the route
      * @see #updateSessionInfo(RouteSessionInfo)
      */
-    public abstract void onSelectRoute(int sessionId, @NonNull String routeId);
+    public abstract void onSelectRoute(@NonNull String sessionId, @NonNull String routeId);
 
     //TODO: make a way to reject the request
     /**
@@ -286,7 +294,7 @@
      * @param sessionId id of the session
      * @param routeId id of the route
      */
-    public abstract void onDeselectRoute(int sessionId, @NonNull String routeId);
+    public abstract void onDeselectRoute(@NonNull String sessionId, @NonNull String routeId);
 
     //TODO: make a way to reject the request
     /**
@@ -298,7 +306,7 @@
      * @param sessionId id of the session
      * @param routeId id of the route
      */
-    public abstract void onTransferToRoute(int sessionId, @NonNull String routeId);
+    public abstract void onTransferToRoute(@NonNull String sessionId, @NonNull String routeId);
 
     /**
      * Called when the {@link RouteDiscoveryRequest discovery request} has changed.
@@ -385,37 +393,53 @@
                     requestId));
         }
         @Override
-        public void releaseSession(int sessionId) {
+        public void releaseSession(@NonNull String sessionId) {
             if (!checkCallerisSystem()) {
                 return;
             }
+            if (TextUtils.isEmpty(sessionId)) {
+                Log.w(TAG, "releaseSession: Ignoring empty sessionId from system service.");
+                return;
+            }
             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::releaseSession,
                     MediaRoute2ProviderService.this, sessionId));
         }
 
         @Override
-        public void selectRoute(int sessionId, String routeId) {
+        public void selectRoute(@NonNull String sessionId, String routeId) {
             if (!checkCallerisSystem()) {
                 return;
             }
+            if (TextUtils.isEmpty(sessionId)) {
+                Log.w(TAG, "selectRoute: Ignoring empty sessionId from system service.");
+                return;
+            }
             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSelectRoute,
                     MediaRoute2ProviderService.this, sessionId, routeId));
         }
 
         @Override
-        public void deselectRoute(int sessionId, String routeId) {
+        public void deselectRoute(@NonNull String sessionId, String routeId) {
             if (!checkCallerisSystem()) {
                 return;
             }
+            if (TextUtils.isEmpty(sessionId)) {
+                Log.w(TAG, "deselectRoute: Ignoring empty sessionId from system service.");
+                return;
+            }
             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onDeselectRoute,
                     MediaRoute2ProviderService.this, sessionId, routeId));
         }
 
         @Override
-        public void transferToRoute(int sessionId, String routeId) {
+        public void transferToRoute(@NonNull String sessionId, String routeId) {
             if (!checkCallerisSystem()) {
                 return;
             }
+            if (TextUtils.isEmpty(sessionId)) {
+                Log.w(TAG, "transferToRoute: Ignoring empty sessionId from system service.");
+                return;
+            }
             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onTransferToRoute,
                     MediaRoute2ProviderService.this, sessionId, routeId));
         }
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index dea8b04..8ebf617 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -342,8 +342,7 @@
         final int requestId;
         requestId = mSessionCreationRequestCnt.getAndIncrement();
 
-        SessionCreationRequest request = new SessionCreationRequest(
-                requestId, route, routeType);
+        SessionCreationRequest request = new SessionCreationRequest(requestId, route, routeType);
         mSessionCreationRequests.add(request);
 
         Client2 client;
@@ -352,8 +351,7 @@
         }
         if (client != null) {
             try {
-                mMediaRouterService.requestCreateSession(
-                        client, route, routeType, requestId);
+                mMediaRouterService.requestCreateSession(client, route, routeType, requestId);
             } catch (RemoteException ex) {
                 Log.e(TAG, "Unable to request to create session.", ex);
                 mHandler.sendMessage(obtainMessage(MediaRouter2::createControllerOnHandler,
@@ -542,7 +540,7 @@
         if (sessionInfo != null) {
             RouteSessionController controller = new RouteSessionController(sessionInfo);
             synchronized (sRouterLock) {
-                mSessionControllers.put(controller.getUniqueSessionId(), controller);
+                mSessionControllers.put(controller.getSessionId(), controller);
             }
             notifySessionCreated(controller);
         }
@@ -556,12 +554,12 @@
 
         RouteSessionController matchingController;
         synchronized (sRouterLock) {
-            matchingController = mSessionControllers.get(sessionInfo.getUniqueSessionId());
+            matchingController = mSessionControllers.get(sessionInfo.getId());
         }
 
         if (matchingController == null) {
             Log.w(TAG, "changeSessionInfoOnHandler: Matching controller not found. uniqueSessionId="
-                    + sessionInfo.getUniqueSessionId());
+                    + sessionInfo.getId());
             return;
         }
 
@@ -582,7 +580,7 @@
             return;
         }
 
-        final String uniqueSessionId = sessionInfo.getUniqueSessionId();
+        final String uniqueSessionId = sessionInfo.getId();
         RouteSessionController matchingController;
         synchronized (sRouterLock) {
             matchingController = mSessionControllers.get(uniqueSessionId);
@@ -591,7 +589,7 @@
         if (matchingController == null) {
             if (DEBUG) {
                 Log.d(TAG, "releaseControllerOnHandler: Matching controller not found. "
-                        + "uniqueSessionId=" + sessionInfo.getUniqueSessionId());
+                        + "uniqueSessionId=" + sessionInfo.getId());
             }
             return;
         }
@@ -783,20 +781,9 @@
         /**
          * @return the ID of the session
          */
-        public int getSessionId() {
+        public String getSessionId() {
             synchronized (mControllerLock) {
-                return mSessionInfo.getSessionId();
-            }
-        }
-
-        /**
-         * @return the unique ID of the session
-         * @hide
-         */
-        @NonNull
-        public String getUniqueSessionId() {
-            synchronized (mControllerLock) {
-                return mSessionInfo.getUniqueSessionId();
+                return mSessionInfo.getId();
             }
         }
 
@@ -913,7 +900,7 @@
             }
             if (client != null) {
                 try {
-                    mMediaRouterService.selectRoute(client, getUniqueSessionId(), route);
+                    mMediaRouterService.selectRoute(client, getSessionId(), route);
                 } catch (RemoteException ex) {
                     Log.e(TAG, "Unable to select route for session.", ex);
                 }
@@ -960,7 +947,7 @@
             }
             if (client != null) {
                 try {
-                    mMediaRouterService.deselectRoute(client, getUniqueSessionId(), route);
+                    mMediaRouterService.deselectRoute(client, getSessionId(), route);
                 } catch (RemoteException ex) {
                     Log.e(TAG, "Unable to remove route from session.", ex);
                 }
@@ -1008,7 +995,7 @@
             }
             if (client != null) {
                 try {
-                    mMediaRouterService.transferToRoute(client, getUniqueSessionId(), route);
+                    mMediaRouterService.transferToRoute(client, getSessionId(), route);
                 } catch (RemoteException ex) {
                     Log.e(TAG, "Unable to transfer to route for session.", ex);
                 }
@@ -1033,12 +1020,12 @@
 
             Client2 client;
             synchronized (sRouterLock) {
-                mSessionControllers.remove(getUniqueSessionId(), this);
+                mSessionControllers.remove(getSessionId(), this);
                 client = mClient;
             }
             if (client != null) {
                 try {
-                    mMediaRouterService.releaseSession(client, getUniqueSessionId());
+                    mMediaRouterService.releaseSession(client, getSessionId());
                 } catch (RemoteException ex) {
                     Log.e(TAG, "Unable to notify of controller release", ex);
                 }
@@ -1068,6 +1055,7 @@
 
             List<MediaRoute2Info> routes = new ArrayList<>();
             synchronized (sRouterLock) {
+                // TODO: Maybe able to change using Collection.stream()?
                 for (String routeId : routeIds) {
                     MediaRoute2Info route = mRoutes.get(routeId);
                     if (route != null) {
diff --git a/media/java/android/media/MediaRouter2Utils.java b/media/java/android/media/MediaRouter2Utils.java
new file mode 100644
index 0000000..4904582
--- /dev/null
+++ b/media/java/android/media/MediaRouter2Utils.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2020 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 android.media;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.text.TextUtils;
+import android.util.Log;
+
+/**
+ * @hide
+ */
+public class MediaRouter2Utils {
+
+    static final String TAG = "MR2Utils";
+    static final String SEPARATOR = ":";
+
+    /**
+     * @hide
+     */
+    @NonNull
+    public static String toUniqueId(@NonNull String providerId, @NonNull String id) {
+        if (TextUtils.isEmpty(providerId)) {
+            Log.w(TAG, "toUniqueId: providerId shouldn't be empty");
+            return null;
+        }
+        if (TextUtils.isEmpty(id)) {
+            Log.w(TAG, "toUniqueId: id shouldn't be null");
+            return null;
+        }
+
+        return providerId + SEPARATOR + id;
+    }
+
+    /**
+     * Gets provider ID from unique ID.
+     * If the corresponding provider ID could not be generated, it will return null.
+     *
+     * @hide
+     */
+    @Nullable
+    public static String getProviderId(@NonNull String uniqueId) {
+        if (TextUtils.isEmpty(uniqueId)) {
+            Log.w(TAG, "getProviderId: uniqueId shouldn't be empty");
+            return null;
+        }
+
+        int firstIndexOfSeparator = uniqueId.indexOf(SEPARATOR);
+        if (firstIndexOfSeparator == -1) {
+            return null;
+        }
+
+        String providerId = uniqueId.substring(0, firstIndexOfSeparator);
+        if (TextUtils.isEmpty(providerId)) {
+            return null;
+        }
+
+        return providerId;
+    }
+
+    /**
+     * Gets the original ID (i.e. non-unique route/session ID) from unique ID.
+     * If the corresponding ID could not be generated, it will return null.
+     *
+     * @hide
+     */
+    @Nullable
+    public static String getOriginalId(@NonNull String uniqueId) {
+        if (TextUtils.isEmpty(uniqueId)) {
+            Log.w(TAG, "getOriginalId: uniqueId shouldn't be empty");
+            return null;
+        }
+
+        int firstIndexOfSeparator = uniqueId.indexOf(SEPARATOR);
+        if (firstIndexOfSeparator == -1 || firstIndexOfSeparator + 1 >= uniqueId.length()) {
+            return null;
+        }
+
+        String providerId = uniqueId.substring(firstIndexOfSeparator + 1);
+        if (TextUtils.isEmpty(providerId)) {
+            return null;
+        }
+
+        return providerId;
+    }
+}
diff --git a/media/java/android/media/RouteSessionInfo.java b/media/java/android/media/RouteSessionInfo.java
index cb168860..5330630 100644
--- a/media/java/android/media/RouteSessionInfo.java
+++ b/media/java/android/media/RouteSessionInfo.java
@@ -22,6 +22,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
+import android.util.Log;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -33,6 +34,7 @@
  * @hide
  */
 public class RouteSessionInfo implements Parcelable {
+
     @NonNull
     public static final Creator<RouteSessionInfo> CREATOR =
             new Creator<RouteSessionInfo>() {
@@ -46,8 +48,10 @@
                 }
             };
 
-    final int mSessionId;
-    final String mPackageName;
+    public static final String TAG = "RouteSessionInfo";
+
+    final String mId;
+    final String mClientPackageName;
     final String mRouteType;
     @Nullable
     final String mProviderId;
@@ -61,15 +65,19 @@
     RouteSessionInfo(@NonNull Builder builder) {
         Objects.requireNonNull(builder, "builder must not be null.");
 
-        mSessionId = builder.mSessionId;
-        mPackageName = builder.mPackageName;
+        mId = builder.mId;
+        mClientPackageName = builder.mClientPackageName;
         mRouteType = builder.mRouteType;
         mProviderId = builder.mProviderId;
 
-        mSelectedRoutes = Collections.unmodifiableList(builder.mSelectedRoutes);
-        mSelectableRoutes = Collections.unmodifiableList(builder.mSelectableRoutes);
-        mDeselectableRoutes = Collections.unmodifiableList(builder.mDeselectableRoutes);
-        mTransferrableRoutes = Collections.unmodifiableList(builder.mTransferrableRoutes);
+        mSelectedRoutes = Collections.unmodifiableList(
+                convertToUniqueRouteIds(builder.mSelectedRoutes));
+        mSelectableRoutes = Collections.unmodifiableList(
+                convertToUniqueRouteIds(builder.mSelectableRoutes));
+        mDeselectableRoutes = Collections.unmodifiableList(
+                convertToUniqueRouteIds(builder.mDeselectableRoutes));
+        mTransferrableRoutes = Collections.unmodifiableList(
+                convertToUniqueRouteIds(builder.mTransferrableRoutes));
 
         mControlHints = builder.mControlHints;
     }
@@ -77,8 +85,8 @@
     RouteSessionInfo(@NonNull Parcel src) {
         Objects.requireNonNull(src, "src must not be null.");
 
-        mSessionId = src.readInt();
-        mPackageName = ensureString(src.readString());
+        mId = ensureString(src.readString());
+        mClientPackageName = ensureString(src.readString());
         mRouteType = ensureString(src.readString());
         mProviderId = src.readString();
 
@@ -105,73 +113,50 @@
     }
 
     /**
-     * Gets non-unique session id (int) from unique session id (string).
-     * If the corresponding session id could not be generated, it will return null.
-     * @hide
-     */
-    @Nullable
-    public static Integer getSessionId(@NonNull String uniqueSessionId) {
-        int lastIndexOfSeparator = uniqueSessionId.lastIndexOf("/");
-        if (lastIndexOfSeparator == -1 || lastIndexOfSeparator + 1 >= uniqueSessionId.length()) {
-            return null;
-        }
-
-        String integerString = uniqueSessionId.substring(lastIndexOfSeparator + 1);
-        if (TextUtils.isEmpty(integerString)) {
-            return null;
-        }
-
-        try {
-            return Integer.parseInt(integerString);
-        } catch (NumberFormatException ex) {
-            return null;
-        }
-    }
-
-    /**
-     * Gets provider ID (string) from unique session id (string).
-     * If the corresponding provider ID could not be generated, it will return null.
-     * @hide
-     *
-     * TODO: This logic seems error-prone. Consider to use long uniqueId.
-     */
-    @Nullable
-    public static String getProviderId(@NonNull String uniqueSessionId) {
-        int lastIndexOfSeparator = uniqueSessionId.lastIndexOf("/");
-        if (lastIndexOfSeparator == -1) {
-            return null;
-        }
-
-        String result = uniqueSessionId.substring(0, lastIndexOfSeparator);
-        if (TextUtils.isEmpty(result)) {
-            return null;
-        }
-        return result;
-    }
-
-    /**
      * Returns whether the session info is valid or not
+     *
+     * TODO in this CL: Remove this method.
      */
     public boolean isValid() {
-        return !TextUtils.isEmpty(mPackageName)
+        return !TextUtils.isEmpty(mId)
+                && !TextUtils.isEmpty(mClientPackageName)
                 && !TextUtils.isEmpty(mRouteType)
                 && mSelectedRoutes.size() > 0;
     }
 
     /**
-     * Gets the id of the session
+     * Gets the id of the session. The sessions which are given by {@link MediaRouter2} will have
+     * unique IDs.
+     * <p>
+     * In order to ensure uniqueness in {@link MediaRouter2} side, the value of this method
+     * can be different from what was set in {@link MediaRoute2ProviderService}.
+     *
+     * @see Builder#Builder(String, String, String)
      */
     @NonNull
-    public int getSessionId() {
-        return mSessionId;
+    public String getId() {
+        if (mProviderId != null) {
+            return MediaRouter2Utils.toUniqueId(mProviderId, mId);
+        } else {
+            return mId;
+        }
+    }
+
+    /**
+     * Gets the original id set by {@link Builder#Builder(String, String, String)}.
+     * @hide
+     */
+    @NonNull
+    public String getOriginalId() {
+        return mId;
     }
 
     /**
      * Gets the client package name of the session
      */
     @NonNull
-    public String getPackageName() {
-        return mPackageName;
+    public String getClientPackageName() {
+        return mClientPackageName;
     }
 
     /**
@@ -193,19 +178,6 @@
     }
 
     /**
-     * Gets the unique id of the session.
-     * @hide
-     */
-    @NonNull
-    public String getUniqueSessionId() {
-        StringBuilder sessionIdBuilder = new StringBuilder()
-                .append(mProviderId)
-                .append("/")
-                .append(mSessionId);
-        return sessionIdBuilder.toString();
-    }
-
-    /**
      * Gets the list of ids of selected routes for the session. It shouldn't be empty.
      */
     @NonNull
@@ -252,8 +224,8 @@
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeInt(mSessionId);
-        dest.writeString(mPackageName);
+        dest.writeString(mId);
+        dest.writeString(mClientPackageName);
         dest.writeString(mRouteType);
         dest.writeString(mProviderId);
         dest.writeStringList(mSelectedRoutes);
@@ -267,7 +239,7 @@
     public String toString() {
         StringBuilder result = new StringBuilder()
                 .append("RouteSessionInfo{ ")
-                .append("sessionId=").append(mSessionId)
+                .append("sessionId=").append(mId)
                 .append(", routeType=").append(mRouteType)
                 .append(", selectedRoutes={")
                 .append(String.join(",", mSelectedRoutes))
@@ -285,12 +257,30 @@
         return result.toString();
     }
 
+    private List<String> convertToUniqueRouteIds(@NonNull List<String> routeIds) {
+        if (routeIds == null) {
+            Log.w(TAG, "routeIds is null. Returning an empty list");
+            return Collections.emptyList();
+        }
+
+        // mProviderId can be null if not set. Return the original list for this case.
+        if (mProviderId == null) {
+            return routeIds;
+        }
+
+        List<String> result = new ArrayList<>();
+        for (String routeId : routeIds) {
+            result.add(MediaRouter2Utils.toUniqueId(mProviderId, routeId));
+        }
+        return result;
+    }
+
     /**
      * Builder class for {@link RouteSessionInfo}.
      */
     public static final class Builder {
-        final String mPackageName;
-        final int mSessionId;
+        final String mId;
+        final String mClientPackageName;
         final String mRouteType;
         String mProviderId;
         final List<String> mSelectedRoutes;
@@ -299,22 +289,42 @@
         final List<String> mTransferrableRoutes;
         Bundle mControlHints;
 
-        public Builder(int sessionId, @NonNull String packageName,
+        /**
+         * Constructor for builder to create {@link RouteSessionInfo}.
+         * <p>
+         * In order to ensure ID uniqueness in {@link MediaRouter2} side, the value of
+         * {@link RouteSessionInfo#getId()} can be different from what was set in
+         * {@link MediaRoute2ProviderService}.
+         * </p>
+         *
+         * @see MediaRoute2Info#getId()
+         */
+        public Builder(@NonNull String id, @NonNull String clientPackageName,
                 @NonNull String routeType) {
-            mSessionId = sessionId;
-            mPackageName = Objects.requireNonNull(packageName, "packageName must not be null");
-            mRouteType = Objects.requireNonNull(routeType,
-                    "routeType must not be null");
-
+            if (TextUtils.isEmpty(id)) {
+                throw new IllegalArgumentException("id must not be empty");
+            }
+            mId = id;
+            mClientPackageName = Objects.requireNonNull(
+                    clientPackageName, "clientPackageName must not be null");
+            mRouteType = Objects.requireNonNull(routeType, "routeType must not be null");
             mSelectedRoutes = new ArrayList<>();
             mSelectableRoutes = new ArrayList<>();
             mDeselectableRoutes = new ArrayList<>();
             mTransferrableRoutes = new ArrayList<>();
         }
 
-        public Builder(RouteSessionInfo sessionInfo) {
-            mSessionId = sessionInfo.mSessionId;
-            mPackageName = sessionInfo.mPackageName;
+        /**
+         * Constructor for builder to create {@link RouteSessionInfo} with
+         * existing {@link RouteSessionInfo} instance.
+         *
+         * @param sessionInfo the existing instance to copy data from.
+         */
+        public Builder(@NonNull RouteSessionInfo sessionInfo) {
+            Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
+
+            mId = sessionInfo.mId;
+            mClientPackageName = sessionInfo.mClientPackageName;
             mRouteType = sessionInfo.mRouteType;
             mProviderId = sessionInfo.mProviderId;
 
@@ -334,21 +344,12 @@
          * @hide
          */
         @NonNull
-        public Builder setProviderId(String providerId) {
-            mProviderId = providerId;
-            convertToUniqueRouteIds(providerId, mSelectedRoutes);
-            convertToUniqueRouteIds(providerId, mSelectableRoutes);
-            convertToUniqueRouteIds(providerId, mDeselectableRoutes);
-            convertToUniqueRouteIds(providerId, mTransferrableRoutes);
-            return this;
-        }
-
-        private void convertToUniqueRouteIds(@NonNull String providerId,
-                @NonNull List<String> routeIds) {
-            for (int i = 0; i < routeIds.size(); i++) {
-                String routeId = routeIds.get(i);
-                routeIds.set(i, MediaRoute2Info.toUniqueId(providerId, routeId));
+        public Builder setProviderId(@NonNull String providerId) {
+            if (TextUtils.isEmpty(providerId)) {
+                throw new IllegalArgumentException("providerId must not be empty");
             }
+            mProviderId = providerId;
+            return this;
         }
 
         /**
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index aff7257..aece39d 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -940,16 +940,15 @@
         /**
          * Called when a media key event is dispatched through the media session service. The
          * session token can be {@link null} if the framework has sent the media key event to the
-         * media button receiver to revive the media app's playback.
-         *
-         * the session is dead when , but the framework sent
+         * media button receiver to revive the media app's playback after the corresponding session
+         * is released.
          *
          * @param event Dispatched media key event.
          * @param packageName Package
          * @param sessionToken The media session's token. Can be {@code null}.
          */
         default void onMediaKeyEventDispatched(@NonNull KeyEvent event, @NonNull String packageName,
-                @NonNull MediaSession.Token sessionToken) { }
+                @Nullable MediaSession.Token sessionToken) { }
     }
 
     /**
diff --git a/media/java/android/media/soundtrigger/SoundTriggerManager.java b/media/java/android/media/soundtrigger/SoundTriggerManager.java
index 1c38301..938ffcd 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerManager.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerManager.java
@@ -428,8 +428,7 @@
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
     public int setParameter(@Nullable UUID soundModelId,
-            @ModelParams int modelParam, int value)
-            throws UnsupportedOperationException, IllegalArgumentException {
+            @ModelParams int modelParam, int value) {
         try {
             return mSoundTriggerService.setParameter(new ParcelUuid(soundModelId), modelParam,
                     value);
@@ -449,15 +448,10 @@
      * @param soundModelId UUID of model to get parameter
      * @param modelParam   {@link ModelParams}
      * @return value of parameter
-     * @throws UnsupportedOperationException if hal or model do not support this API.
-     *         {@link SoundTriggerManager#queryParameter} should be checked first.
-     * @throws IllegalArgumentException if invalid model handle or parameter is passed.
-     *         {@link SoundTriggerManager#queryParameter} should be checked first.
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
     public int getParameter(@NonNull UUID soundModelId,
-            @ModelParams int modelParam)
-            throws UnsupportedOperationException, IllegalArgumentException {
+            @ModelParams int modelParam) {
         try {
             return mSoundTriggerService.getParameter(new ParcelUuid(soundModelId), modelParam);
         } catch (RemoteException e) {
@@ -479,8 +473,7 @@
     public ModelParamRange queryParameter(@Nullable UUID soundModelId,
             @ModelParams int modelParam) {
         try {
-            return mSoundTriggerService.queryParameter(new ParcelUuid(soundModelId),
-                    modelParam);
+            return mSoundTriggerService.queryParameter(new ParcelUuid(soundModelId), modelParam);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl b/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl
index 1a3b402..909f1a00 100644
--- a/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl
+++ b/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl
@@ -30,6 +30,14 @@
      * Unique implementation ID. The UUID must change with each version of
        the engine implementation */
     String     uuid;
+    /**
+     * String naming the architecture used for running the supported models.
+     * (eg. a platform running models on a DSP could implement this string to convey the DSP
+     * architecture used)
+     * This property is supported for soundtrigger HAL v2.3 and above.
+     * If running a previous version, the string will be empty.
+     */
+    String supportedModelArch;
     /** Maximum number of concurrent sound models loaded */
     int maxSoundModels;
     /** Maximum number of key phrases */
diff --git a/media/java/android/media/soundtrigger_middleware/Status.aidl b/media/java/android/media/soundtrigger_middleware/Status.aidl
index d8f9d8f..5d082e2 100644
--- a/media/java/android/media/soundtrigger_middleware/Status.aidl
+++ b/media/java/android/media/soundtrigger_middleware/Status.aidl
@@ -26,4 +26,6 @@
     RESOURCE_CONTENTION = 1,
     /** Operation is not supported in this implementation. This is a permanent condition. */
     OPERATION_NOT_SUPPORTED = 2,
+    /** Temporary lack of permission. */
+    TEMPORARY_PERMISSION_DENIED = 3,
 }
diff --git a/media/java/android/media/tv/tuner/Descrambler.java b/media/java/android/media/tv/tuner/Descrambler.java
new file mode 100644
index 0000000..f9f7a22
--- /dev/null
+++ b/media/java/android/media/tv/tuner/Descrambler.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2019 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 android.media.tv.tuner;
+
+import android.annotation.Nullable;
+import android.media.tv.tuner.Tuner.Filter;
+import android.media.tv.tuner.TunerConstants.DemuxPidType;
+
+/**
+ * This class is used to interact with descramblers.
+ *
+ * <p> Descrambler is a hardware component used to descramble data.
+ *
+ * <p> This class controls the TIS interaction with Tuner HAL.
+ * @hide
+ */
+public class Descrambler implements AutoCloseable {
+    private long mNativeContext;
+
+    private native int nativeAddPid(int pidType, int pid, Filter filter);
+    private native int nativeRemovePid(int pidType, int pid, Filter filter);
+    private native int nativeSetKeyToken(byte[] keyToken);
+    private native int nativeClose();
+
+    private Descrambler() {}
+
+    /**
+     * Add packets' PID to the descrambler for descrambling.
+     *
+     * The descrambler will start descrambling packets with this PID. Multiple PIDs can be added
+     * into one descrambler instance because descambling can happen simultaneously on packets
+     * from different PIDs.
+     *
+     * If the Descrambler previously contained a filter for the PID, the old filter is replaced
+     * by the specified filter.
+     *
+     * @param pidType the type of the PID.
+     * @param pid the PID of packets to start to be descrambled.
+     * @param filter an optional filter instance to identify upper stream.
+     * @return result status of the operation.
+     *
+     * @hide
+     */
+    public int addPid(@DemuxPidType int pidType, int pid, @Nullable Filter filter) {
+        return nativeAddPid(pidType, pid, filter);
+    }
+
+    /**
+     * Remove packets' PID from the descrambler
+     *
+     * The descrambler will stop descrambling packets with this PID.
+     *
+     * @param pidType the type of the PID.
+     * @param pid the PID of packets to stop to be descrambled.
+     * @param filter an optional filter instance to identify upper stream.
+     * @return result status of the operation.
+     *
+     * @hide
+     */
+    public int removePid(@DemuxPidType int pidType, int pid, @Nullable Filter filter) {
+        return nativeRemovePid(pidType, pid, filter);
+    }
+
+    /**
+     * Set a key token to link descrambler to a key slot
+     *
+     * A descrambler instance can have only one key slot to link, but a key slot can hold a few
+     * keys for different purposes.
+     *
+     * @param keyToken the token to be used to link the key slot.
+     * @return result status of the operation.
+     *
+     * @hide
+     */
+    public int setKeyToken(byte[] keyToken) {
+        return nativeSetKeyToken(keyToken);
+    }
+
+    /**
+     * Release the descrambler instance.
+     *
+     * @hide
+     */
+    @Override
+    public void close() {
+        nativeClose();
+    }
+
+}
diff --git a/media/java/android/media/tv/tuner/Dvr.java b/media/java/android/media/tv/tuner/Dvr.java
new file mode 100644
index 0000000..0bfba8f
--- /dev/null
+++ b/media/java/android/media/tv/tuner/Dvr.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2019 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 android.media.tv.tuner;
+
+import android.annotation.NonNull;
+import android.media.tv.tuner.Tuner.DvrCallback;
+import android.media.tv.tuner.Tuner.Filter;
+import android.os.ParcelFileDescriptor;
+
+/** @hide */
+public class Dvr {
+    private long mNativeContext;
+    private DvrCallback mCallback;
+
+    private native int nativeAttachFilter(Filter filter);
+    private native int nativeDetachFilter(Filter filter);
+    private native int nativeConfigureDvr(DvrSettings settings);
+    private native int nativeStartDvr();
+    private native int nativeStopDvr();
+    private native int nativeFlushDvr();
+    private native int nativeClose();
+    private native void nativeSetFileDescriptor(int fd);
+    private native int nativeRead(int size);
+    private native int nativeRead(byte[] bytes, int offset, int size);
+    private native int nativeWrite(int size);
+    private native int nativeWrite(byte[] bytes, int offset, int size);
+
+    private Dvr() {}
+
+    /**
+     * Attaches a filter to DVR interface for recording.
+     *
+     * @param filter the filter to be attached.
+     * @return result status of the operation.
+     */
+    public int attachFilter(Filter filter) {
+        return nativeAttachFilter(filter);
+    }
+
+    /**
+     * Detaches a filter from DVR interface.
+     *
+     * @param filter the filter to be detached.
+     * @return result status of the operation.
+     */
+    public int detachFilter(Filter filter) {
+        return nativeDetachFilter(filter);
+    }
+
+    /**
+     * Configures the DVR.
+     *
+     * @param settings the settings of the DVR interface.
+     * @return result status of the operation.
+     */
+    public int configure(DvrSettings settings) {
+        return nativeConfigureDvr(settings);
+    }
+
+    /**
+     * Starts DVR.
+     *
+     * Starts consuming playback data or producing data for recording.
+     *
+     * @return result status of the operation.
+     */
+    public int start() {
+        return nativeStartDvr();
+    }
+
+    /**
+     * Stops DVR.
+     *
+     * Stops consuming playback data or producing data for recording.
+     *
+     * @return result status of the operation.
+     */
+    public int stop() {
+        return nativeStopDvr();
+    }
+
+    /**
+     * Flushed DVR data.
+     *
+     * @return result status of the operation.
+     */
+    public int flush() {
+        return nativeFlushDvr();
+    }
+
+    /**
+     * closes the DVR instance to release resources.
+     *
+     * @return result status of the operation.
+     */
+    public int close() {
+        return nativeClose();
+    }
+
+    /**
+     * Sets file descriptor to read/write data.
+     */
+    public void setFileDescriptor(ParcelFileDescriptor fd) {
+        nativeSetFileDescriptor(fd.getFd());
+    }
+
+    /**
+     * Reads data from the file for DVR playback.
+     */
+    public int read(int size) {
+        return nativeRead(size);
+    }
+
+    /**
+     * Reads data from the buffer for DVR playback.
+     */
+    public int read(@NonNull byte[] bytes, int offset, int size) {
+        if (size + offset > bytes.length) {
+            throw new ArrayIndexOutOfBoundsException(
+                    "Array length=" + bytes.length + ", offset=" + offset + ", size=" + size);
+        }
+        return nativeRead(bytes, offset, size);
+    }
+
+    /**
+     * Writes recording data to file.
+     */
+    public int write(int size) {
+        return nativeWrite(size);
+    }
+
+    /**
+     * Writes recording data to buffer.
+     */
+    public int write(@NonNull byte[] bytes, int offset, int size) {
+        return nativeWrite(bytes, offset, size);
+    }
+}
diff --git a/media/java/android/media/tv/tuner/Filter.java b/media/java/android/media/tv/tuner/Filter.java
new file mode 100644
index 0000000..db3b97a
--- /dev/null
+++ b/media/java/android/media/tv/tuner/Filter.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2019 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 android.media.tv.tuner;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.media.tv.tuner.Tuner.FilterCallback;
+import android.media.tv.tuner.filter.FilterConfiguration;
+import android.media.tv.tuner.filter.Settings;
+
+/**
+ * Tuner data filter.
+ *
+ * <p> This class is used to filter wanted data according to the filter's configuration.
+ * @hide
+ */
+public class Filter implements AutoCloseable {
+    private long mNativeContext;
+    private FilterCallback mCallback;
+    int mId;
+
+    private native int nativeConfigureFilter(
+            int type, int subType, FilterConfiguration settings);
+    private native int nativeGetId();
+    private native int nativeSetDataSource(Tuner.Filter source);
+    private native int nativeStartFilter();
+    private native int nativeStopFilter();
+    private native int nativeFlushFilter();
+    private native int nativeRead(byte[] buffer, int offset, int size);
+    private native int nativeClose();
+
+    private Filter(int id) {
+        mId = id;
+    }
+
+    private void onFilterStatus(int status) {
+    }
+
+    /**
+     * Configures the filter.
+     *
+     * @param settings the settings of the filter.
+     * @return result status of the operation.
+     * @hide
+     */
+    public int configure(FilterConfiguration settings) {
+        int subType = -1;
+        Settings s = settings.getSettings();
+        if (s != null) {
+            subType = s.getType();
+        }
+        return nativeConfigureFilter(settings.getType(), subType, settings);
+    }
+
+    /**
+     * Gets the filter Id.
+     *
+     * @return the hardware resource Id for the filter.
+     * @hide
+     */
+    public int getId() {
+        return nativeGetId();
+    }
+
+    /**
+     * Sets the filter's data source.
+     *
+     * A filter uses demux as data source by default. If the data was packetized
+     * by multiple protocols, multiple filters may need to work together to
+     * extract all protocols' header. Then a filter's data source can be output
+     * from another filter.
+     *
+     * @param source the filter instance which provides data input. Switch to
+     * use demux as data source if the filter instance is NULL.
+     * @return result status of the operation.
+     * @hide
+     */
+    public int setDataSource(@Nullable Tuner.Filter source) {
+        return nativeSetDataSource(source);
+    }
+
+    /**
+     * Starts the filter.
+     *
+     * @return result status of the operation.
+     * @hide
+     */
+    public int start() {
+        return nativeStartFilter();
+    }
+
+
+    /**
+     * Stops the filter.
+     *
+     * @return result status of the operation.
+     * @hide
+     */
+    public int stop() {
+        return nativeStopFilter();
+    }
+
+    /**
+     * Flushes the filter.
+     *
+     * @return result status of the operation.
+     * @hide
+     */
+    public int flush() {
+        return nativeFlushFilter();
+    }
+
+    /** @hide */
+    public int read(@NonNull byte[] buffer, int offset, int size) {
+        size = Math.min(size, buffer.length - offset);
+        return nativeRead(buffer, offset, size);
+    }
+
+    /**
+     * Release the Filter instance.
+     *
+     * @hide
+     */
+    @Override
+    public void close() {
+        nativeClose();
+    }
+}
diff --git a/media/java/android/media/tv/tuner/FrontendCapabilities.java b/media/java/android/media/tv/tuner/FrontendCapabilities.java
deleted file mode 100644
index fcfd7c8..0000000
--- a/media/java/android/media/tv/tuner/FrontendCapabilities.java
+++ /dev/null
@@ -1,291 +0,0 @@
-/*
- * Copyright 2019 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 android.media.tv.tuner;
-
-/**
- * Frontend Capabilities.
- * @hide
- */
-public class FrontendCapabilities {
-    /** Analog Capabilities. */
-    public class Analog extends FrontendCapabilities {
-        private final int mTypeCap;
-        private final int mSifStandardCap;
-
-        Analog(int typeCap, int sifStandardCap) {
-            mTypeCap = typeCap;
-            mSifStandardCap = sifStandardCap;
-        }
-        /**
-         * Gets type capability.
-         */
-        public int getTypeCapability() {
-            return mTypeCap;
-        }
-        /** Gets SIF standard capability. */
-        public int getSifStandardCapability() {
-            return mSifStandardCap;
-        }
-    }
-
-    /** ATSC Capabilities. */
-    public class Atsc extends FrontendCapabilities {
-        private final int mModulationCap;
-
-        Atsc(int modulationCap) {
-            mModulationCap = modulationCap;
-        }
-        /** Gets modulation capability. */
-        public int getModulationCapability() {
-            return mModulationCap;
-        }
-    }
-
-    /** ATSC-3 Capabilities. */
-    public class Atsc3 extends FrontendCapabilities {
-        private final int mBandwidthCap;
-        private final int mModulationCap;
-        private final int mTimeInterleaveModeCap;
-        private final int mCodeRateCap;
-        private final int mFecCap;
-        private final int mDemodOutputFormatCap;
-
-        Atsc3(int bandwidthCap, int modulationCap, int timeInterleaveModeCap, int codeRateCap,
-                int fecCap, int demodOutputFormatCap) {
-            mBandwidthCap = bandwidthCap;
-            mModulationCap = modulationCap;
-            mTimeInterleaveModeCap = timeInterleaveModeCap;
-            mCodeRateCap = codeRateCap;
-            mFecCap = fecCap;
-            mDemodOutputFormatCap = demodOutputFormatCap;
-        }
-
-        /** Gets bandwidth capability. */
-        public int getBandwidthCapability() {
-            return mBandwidthCap;
-        }
-        /** Gets modulation capability. */
-        public int getModulationCapability() {
-            return mModulationCap;
-        }
-        /** Gets time interleave mod capability. */
-        public int getTimeInterleaveModeCapability() {
-            return mTimeInterleaveModeCap;
-        }
-        /** Gets code rate capability. */
-        public int getCodeRateCapability() {
-            return mCodeRateCap;
-        }
-        /** Gets FEC capability. */
-        public int getFecCapability() {
-            return mFecCap;
-        }
-        /** Gets demodulator output format capability. */
-        public int getDemodOutputFormatCapability() {
-            return mDemodOutputFormatCap;
-        }
-    }
-
-    /** DVBS Capabilities. */
-    public class Dvbs extends FrontendCapabilities {
-        private final int mModulationCap;
-        private final long mInnerFecCap;
-        private final int mStandard;
-
-        Dvbs(int modulationCap, long innerFecCap, int standard) {
-            mModulationCap = modulationCap;
-            mInnerFecCap = innerFecCap;
-            mStandard = standard;
-        }
-
-        /** Gets modulation capability. */
-        public int getModulationCapability() {
-            return mModulationCap;
-        }
-        /** Gets inner FEC capability. */
-        public long getInnerFecCapability() {
-            return mInnerFecCap;
-        }
-        /** Gets DVBS standard capability. */
-        public int getStandardCapability() {
-            return mStandard;
-        }
-    }
-
-    /** DVBC Capabilities. */
-    public class Dvbc extends FrontendCapabilities {
-        private final int mModulationCap;
-        private final int mFecCap;
-        private final int mAnnexCap;
-
-        Dvbc(int modulationCap, int fecCap, int annexCap) {
-            mModulationCap = modulationCap;
-            mFecCap = fecCap;
-            mAnnexCap = annexCap;
-        }
-
-        /** Gets modulation capability. */
-        public int getModulationCapability() {
-            return mModulationCap;
-        }
-        /** Gets FEC capability. */
-        public int getFecCapability() {
-            return mFecCap;
-        }
-        /** Gets annex capability. */
-        public int getAnnexCapability() {
-            return mAnnexCap;
-        }
-    }
-
-    /** DVBT Capabilities. */
-    public class Dvbt extends FrontendCapabilities {
-        private final int mTransmissionModeCap;
-        private final int mBandwidthCap;
-        private final int mConstellationCap;
-        private final int mCoderateCap;
-        private final int mHierarchyCap;
-        private final int mGuardIntervalCap;
-        private final boolean mIsT2Supported;
-        private final boolean mIsMisoSupported;
-
-        Dvbt(int transmissionModeCap, int bandwidthCap, int constellationCap, int coderateCap,
-                int hierarchyCap, int guardIntervalCap, boolean isT2Supported,
-                boolean isMisoSupported) {
-            mTransmissionModeCap = transmissionModeCap;
-            mBandwidthCap = bandwidthCap;
-            mConstellationCap = constellationCap;
-            mCoderateCap = coderateCap;
-            mHierarchyCap = hierarchyCap;
-            mGuardIntervalCap = guardIntervalCap;
-            mIsT2Supported = isT2Supported;
-            mIsMisoSupported = isMisoSupported;
-        }
-
-        /** Gets transmission mode capability. */
-        public int getTransmissionModeCapability() {
-            return mTransmissionModeCap;
-        }
-        /** Gets bandwidth capability. */
-        public int getBandwidthCapability() {
-            return mBandwidthCap;
-        }
-        /** Gets constellation capability. */
-        public int getConstellationCapability() {
-            return mConstellationCap;
-        }
-        /** Gets code rate capability. */
-        public int getCodeRateCapability() {
-            return mCoderateCap;
-        }
-        /** Gets hierarchy capability. */
-        public int getHierarchyCapability() {
-            return mHierarchyCap;
-        }
-        /** Gets guard interval capability. */
-        public int getGuardIntervalCapability() {
-            return mGuardIntervalCap;
-        }
-        /** Returns whether T2 is supported. */
-        public boolean getIsT2Supported() {
-            return mIsT2Supported;
-        }
-        /** Returns whether MISO is supported. */
-        public boolean getIsMisoSupported() {
-            return mIsMisoSupported;
-        }
-    }
-
-    /** ISDBS Capabilities. */
-    public class Isdbs extends FrontendCapabilities {
-        private final int mModulationCap;
-        private final int mCoderateCap;
-
-        Isdbs(int modulationCap, int coderateCap) {
-            mModulationCap = modulationCap;
-            mCoderateCap = coderateCap;
-        }
-
-        /** Gets modulation capability. */
-        public int getModulationCapability() {
-            return mModulationCap;
-        }
-        /** Gets code rate capability. */
-        public int getCodeRateCapability() {
-            return mCoderateCap;
-        }
-    }
-
-    /** ISDBS-3 Capabilities. */
-    public class Isdbs3 extends FrontendCapabilities {
-        private final int mModulationCap;
-        private final int mCoderateCap;
-
-        Isdbs3(int modulationCap, int coderateCap) {
-            mModulationCap = modulationCap;
-            mCoderateCap = coderateCap;
-        }
-
-        /** Gets modulation capability. */
-        public int getModulationCapability() {
-            return mModulationCap;
-        }
-        /** Gets code rate capability. */
-        public int getCodeRateCapability() {
-            return mCoderateCap;
-        }
-    }
-
-    /** ISDBC Capabilities. */
-    public class Isdbc extends FrontendCapabilities {
-        private final int mModeCap;
-        private final int mBandwidthCap;
-        private final int mModulationCap;
-        private final int mCoderateCap;
-        private final int mGuardIntervalCap;
-
-        Isdbc(int modeCap, int bandwidthCap, int modulationCap, int coderateCap,
-                int guardIntervalCap) {
-            mModeCap = modeCap;
-            mBandwidthCap = bandwidthCap;
-            mModulationCap = modulationCap;
-            mCoderateCap = coderateCap;
-            mGuardIntervalCap = guardIntervalCap;
-        }
-
-        /** Gets mode capability. */
-        public int getModeCapability() {
-            return mModeCap;
-        }
-        /** Gets bandwidth capability. */
-        public int getBandwidthCapability() {
-            return mBandwidthCap;
-        }
-        /** Gets modulation capability. */
-        public int getModulationCapability() {
-            return mModulationCap;
-        }
-        /** Gets code rate capability. */
-        public int getCodeRateCapability() {
-            return mCoderateCap;
-        }
-        /** Gets guard interval capability. */
-        public int getGuardIntervalCapability() {
-            return mGuardIntervalCap;
-        }
-    }
-}
diff --git a/media/java/android/media/tv/tuner/TimeFilter.java b/media/java/android/media/tv/tuner/TimeFilter.java
new file mode 100644
index 0000000..8bd0d26
--- /dev/null
+++ b/media/java/android/media/tv/tuner/TimeFilter.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2019 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 android.media.tv.tuner;
+
+import android.annotation.Nullable;
+import android.media.tv.tuner.TunerConstants.Result;
+
+/**
+ *  A timer filter is used to filter data based on timestamps.
+ *
+ *  <p> If the timestamp is set, data is discarded if its timestamp is smaller than the
+ *  timestamp in this time filter.
+ *
+ *  <p> The format of the timestamps is the same as PTS defined in ISO/IEC 13818-1:2019. The
+ *  timestamps may or may not be related to PTS or DTS.
+ *
+ * @hide
+ */
+public class TimeFilter implements AutoCloseable {
+    private native int nativeSetTimestamp(long timestamp);
+    private native int nativeClearTimestamp();
+    private native Long nativeGetTimestamp();
+    private native Long nativeGetSourceTime();
+    private native int nativeClose();
+
+    private boolean mEnable = false;
+
+    /**
+     * Set timestamp for time based filter.
+     *
+     * It is used to set initial timestamp and enable time filtering. Once set, the time will be
+     * increased automatically like a clock. Contents are discarded if their timestamps are
+     * older than the time in the time filter.
+     *
+     * This method can be called more than once to reset the initial timestamp.
+     *
+     * @param timestamp initial timestamp for the time filter before it's increased. It's
+     * based on the 90KHz counter, and it's the same format as PTS (Presentation Time Stamp)
+     * defined in ISO/IEC 13818-1:2019. The timestamps may or may not be related to PTS or DTS.
+     * @return result status of the operation.
+     */
+    @Result
+    public int setCurrentTimestamp(long timestamp) {
+        int res = nativeSetTimestamp(timestamp);
+        // TODO: use a constant for SUCCESS
+        if (res == 0) {
+            mEnable = true;
+        }
+        return res;
+    }
+
+    /**
+     * Clear the timestamp in the time filter.
+     *
+     * It is used to clear the time value of the time filter. Time filtering is disabled then.
+     *
+     * @return result status of the operation.
+     */
+    @Result
+    public int clearTimestamp() {
+        int res = nativeClearTimestamp();
+        if (res == 0) {
+            mEnable = false;
+        }
+        return res;
+    }
+
+    /**
+     * Get the current time in the time filter.
+     *
+     * It is used to inquiry current time in the time filter.
+     *
+     * @return current timestamp in the time filter. It's based on the 90KHz counter, and it's
+     * the same format as PTS (Presentation Time Stamp) defined in ISO/IEC 13818-1:2019. The
+     * timestamps may or may not be related to PTS or DTS. {@code null} if the timestamp is
+     * never set.
+     */
+    @Nullable
+    public Long getTimeStamp() {
+        if (!mEnable) {
+            return null;
+        }
+        return nativeGetTimestamp();
+    }
+
+    /**
+     * Get the timestamp from the beginning of incoming data stream.
+     *
+     * It is used to inquiry the timestamp from the beginning of incoming data stream.
+     *
+     * @return first timestamp of incoming data stream. It's based on the 90KHz counter, and
+     * it's the same format as PTS (Presentation Time Stamp) defined in ISO/IEC 13818-1:2019.
+     * The timestamps may or may not be related to PTS or DTS.
+     */
+    @Nullable
+    public Long getSourceTime() {
+        if (!mEnable) {
+            return null;
+        }
+        return nativeGetSourceTime();
+    }
+
+    /**
+     * Close the Time Filter instance
+     *
+     * It is to release the TimeFilter instance. Resources are reclaimed so the instance must
+     * not be accessed after this method is called.
+     */
+    @Override
+    public void close() {
+        nativeClose();
+    }
+}
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 1735518..962a7f6 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -21,18 +21,15 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.content.Context;
-import android.media.tv.tuner.TunerConstants.DemuxPidType;
 import android.media.tv.tuner.TunerConstants.FilterStatus;
 import android.media.tv.tuner.TunerConstants.FilterSubtype;
-import android.media.tv.tuner.TunerConstants.FilterType;
 import android.media.tv.tuner.TunerConstants.FrontendScanType;
 import android.media.tv.tuner.TunerConstants.LnbPosition;
 import android.media.tv.tuner.TunerConstants.LnbTone;
 import android.media.tv.tuner.TunerConstants.LnbVoltage;
 import android.media.tv.tuner.TunerConstants.Result;
-import android.media.tv.tuner.filter.FilterConfiguration;
+import android.media.tv.tuner.filter.FilterConfiguration.FilterType;
 import android.media.tv.tuner.filter.FilterEvent;
-import android.media.tv.tuner.filter.Settings;
 import android.media.tv.tuner.frontend.FrontendCallback;
 import android.media.tv.tuner.frontend.FrontendInfo;
 import android.media.tv.tuner.frontend.FrontendStatus;
@@ -40,7 +37,6 @@
 import android.os.Looper;
 import android.os.Message;
 
-import java.io.FileDescriptor;
 import java.util.List;
 
 /**
@@ -458,122 +454,11 @@
      * Tuner data filter.
      *
      * <p> This class is used to filter wanted data according to the filter's configuration.
+     * TODO: remove
      */
     public class Filter {
-        private long mNativeContext;
-        private FilterCallback mCallback;
-        int mId;
-
-        private native int nativeConfigureFilter(
-                int type, int subType, FilterConfiguration settings);
-        private native int nativeGetId();
-        private native int nativeSetDataSource(Filter source);
-        private native int nativeStartFilter();
-        private native int nativeStopFilter();
-        private native int nativeFlushFilter();
-        private native int nativeRead(byte[] buffer, int offset, int size);
-        private native int nativeClose();
-
-        private Filter(int id) {
-            mId = id;
-        }
-
-        private void onFilterStatus(int status) {
-            if (mHandler != null) {
-                mHandler.sendMessage(
-                        mHandler.obtainMessage(MSG_ON_FILTER_STATUS, status, 0, this));
-            }
-        }
-
-        /**
-         * Configures the filter.
-         *
-         * @param settings the settings of the filter.
-         * @return result status of the operation.
-         * @hide
-         */
-        public int configure(FilterConfiguration settings) {
-            int subType = -1;
-            Settings s = settings.getSettings();
-            if (s != null) {
-                subType = s.getType();
-            }
-            return nativeConfigureFilter(settings.getType(), subType, settings);
-        }
-
-        /**
-         * Gets the filter Id.
-         *
-         * @return the hardware resource Id for the filter.
-         * @hide
-         */
-        public int getId() {
-            return nativeGetId();
-        }
-
-        /**
-         * Sets the filter's data source.
-         *
-         * A filter uses demux as data source by default. If the data was packetized
-         * by multiple protocols, multiple filters may need to work together to
-         * extract all protocols' header. Then a filter's data source can be output
-         * from another filter.
-         *
-         * @param source the filter instance which provides data input. Switch to
-         * use demux as data source if the filter instance is NULL.
-         * @return result status of the operation.
-         * @hide
-         */
-        public int setDataSource(@Nullable Filter source) {
-            return nativeSetDataSource(source);
-        }
-
-        /**
-         * Starts the filter.
-         *
-         * @return result status of the operation.
-         * @hide
-         */
-        public int start() {
-            return nativeStartFilter();
-        }
-
-
-        /**
-         * Stops the filter.
-         *
-         * @return result status of the operation.
-         * @hide
-         */
-        public int stop() {
-            return nativeStopFilter();
-        }
-
-        /**
-         * Flushes the filter.
-         *
-         * @return result status of the operation.
-         * @hide
-         */
-        public int flush() {
-            return nativeFlushFilter();
-        }
-
-        /** @hide */
-        public int read(@NonNull byte[] buffer, int offset, int size) {
-            size = Math.min(size, buffer.length - offset);
-            return nativeRead(buffer, offset, size);
-        }
-
-        /**
-         * Release the Filter instance.
-         *
-         * @return result status of the operation.
-         * @hide
-         */
-        public int close() {
-            return nativeClose();
-        }
+        FilterCallback mCallback;
+        private Filter() {}
     }
 
     private Filter openFilter(@FilterType int mainType, @FilterSubtype int subType, int bufferSize,
@@ -590,115 +475,6 @@
     }
 
     /**
-     *  A timer filter is used to filter data based on timestamps.
-     *
-     *  <p> If the timestamp is set, data is discarded if its timestamp is smaller than the
-     *  timestamp in this time filter.
-     *
-     *  <p> The format of the timestamps is the same as PTS defined in ISO/IEC 13818-1:2019. The
-     *  timestamps may or may not be related to PTS or DTS.
-     *
-     * @hide
-     */
-    public class TimeFilter {
-        private native int nativeSetTimestamp(long timestamp);
-        private native int nativeClearTimestamp();
-        private native Long nativeGetTimestamp();
-        private native Long nativeGetSourceTime();
-        private native int nativeClose();
-
-        private boolean mEnable = false;
-
-        /**
-         * Set timestamp for time based filter.
-         *
-         * It is used to set initial timestamp and enable time filtering. Once set, the time will be
-         * increased automatically like a clock. Contents are discarded if their timestamps are
-         * older than the time in the time filter.
-         *
-         * This method can be called more than once to reset the initial timestamp.
-         *
-         * @param timestamp initial timestamp for the time filter before it's increased. It's
-         * based on the 90KHz counter, and it's the same format as PTS (Presentation Time Stamp)
-         * defined in ISO/IEC 13818-1:2019. The timestamps may or may not be related to PTS or DTS.
-         * @return result status of the operation.
-         */
-        @Result
-        public int setCurrentTimestamp(long timestamp) {
-            int res = nativeSetTimestamp(timestamp);
-            // TODO: use a constant for SUCCESS
-            if (res == 0) {
-                mEnable = true;
-            }
-            return res;
-        }
-
-        /**
-         * Clear the timestamp in the time filter.
-         *
-         * It is used to clear the time value of the time filter. Time filtering is disabled then.
-         *
-         * @return result status of the operation.
-         */
-        @Result
-        public int clearTimestamp() {
-            int res = nativeClearTimestamp();
-            if (res == 0) {
-                mEnable = false;
-            }
-            return res;
-        }
-
-        /**
-         * Get the current time in the time filter.
-         *
-         * It is used to inquiry current time in the time filter.
-         *
-         * @return current timestamp in the time filter. It's based on the 90KHz counter, and it's
-         * the same format as PTS (Presentation Time Stamp) defined in ISO/IEC 13818-1:2019. The
-         * timestamps may or may not be related to PTS or DTS. {@code null} if the timestamp is
-         * never set.
-         */
-        @Nullable
-        public Long getTimeStamp() {
-            if (!mEnable) {
-                return null;
-            }
-            return nativeGetTimestamp();
-        }
-
-        /**
-         * Get the timestamp from the beginning of incoming data stream.
-         *
-         * It is used to inquiry the timestamp from the beginning of incoming data stream.
-         *
-         * @return first timestamp of incoming data stream. It's based on the 90KHz counter, and
-         * it's the same format as PTS (Presentation Time Stamp) defined in ISO/IEC 13818-1:2019.
-         * The timestamps may or may not be related to PTS or DTS.
-         */
-        @Nullable
-        public Long getSourceTime() {
-            if (!mEnable) {
-                return null;
-            }
-            return nativeGetSourceTime();
-        }
-
-        /**
-         * Close the Time Filter instance
-         *
-         * It is to release the TimeFilter instance. Resources are reclaimed so the instance must
-         * not be accessed after this method is called.
-         *
-         * @return result status of the operation.
-         */
-        @Result
-        public int close() {
-            return nativeClose();
-        }
-    }
-
-    /**
      * Open a time filter instance.
      *
      * It is used to open time filter of demux.
@@ -822,81 +598,11 @@
      * <p> Descrambler is a hardware component used to descramble data.
      *
      * <p> This class controls the TIS interaction with Tuner HAL.
-     * TODO: make it static and extends Closable.
+     * TODO: Remove
      */
     public class Descrambler {
-        private long mNativeContext;
-
-        private native int nativeAddPid(int pidType, int pid, Filter filter);
-        private native int nativeRemovePid(int pidType, int pid, Filter filter);
-        private native int nativeSetKeyToken(byte[] keyToken);
-        private native int nativeClose();
-
-        private Descrambler() {}
-
-        /**
-         * Add packets' PID to the descrambler for descrambling.
-         *
-         * The descrambler will start descrambling packets with this PID. Multiple PIDs can be added
-         * into one descrambler instance because descambling can happen simultaneously on packets
-         * from different PIDs.
-         *
-         * If the Descrambler previously contained a filter for the PID, the old filter is replaced
-         * by the specified filter.
-         *
-         * @param pidType the type of the PID.
-         * @param pid the PID of packets to start to be descrambled.
-         * @param filter an optional filter instance to identify upper stream.
-         * @return result status of the operation.
-         *
-         * @hide
-         */
-        public int addPid(@DemuxPidType int pidType, int pid, @Nullable Filter filter) {
-            return nativeAddPid(pidType, pid, filter);
+        private Descrambler() {
         }
-
-        /**
-         * Remove packets' PID from the descrambler
-         *
-         * The descrambler will stop descrambling packets with this PID.
-         *
-         * @param pidType the type of the PID.
-         * @param pid the PID of packets to stop to be descrambled.
-         * @param filter an optional filter instance to identify upper stream.
-         * @return result status of the operation.
-         *
-         * @hide
-         */
-        public int removePid(@DemuxPidType int pidType, int pid, @Nullable Filter filter) {
-            return nativeRemovePid(pidType, pid, filter);
-        }
-
-        /**
-         * Set a key token to link descrambler to a key slot
-         *
-         * A descrambler instance can have only one key slot to link, but a key slot can hold a few
-         * keys for different purposes.
-         *
-         * @param keyToken the token to be used to link the key slot.
-         * @return result status of the operation.
-         *
-         * @hide
-         */
-        public int setKeyToken(byte[] keyToken) {
-            return nativeSetKeyToken(keyToken);
-        }
-
-        /**
-         * Release the descrambler instance.
-         *
-         * @return result status of the operation.
-         *
-         * @hide
-         */
-        public int close() {
-            return nativeClose();
-        }
-
     }
 
     /**
@@ -911,137 +617,6 @@
         return nativeOpenDescrambler();
     }
 
-    // TODO: consider splitting Dvr to Playback and Recording
-    /** @hide */
-    public class Dvr {
-        private long mNativeContext;
-        private DvrCallback mCallback;
-
-        private native int nativeAttachFilter(Filter filter);
-        private native int nativeDetachFilter(Filter filter);
-        private native int nativeConfigureDvr(DvrSettings settings);
-        private native int nativeStartDvr();
-        private native int nativeStopDvr();
-        private native int nativeFlushDvr();
-        private native int nativeClose();
-        private native void nativeSetFileDescriptor(FileDescriptor fd);
-        private native int nativeRead(int size);
-        private native int nativeRead(byte[] bytes, int offset, int size);
-        private native int nativeWrite(int size);
-        private native int nativeWrite(byte[] bytes, int offset, int size);
-
-        private Dvr() {}
-
-        /**
-         * Attaches a filter to DVR interface for recording.
-         *
-         * @param filter the filter to be attached.
-         * @return result status of the operation.
-         */
-        public int attachFilter(Filter filter) {
-            return nativeAttachFilter(filter);
-        }
-
-        /**
-         * Detaches a filter from DVR interface.
-         *
-         * @param filter the filter to be detached.
-         * @return result status of the operation.
-         */
-        public int detachFilter(Filter filter) {
-            return nativeDetachFilter(filter);
-        }
-
-        /**
-         * Configures the DVR.
-         *
-         * @param settings the settings of the DVR interface.
-         * @return result status of the operation.
-         */
-        public int configure(DvrSettings settings) {
-            return nativeConfigureDvr(settings);
-        }
-
-        /**
-         * Starts DVR.
-         *
-         * Starts consuming playback data or producing data for recording.
-         *
-         * @return result status of the operation.
-         */
-        public int start() {
-            return nativeStartDvr();
-        }
-
-        /**
-         * Stops DVR.
-         *
-         * Stops consuming playback data or producing data for recording.
-         *
-         * @return result status of the operation.
-         */
-        public int stop() {
-            return nativeStopDvr();
-        }
-
-        /**
-         * Flushed DVR data.
-         *
-         * @return result status of the operation.
-         */
-        public int flush() {
-            return nativeFlushDvr();
-        }
-
-        /**
-         * closes the DVR instance to release resources.
-         *
-         * @return result status of the operation.
-         */
-        public int close() {
-            return nativeClose();
-        }
-
-        /**
-         * Sets file descriptor to read/write data.
-         */
-        public void setFileDescriptor(FileDescriptor fd) {
-            nativeSetFileDescriptor(fd);
-        }
-
-        /**
-         * Reads data from the file for DVR playback.
-         */
-        public int read(int size) {
-            return nativeRead(size);
-        }
-
-        /**
-         * Reads data from the buffer for DVR playback.
-         */
-        public int read(@NonNull byte[] bytes, int offset, int size) {
-            if (size + offset > bytes.length) {
-                throw new ArrayIndexOutOfBoundsException(
-                        "Array length=" + bytes.length + ", offset=" + offset + ", size=" + size);
-            }
-            return nativeRead(bytes, offset, size);
-        }
-
-        /**
-         * Writes recording data to file.
-         */
-        public int write(int size) {
-            return nativeWrite(size);
-        }
-
-        /**
-         * Writes recording data to buffer.
-         */
-        public int write(@NonNull byte[] bytes, int offset, int size) {
-            return nativeWrite(bytes, offset, size);
-        }
-    }
-
     private Dvr openDvr(int type, int bufferSize) {
         Dvr dvr = nativeOpenDvr(type, bufferSize);
         return dvr;
diff --git a/media/java/android/media/tv/tuner/TunerConstants.java b/media/java/android/media/tv/tuner/TunerConstants.java
index e024432..bbaa518 100644
--- a/media/java/android/media/tv/tuner/TunerConstants.java
+++ b/media/java/android/media/tv/tuner/TunerConstants.java
@@ -128,21 +128,6 @@
     public static final int FRONTEND_SETTINGS_ISDBT = 9;
 
     /** @hide */
-    @IntDef({FILTER_TYPE_TS, FILTER_TYPE_MMTP, FILTER_TYPE_IP, FILTER_TYPE_TLV, FILTER_TYPE_ALP})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface FilterType {}
-    /** @hide */
-    public static final int FILTER_TYPE_TS = Constants.DemuxFilterMainType.TS;
-    /** @hide */
-    public static final int FILTER_TYPE_MMTP = Constants.DemuxFilterMainType.MMTP;
-    /** @hide */
-    public static final int FILTER_TYPE_IP = Constants.DemuxFilterMainType.IP;
-    /** @hide */
-    public static final int FILTER_TYPE_TLV = Constants.DemuxFilterMainType.TLV;
-    /** @hide */
-    public static final int FILTER_TYPE_ALP = Constants.DemuxFilterMainType.ALP;
-
-    /** @hide */
     @IntDef({FILTER_SUBTYPE_UNDEFINED, FILTER_SUBTYPE_SECTION, FILTER_SUBTYPE_PES,
             FILTER_SUBTYPE_AUDIO, FILTER_SUBTYPE_VIDEO, FILTER_SUBTYPE_DOWNLOAD,
             FILTER_SUBTYPE_RECORD, FILTER_SUBTYPE_TS, FILTER_SUBTYPE_PCR, FILTER_SUBTYPE_TEMI,
diff --git a/media/java/android/media/tv/tuner/TunerUtils.java b/media/java/android/media/tv/tuner/TunerUtils.java
index a7ccb02..8780b72 100644
--- a/media/java/android/media/tv/tuner/TunerUtils.java
+++ b/media/java/android/media/tv/tuner/TunerUtils.java
@@ -20,7 +20,8 @@
 import android.content.pm.PackageManager;
 import android.hardware.tv.tuner.V1_0.Constants;
 import android.media.tv.tuner.TunerConstants.FilterSubtype;
-import android.media.tv.tuner.TunerConstants.FilterType;
+import android.media.tv.tuner.filter.FilterConfiguration;
+import android.media.tv.tuner.filter.FilterConfiguration.FilterType;
 
 /**
  * Utility class for tuner framework.
@@ -50,7 +51,7 @@
      * @param subtype filter subtype.
      */
     public static int getFilterSubtype(@FilterType int mainType, @FilterSubtype int subtype) {
-        if (mainType == TunerConstants.FILTER_TYPE_TS) {
+        if (mainType == FilterConfiguration.FILTER_TYPE_TS) {
             switch (subtype) {
                 case TunerConstants.FILTER_SUBTYPE_UNDEFINED:
                     return Constants.DemuxTsFilterType.UNDEFINED;
@@ -73,7 +74,7 @@
                 default:
                     break;
             }
-        } else if (mainType == TunerConstants.FILTER_TYPE_MMTP) {
+        } else if (mainType == FilterConfiguration.FILTER_TYPE_MMTP) {
             switch (subtype) {
                 case TunerConstants.FILTER_SUBTYPE_UNDEFINED:
                     return Constants.DemuxMmtpFilterType.UNDEFINED;
@@ -95,7 +96,7 @@
                     break;
             }
 
-        } else if (mainType == TunerConstants.FILTER_TYPE_IP) {
+        } else if (mainType == FilterConfiguration.FILTER_TYPE_IP) {
             switch (subtype) {
                 case TunerConstants.FILTER_SUBTYPE_UNDEFINED:
                     return Constants.DemuxIpFilterType.UNDEFINED;
@@ -112,7 +113,7 @@
                 default:
                     break;
             }
-        } else if (mainType == TunerConstants.FILTER_TYPE_TLV) {
+        } else if (mainType == FilterConfiguration.FILTER_TYPE_TLV) {
             switch (subtype) {
                 case TunerConstants.FILTER_SUBTYPE_UNDEFINED:
                     return Constants.DemuxTlvFilterType.UNDEFINED;
@@ -125,7 +126,7 @@
                 default:
                     break;
             }
-        } else if (mainType == TunerConstants.FILTER_TYPE_ALP) {
+        } else if (mainType == FilterConfiguration.FILTER_TYPE_ALP) {
             switch (subtype) {
                 case TunerConstants.FILTER_SUBTYPE_UNDEFINED:
                     return Constants.DemuxAlpFilterType.UNDEFINED;
diff --git a/media/java/android/media/tv/tuner/filter/AlpFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/AlpFilterConfiguration.java
index 4d02b84..f0fe533 100644
--- a/media/java/android/media/tv/tuner/filter/AlpFilterConfiguration.java
+++ b/media/java/android/media/tv/tuner/filter/AlpFilterConfiguration.java
@@ -16,8 +16,6 @@
 
 package android.media.tv.tuner.filter;
 
-import android.media.tv.tuner.TunerConstants;
-
 /**
  * Filter configuration for a ALP filter.
  * @hide
@@ -32,6 +30,6 @@
 
     @Override
     public int getType() {
-        return TunerConstants.FILTER_TYPE_ALP;
+        return FilterConfiguration.FILTER_TYPE_ALP;
     }
 }
diff --git a/media/java/android/media/tv/tuner/filter/FilterConfiguration.java b/media/java/android/media/tv/tuner/filter/FilterConfiguration.java
index 930ca74..99b10cd 100644
--- a/media/java/android/media/tv/tuner/filter/FilterConfiguration.java
+++ b/media/java/android/media/tv/tuner/filter/FilterConfiguration.java
@@ -16,28 +16,62 @@
 
 package android.media.tv.tuner.filter;
 
+import android.annotation.IntDef;
 import android.annotation.Nullable;
-import android.media.tv.tuner.TunerConstants.FilterType;
+import android.hardware.tv.tuner.V1_0.Constants;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
 /**
- * Demux Filter configuration.
+ * Filter configuration used to configure filters.
  *
  * @hide
  */
 public abstract class FilterConfiguration {
-    @Nullable
-    protected final Settings mSettings;
 
-    protected FilterConfiguration(Settings settings) {
+    /** @hide */
+    @IntDef({FILTER_TYPE_TS, FILTER_TYPE_MMTP, FILTER_TYPE_IP, FILTER_TYPE_TLV, FILTER_TYPE_ALP})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface FilterType {}
+
+    /**
+     * TS filter type.
+     */
+    public static final int FILTER_TYPE_TS = Constants.DemuxFilterMainType.TS;
+    /**
+     * MMTP filter type.
+     */
+    public static final int FILTER_TYPE_MMTP = Constants.DemuxFilterMainType.MMTP;
+    /**
+     * IP filter type.
+     */
+    public static final int FILTER_TYPE_IP = Constants.DemuxFilterMainType.IP;
+    /**
+     * TLV filter type.
+     */
+    public static final int FILTER_TYPE_TLV = Constants.DemuxFilterMainType.TLV;
+    /**
+     * ALP filter type.
+     */
+    public static final int FILTER_TYPE_ALP = Constants.DemuxFilterMainType.ALP;
+
+    @Nullable
+    private final Settings mSettings;
+
+    /* package */ FilterConfiguration(Settings settings) {
         mSettings = settings;
     }
 
     /**
-     * Gets filter configuration type
+     * Gets filter configuration type.
+     * @hide
      */
     @FilterType
     public abstract int getType();
 
+    /** @hide */
+    @Nullable
     public Settings getSettings() {
         return mSettings;
     }
diff --git a/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java
index 2c706c0..c896368 100644
--- a/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java
+++ b/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java
@@ -16,8 +16,6 @@
 
 package android.media.tv.tuner.filter;
 
-import android.media.tv.tuner.TunerConstants;
-
 /**
  * Filter configuration for a IP filter.
  * @hide
@@ -35,6 +33,6 @@
 
     @Override
     public int getType() {
-        return TunerConstants.FILTER_TYPE_IP;
+        return FilterConfiguration.FILTER_TYPE_IP;
     }
 }
diff --git a/media/java/android/media/tv/tuner/filter/MmtpFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/MmtpFilterConfiguration.java
index f70e70a..9045ce6 100644
--- a/media/java/android/media/tv/tuner/filter/MmtpFilterConfiguration.java
+++ b/media/java/android/media/tv/tuner/filter/MmtpFilterConfiguration.java
@@ -16,8 +16,6 @@
 
 package android.media.tv.tuner.filter;
 
-import android.media.tv.tuner.TunerConstants;
-
 /**
  * Filter configuration for a MMTP filter.
  * @hide
@@ -31,6 +29,6 @@
 
     @Override
     public int getType() {
-        return TunerConstants.FILTER_TYPE_MMTP;
+        return FilterConfiguration.FILTER_TYPE_MMTP;
     }
 }
diff --git a/media/java/android/media/tv/tuner/filter/PesSettings.java b/media/java/android/media/tv/tuner/filter/PesSettings.java
index 3d2c265..f38abf1 100644
--- a/media/java/android/media/tv/tuner/filter/PesSettings.java
+++ b/media/java/android/media/tv/tuner/filter/PesSettings.java
@@ -16,45 +16,54 @@
 
 package android.media.tv.tuner.filter;
 
+import android.annotation.NonNull;
 import android.media.tv.tuner.TunerConstants;
 import android.media.tv.tuner.TunerUtils;
+import android.media.tv.tuner.filter.FilterConfiguration.FilterType;
 
 /**
  * Filter Settings for a PES Data.
+ *
  * @hide
  */
 public class PesSettings extends Settings {
-    private int mStreamId;
-    private boolean mIsRaw;
+    private final int mStreamId;
+    private final boolean mIsRaw;
 
-    private PesSettings(int mainType, int streamId, boolean isRaw) {
+    private PesSettings(@FilterType int mainType, int streamId, boolean isRaw) {
         super(TunerUtils.getFilterSubtype(mainType, TunerConstants.FILTER_SUBTYPE_PES));
         mStreamId = streamId;
         mIsRaw = isRaw;
     }
 
     /**
-     * Creates a builder for PesSettings.
+     * Creates a builder for {@link PesSettings}.
+     *
+     * @param mainType the filter main type of the settings.
      */
-    public static Builder newBuilder(int mainType) {
+    @NonNull
+    public static Builder newBuilder(@FilterType int mainType) {
         return new Builder(mainType);
     }
 
     /**
-     * Builder for PesSettings.
+     * Builder for {@link PesSettings}.
      */
     public static class Builder {
         private final int mMainType;
         private int mStreamId;
         private boolean mIsRaw;
 
-        public Builder(int mainType) {
+        private Builder(int mainType) {
             mMainType = mainType;
         }
 
         /**
          * Sets stream ID.
+         *
+         * @param streamId the stream ID.
          */
+        @NonNull
         public Builder setStreamId(int streamId) {
             mStreamId = streamId;
             return this;
@@ -62,16 +71,20 @@
 
         /**
          * Sets whether it's raw.
-         * true if the filter send onFilterStatus instead of onFilterEvent.
+         *
+         * @param isRaw {@code true} if the data is raw. Filter sends onFilterStatus callback
+         * instead of onFilterEvent for raw data. {@code false} otherwise.
          */
+        @NonNull
         public Builder setIsRaw(boolean isRaw) {
             mIsRaw = isRaw;
             return this;
         }
 
         /**
-         * Builds a PesSettings instance.
+         * Builds a {@link PesSettings} object.
          */
+        @NonNull
         public PesSettings build() {
             return new PesSettings(mMainType, mStreamId, mIsRaw);
         }
diff --git a/media/java/android/media/tv/tuner/filter/Settings.java b/media/java/android/media/tv/tuner/filter/Settings.java
index 789ed08..146aca74 100644
--- a/media/java/android/media/tv/tuner/filter/Settings.java
+++ b/media/java/android/media/tv/tuner/filter/Settings.java
@@ -18,18 +18,20 @@
 
 /**
  * Settings for filters of different subtypes.
+ *
  * @hide
  */
 public abstract class Settings {
-    protected final int mType;
+    private final int mType;
 
-    protected Settings(int type) {
+    /* package */ Settings(int type) {
         mType = type;
     }
 
     /**
      * Gets filter settings type.
-     * @return
+     *
+     * @hide
      */
     public int getType() {
         return mType;
diff --git a/media/java/android/media/tv/tuner/filter/TlvFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/TlvFilterConfiguration.java
index 1f8bcb3..de8ee75 100644
--- a/media/java/android/media/tv/tuner/filter/TlvFilterConfiguration.java
+++ b/media/java/android/media/tv/tuner/filter/TlvFilterConfiguration.java
@@ -16,8 +16,6 @@
 
 package android.media.tv.tuner.filter;
 
-import android.media.tv.tuner.TunerConstants;
-
 /**
  * Filter configuration for a TLV filter.
  * @hide
@@ -33,6 +31,6 @@
 
     @Override
     public int getType() {
-        return TunerConstants.FILTER_TYPE_TLV;
+        return FilterConfiguration.FILTER_TYPE_TLV;
     }
 }
diff --git a/media/java/android/media/tv/tuner/filter/TsFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/TsFilterConfiguration.java
index 103698c..d0241b6 100644
--- a/media/java/android/media/tv/tuner/filter/TsFilterConfiguration.java
+++ b/media/java/android/media/tv/tuner/filter/TsFilterConfiguration.java
@@ -16,14 +16,15 @@
 
 package android.media.tv.tuner.filter;
 
-import android.media.tv.tuner.TunerConstants;
+import android.annotation.NonNull;
 
 /**
  * Filter configuration for a TS filter.
+ *
  * @hide
  */
 public class TsFilterConfiguration extends FilterConfiguration {
-    private int mTpid;
+    private final int mTpid;
 
     private TsFilterConfiguration(Settings settings, int tpid) {
         super(settings);
@@ -32,42 +33,50 @@
 
     @Override
     public int getType() {
-        return TunerConstants.FILTER_TYPE_TS;
+        return FilterConfiguration.FILTER_TYPE_TS;
     }
 
     /**
-     * Creates a new builder.
+     * Creates a builder for {@link TsFilterConfiguration}.
      */
-    public static TsFilterConfiguration.Builder newBuilder() {
-        return new TsFilterConfiguration.Builder();
+    @NonNull
+    public static Builder newBuilder() {
+        return new Builder();
     }
 
     /**
-     * Builder for TsFilterConfiguration.
+     * Builder for {@link TsFilterConfiguration}.
      */
     public static class Builder {
         private Settings mSettings;
         private int mTpid;
 
         /**
-         * Sets settings.
+         * Sets filter settings.
+         *
+         * @param settings the filter settings.
          */
-        public TsFilterConfiguration.Builder setSettings(Settings settings) {
+        @NonNull
+        public Builder setSettings(@NonNull Settings settings) {
             mSettings = settings;
             return this;
         }
 
         /**
-         * Sets TPID.
+         * Sets Tag Protocol ID.
+         *
+         * @param tpid the Tag Protocol ID.
          */
-        public TsFilterConfiguration.Builder setTpid(int tpid) {
+        @NonNull
+        public Builder setTpid(int tpid) {
             mTpid = tpid;
             return this;
         }
 
         /**
-         * Builds a TsFilterConfiguration instance.
+         * Builds a {@link TsFilterConfiguration} object.
          */
+        @NonNull
         public TsFilterConfiguration build() {
             return new TsFilterConfiguration(mSettings, mTpid);
         }
diff --git a/media/java/android/media/tv/tuner/frontend/AnalogFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/AnalogFrontendCapabilities.java
new file mode 100644
index 0000000..2962e98
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/AnalogFrontendCapabilities.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2019 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 android.media.tv.tuner.frontend;
+
+/**
+ * Analog Capabilities.
+ * @hide
+ */
+public class AnalogFrontendCapabilities extends FrontendCapabilities {
+    private final int mTypeCap;
+    private final int mSifStandardCap;
+
+    AnalogFrontendCapabilities(int typeCap, int sifStandardCap) {
+        mTypeCap = typeCap;
+        mSifStandardCap = sifStandardCap;
+    }
+    /**
+     * Gets type capability.
+     */
+    public int getTypeCapability() {
+        return mTypeCap;
+    }
+    /** Gets SIF standard capability. */
+    public int getSifStandardCapability() {
+        return mSifStandardCap;
+    }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/Atsc3FrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/Atsc3FrontendCapabilities.java
new file mode 100644
index 0000000..677f9387
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/Atsc3FrontendCapabilities.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2019 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 android.media.tv.tuner.frontend;
+
+/**
+ * ATSC-3 Capabilities.
+ * @hide
+ */
+public class Atsc3FrontendCapabilities extends FrontendCapabilities {
+    private final int mBandwidthCap;
+    private final int mModulationCap;
+    private final int mTimeInterleaveModeCap;
+    private final int mCodeRateCap;
+    private final int mFecCap;
+    private final int mDemodOutputFormatCap;
+
+    Atsc3FrontendCapabilities(int bandwidthCap, int modulationCap, int timeInterleaveModeCap,
+            int codeRateCap, int fecCap, int demodOutputFormatCap) {
+        mBandwidthCap = bandwidthCap;
+        mModulationCap = modulationCap;
+        mTimeInterleaveModeCap = timeInterleaveModeCap;
+        mCodeRateCap = codeRateCap;
+        mFecCap = fecCap;
+        mDemodOutputFormatCap = demodOutputFormatCap;
+    }
+
+    /** Gets bandwidth capability. */
+    public int getBandwidthCapability() {
+        return mBandwidthCap;
+    }
+    /** Gets modulation capability. */
+    public int getModulationCapability() {
+        return mModulationCap;
+    }
+    /** Gets time interleave mod capability. */
+    public int getTimeInterleaveModeCapability() {
+        return mTimeInterleaveModeCap;
+    }
+    /** Gets code rate capability. */
+    public int getCodeRateCapability() {
+        return mCodeRateCap;
+    }
+    /** Gets FEC capability. */
+    public int getFecCapability() {
+        return mFecCap;
+    }
+    /** Gets demodulator output format capability. */
+    public int getDemodOutputFormatCapability() {
+        return mDemodOutputFormatCap;
+    }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/AtscFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/AtscFrontendCapabilities.java
new file mode 100644
index 0000000..6ae3c63
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/AtscFrontendCapabilities.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2019 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 android.media.tv.tuner.frontend;
+
+/**
+ * ATSC Capabilities.
+ * @hide
+ */
+public class AtscFrontendCapabilities extends FrontendCapabilities {
+    private final int mModulationCap;
+
+    AtscFrontendCapabilities(int modulationCap) {
+        mModulationCap = modulationCap;
+    }
+    /** Gets modulation capability. */
+    public int getModulationCapability() {
+        return mModulationCap;
+    }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/DvbcFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/DvbcFrontendCapabilities.java
new file mode 100644
index 0000000..edea7af
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/DvbcFrontendCapabilities.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2019 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 android.media.tv.tuner.frontend;
+
+/**
+ * DVBC Capabilities.
+ * @hide
+ */
+public class DvbcFrontendCapabilities extends FrontendCapabilities {
+    private final int mModulationCap;
+    private final int mFecCap;
+    private final int mAnnexCap;
+
+    DvbcFrontendCapabilities(int modulationCap, int fecCap, int annexCap) {
+        mModulationCap = modulationCap;
+        mFecCap = fecCap;
+        mAnnexCap = annexCap;
+    }
+
+    /** Gets modulation capability. */
+    public int getModulationCapability() {
+        return mModulationCap;
+    }
+    /** Gets FEC capability. */
+    public int getFecCapability() {
+        return mFecCap;
+    }
+    /** Gets annex capability. */
+    public int getAnnexCapability() {
+        return mAnnexCap;
+    }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/DvbsFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/DvbsFrontendCapabilities.java
new file mode 100644
index 0000000..f5a4157
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/DvbsFrontendCapabilities.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2019 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 android.media.tv.tuner.frontend;
+
+/**
+ * DVBS Capabilities.
+ * @hide
+ */
+public class DvbsFrontendCapabilities extends FrontendCapabilities {
+    private final int mModulationCap;
+    private final long mInnerFecCap;
+    private final int mStandard;
+
+    DvbsFrontendCapabilities(int modulationCap, long innerFecCap, int standard) {
+        mModulationCap = modulationCap;
+        mInnerFecCap = innerFecCap;
+        mStandard = standard;
+    }
+
+    /** Gets modulation capability. */
+    public int getModulationCapability() {
+        return mModulationCap;
+    }
+    /** Gets inner FEC capability. */
+    public long getInnerFecCapability() {
+        return mInnerFecCap;
+    }
+    /** Gets DVBS standard capability. */
+    public int getStandardCapability() {
+        return mStandard;
+    }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/DvbtFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/DvbtFrontendCapabilities.java
new file mode 100644
index 0000000..e9c16ddd4
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/DvbtFrontendCapabilities.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2019 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 android.media.tv.tuner.frontend;
+
+/**
+ * DVBT Capabilities.
+ * @hide
+ */
+public class DvbtFrontendCapabilities extends FrontendCapabilities {
+    private final int mTransmissionModeCap;
+    private final int mBandwidthCap;
+    private final int mConstellationCap;
+    private final int mCoderateCap;
+    private final int mHierarchyCap;
+    private final int mGuardIntervalCap;
+    private final boolean mIsT2Supported;
+    private final boolean mIsMisoSupported;
+
+    DvbtFrontendCapabilities(int transmissionModeCap, int bandwidthCap, int constellationCap,
+            int coderateCap, int hierarchyCap, int guardIntervalCap, boolean isT2Supported,
+            boolean isMisoSupported) {
+        mTransmissionModeCap = transmissionModeCap;
+        mBandwidthCap = bandwidthCap;
+        mConstellationCap = constellationCap;
+        mCoderateCap = coderateCap;
+        mHierarchyCap = hierarchyCap;
+        mGuardIntervalCap = guardIntervalCap;
+        mIsT2Supported = isT2Supported;
+        mIsMisoSupported = isMisoSupported;
+    }
+
+    /** Gets transmission mode capability. */
+    public int getTransmissionModeCapability() {
+        return mTransmissionModeCap;
+    }
+    /** Gets bandwidth capability. */
+    public int getBandwidthCapability() {
+        return mBandwidthCap;
+    }
+    /** Gets constellation capability. */
+    public int getConstellationCapability() {
+        return mConstellationCap;
+    }
+    /** Gets code rate capability. */
+    public int getCodeRateCapability() {
+        return mCoderateCap;
+    }
+    /** Gets hierarchy capability. */
+    public int getHierarchyCapability() {
+        return mHierarchyCap;
+    }
+    /** Gets guard interval capability. */
+    public int getGuardIntervalCapability() {
+        return mGuardIntervalCap;
+    }
+    /** Returns whether T2 is supported. */
+    public boolean getIsT2Supported() {
+        return mIsT2Supported;
+    }
+    /** Returns whether MISO is supported. */
+    public boolean getIsMisoSupported() {
+        return mIsMisoSupported;
+    }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/FrontendCapabilities.java
new file mode 100644
index 0000000..7350bc0
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/FrontendCapabilities.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2019 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 android.media.tv.tuner.frontend;
+
+/**
+ * Frontend Capabilities.
+ * @hide
+ */
+public abstract class FrontendCapabilities {
+}
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendInfo.java b/media/java/android/media/tv/tuner/frontend/FrontendInfo.java
index ef6c029..5d03570 100644
--- a/media/java/android/media/tv/tuner/frontend/FrontendInfo.java
+++ b/media/java/android/media/tv/tuner/frontend/FrontendInfo.java
@@ -16,7 +16,6 @@
 
 package android.media.tv.tuner.frontend;
 
-import android.media.tv.tuner.FrontendCapabilities;
 import android.media.tv.tuner.TunerConstants.FrontendType;
 
 /**
diff --git a/media/java/android/media/tv/tuner/frontend/IsdbcFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/IsdbcFrontendCapabilities.java
new file mode 100644
index 0000000..6544b17
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/IsdbcFrontendCapabilities.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2019 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 android.media.tv.tuner.frontend;
+
+/**
+ * ISDBC Capabilities.
+ * @hide
+ */
+public class IsdbcFrontendCapabilities extends FrontendCapabilities {
+    private final int mModeCap;
+    private final int mBandwidthCap;
+    private final int mModulationCap;
+    private final int mCoderateCap;
+    private final int mGuardIntervalCap;
+
+    IsdbcFrontendCapabilities(int modeCap, int bandwidthCap, int modulationCap, int coderateCap,
+            int guardIntervalCap) {
+        mModeCap = modeCap;
+        mBandwidthCap = bandwidthCap;
+        mModulationCap = modulationCap;
+        mCoderateCap = coderateCap;
+        mGuardIntervalCap = guardIntervalCap;
+    }
+
+    /** Gets mode capability. */
+    public int getModeCapability() {
+        return mModeCap;
+    }
+    /** Gets bandwidth capability. */
+    public int getBandwidthCapability() {
+        return mBandwidthCap;
+    }
+    /** Gets modulation capability. */
+    public int getModulationCapability() {
+        return mModulationCap;
+    }
+    /** Gets code rate capability. */
+    public int getCodeRateCapability() {
+        return mCoderateCap;
+    }
+    /** Gets guard interval capability. */
+    public int getGuardIntervalCapability() {
+        return mGuardIntervalCap;
+    }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendCapabilities.java
new file mode 100644
index 0000000..92832b7
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendCapabilities.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2019 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 android.media.tv.tuner.frontend;
+
+/**
+ * ISDBS-3 Capabilities.
+ * @hide
+ */
+public class Isdbs3FrontendCapabilities extends FrontendCapabilities {
+    private final int mModulationCap;
+    private final int mCoderateCap;
+
+    Isdbs3FrontendCapabilities(int modulationCap, int coderateCap) {
+        mModulationCap = modulationCap;
+        mCoderateCap = coderateCap;
+    }
+
+    /** Gets modulation capability. */
+    public int getModulationCapability() {
+        return mModulationCap;
+    }
+    /** Gets code rate capability. */
+    public int getCodeRateCapability() {
+        return mCoderateCap;
+    }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/IsdbsFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/IsdbsFrontendCapabilities.java
new file mode 100644
index 0000000..b930b25
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/IsdbsFrontendCapabilities.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2019 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 android.media.tv.tuner.frontend;
+
+/**
+ * ISDBS Capabilities.
+ * @hide
+ */
+public class IsdbsFrontendCapabilities extends FrontendCapabilities {
+    private final int mModulationCap;
+    private final int mCoderateCap;
+
+    IsdbsFrontendCapabilities(int modulationCap, int coderateCap) {
+        mModulationCap = modulationCap;
+        mCoderateCap = coderateCap;
+    }
+
+    /** Gets modulation capability. */
+    public int getModulationCapability() {
+        return mModulationCap;
+    }
+    /** Gets code rate capability. */
+    public int getCodeRateCapability() {
+        return mCoderateCap;
+    }
+}
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index ee67613..536a061 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -143,6 +143,10 @@
         "libutils",
     ],
 
+    header_libs: [
+        "libstagefright_foundation_headers",
+    ],
+
     export_include_dirs: ["."],
 
     cflags: [
diff --git a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java
index ec17732..8c0273b 100644
--- a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java
+++ b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java
@@ -64,7 +64,7 @@
             "com.android.mediarouteprovider.TYPE_SPECIAL";
 
     Map<String, MediaRoute2Info> mRoutes = new HashMap<>();
-    Map<String, Integer> mRouteSessionMap = new HashMap<>();
+    Map<String, String> mRouteSessionMap = new HashMap<>();
     private int mNextSessionId = 1000;
 
     private void initializeRoutes() {
@@ -177,7 +177,7 @@
         }
         maybeDeselectRoute(routeId);
 
-        final int sessionId = mNextSessionId;
+        final String sessionId = String.valueOf(mNextSessionId);
         mNextSessionId++;
 
         mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
@@ -196,7 +196,7 @@
     }
 
     @Override
-    public void onDestroySession(int sessionId, RouteSessionInfo lastSessionInfo) {
+    public void onDestroySession(String sessionId, RouteSessionInfo lastSessionInfo) {
         for (String routeId : lastSessionInfo.getSelectedRoutes()) {
             mRouteSessionMap.remove(routeId);
             MediaRoute2Info route = mRoutes.get(routeId);
@@ -209,7 +209,7 @@
     }
 
     @Override
-    public void onSelectRoute(int sessionId, String routeId) {
+    public void onSelectRoute(String sessionId, String routeId) {
         RouteSessionInfo sessionInfo = getSessionInfo(sessionId);
         MediaRoute2Info route = mRoutes.get(routeId);
         if (route == null || sessionInfo == null) {
@@ -218,7 +218,7 @@
         maybeDeselectRoute(routeId);
 
         mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
-                .setClientPackageName(sessionInfo.getPackageName())
+                .setClientPackageName(sessionInfo.getClientPackageName())
                 .build());
         mRouteSessionMap.put(routeId, sessionId);
 
@@ -232,7 +232,7 @@
     }
 
     @Override
-    public void onDeselectRoute(int sessionId, String routeId) {
+    public void onDeselectRoute(String sessionId, String routeId) {
         RouteSessionInfo sessionInfo = getSessionInfo(sessionId);
         MediaRoute2Info route = mRoutes.get(routeId);
 
@@ -254,7 +254,7 @@
     }
 
     @Override
-    public void onTransferToRoute(int sessionId, String routeId) {
+    public void onTransferToRoute(String sessionId, String routeId) {
         RouteSessionInfo sessionInfo = getSessionInfo(sessionId);
         RouteSessionInfo newSessionInfo = new RouteSessionInfo.Builder(sessionInfo)
                 .clearSelectedRoutes()
@@ -271,7 +271,7 @@
             return;
         }
 
-        int sessionId = mRouteSessionMap.get(routeId);
+        String sessionId = mRouteSessionMap.get(routeId);
         onDeselectRoute(sessionId, routeId);
     }
 
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
index af69c7e..ce4bb8e 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
@@ -400,6 +400,7 @@
             assertTrue(createRouteMap(controller2.getSelectedRoutes()).containsKey(ROUTE_ID2));
             assertTrue(TextUtils.equals(TYPE_SAMPLE, controller1.getRouteType()));
             assertTrue(TextUtils.equals(TYPE_SAMPLE, controller2.getRouteType()));
+
         } finally {
             releaseControllers(createdControllers);
             mRouter2.unregisterRouteCallback(routeCallback);
@@ -486,20 +487,21 @@
             public void onSessionInfoChanged(RouteSessionController controller,
                     RouteSessionInfo oldInfo, RouteSessionInfo newInfo) {
                 if (onSessionCreatedLatch.getCount() != 0
-                        || controllers.get(0).getSessionId() != controller.getSessionId()) {
+                        || !TextUtils.equals(
+                                controllers.get(0).getSessionId(), controller.getSessionId())) {
                     return;
                 }
 
                 if (onSessionInfoChangedLatchForSelect.getCount() != 0) {
                     // Check oldInfo
-                    assertEquals(controller.getSessionId(), oldInfo.getSessionId());
+                    assertEquals(controller.getSessionId(), oldInfo.getId());
                     assertEquals(1, oldInfo.getSelectedRoutes().size());
                     assertTrue(oldInfo.getSelectedRoutes().contains(ROUTE_ID1));
                     assertTrue(oldInfo.getSelectableRoutes().contains(
                             ROUTE_ID4_TO_SELECT_AND_DESELECT));
 
                     // Check newInfo
-                    assertEquals(controller.getSessionId(), newInfo.getSessionId());
+                    assertEquals(controller.getSessionId(), newInfo.getId());
                     assertEquals(2, newInfo.getSelectedRoutes().size());
                     assertTrue(newInfo.getSelectedRoutes().contains(ROUTE_ID1));
                     assertTrue(newInfo.getSelectedRoutes().contains(
@@ -510,7 +512,7 @@
                     onSessionInfoChangedLatchForSelect.countDown();
                 } else {
                     // Check newInfo
-                    assertEquals(controller.getSessionId(), newInfo.getSessionId());
+                    assertEquals(controller.getSessionId(), newInfo.getId());
                     assertEquals(1, newInfo.getSelectedRoutes().size());
                     assertTrue(newInfo.getSelectedRoutes().contains(ROUTE_ID1));
                     assertFalse(newInfo.getSelectedRoutes().contains(
@@ -587,18 +589,19 @@
             public void onSessionInfoChanged(RouteSessionController controller,
                     RouteSessionInfo oldInfo, RouteSessionInfo newInfo) {
                 if (onSessionCreatedLatch.getCount() != 0
-                        || controllers.get(0).getSessionId() != controller.getSessionId()) {
+                        || !TextUtils.equals(
+                                controllers.get(0).getSessionId(), controller.getSessionId())) {
                     return;
                 }
 
                 // Check oldInfo
-                assertEquals(controller.getSessionId(), oldInfo.getSessionId());
+                assertEquals(controller.getSessionId(), oldInfo.getId());
                 assertEquals(1, oldInfo.getSelectedRoutes().size());
                 assertTrue(oldInfo.getSelectedRoutes().contains(ROUTE_ID1));
                 assertTrue(oldInfo.getTransferrableRoutes().contains(ROUTE_ID5_TO_TRANSFER_TO));
 
                 // Check newInfo
-                assertEquals(controller.getSessionId(), newInfo.getSessionId());
+                assertEquals(controller.getSessionId(), newInfo.getId());
                 assertEquals(1, newInfo.getSelectedRoutes().size());
                 assertFalse(newInfo.getSelectedRoutes().contains(ROUTE_ID1));
                 assertTrue(newInfo.getSelectedRoutes().contains(ROUTE_ID5_TO_TRANSFER_TO));
@@ -665,7 +668,8 @@
             public void onSessionInfoChanged(RouteSessionController controller,
                     RouteSessionInfo oldInfo, RouteSessionInfo newInfo) {
                 if (onSessionCreatedLatch.getCount() != 0
-                        || controllers.get(0).getSessionId() != controller.getSessionId()) {
+                        || !TextUtils.equals(
+                                controllers.get(0).getSessionId(), controller.getSessionId())) {
                     return;
                 }
                 onSessionInfoChangedLatch.countDown();
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
index c23a5b0..9ff9177c 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
@@ -99,7 +99,7 @@
     public static final String TYPE_SPECIAL =
             "com.android.mediarouteprovider.TYPE_SPECIAL";
 
-    private static final String TYPE_LIVE_AUDIO = "android.media.intent.category.LIVE_AUDIO";
+    private static final String TYPE_LIVE_AUDIO = "android.media.intent.route.TYPE_LIVE_AUDIO";
 
     private static final int TIMEOUT_MS = 5000;
 
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteSessionTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteSessionTest.java
index 2e81a64..9971fc3 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteSessionTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteSessionTest.java
@@ -29,6 +29,7 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class RouteSessionTest {
+    private static final String TEST_SESSION_ID = "test_session_id";
     private static final String TEST_PACKAGE_NAME = "com.android.mediaroutertest";
     private static final String TEST_CONTROL_CATEGORY = "com.android.mediaroutertest.category";
 
@@ -36,17 +37,17 @@
 
     @Test
     public void testValidity() {
-        RouteSessionInfo emptyPackageSession = new RouteSessionInfo.Builder(1,
+        RouteSessionInfo emptyPackageSession = new RouteSessionInfo.Builder(TEST_SESSION_ID,
                 "",
                 TEST_CONTROL_CATEGORY)
                 .addSelectedRoute(TEST_ROUTE_ID1)
                 .build();
-        RouteSessionInfo emptyCategorySession = new RouteSessionInfo.Builder(1,
+        RouteSessionInfo emptyCategorySession = new RouteSessionInfo.Builder(TEST_SESSION_ID,
                 TEST_PACKAGE_NAME, "")
                 .addSelectedRoute(TEST_ROUTE_ID1)
                 .build();
 
-        RouteSessionInfo emptySelectedRouteSession = new RouteSessionInfo.Builder(1,
+        RouteSessionInfo emptySelectedRouteSession = new RouteSessionInfo.Builder(TEST_SESSION_ID,
                 TEST_PACKAGE_NAME, TEST_CONTROL_CATEGORY)
                 .build();
 
@@ -54,9 +55,9 @@
                 .addSelectedRoute(TEST_ROUTE_ID1)
                 .build();
 
-        assertFalse(emptySelectedRouteSession.isValid());
         assertFalse(emptyPackageSession.isValid());
         assertFalse(emptyCategorySession.isValid());
+        assertFalse(emptySelectedRouteSession.isValid());
         assertTrue(validSession.isValid());
     }
 }
diff --git a/mms/OWNERS b/mms/OWNERS
index ba00d5d..befc320 100644
--- a/mms/OWNERS
+++ b/mms/OWNERS
@@ -12,3 +12,5 @@
 shuoq@google.com
 refuhoo@google.com
 nazaninb@google.com
+sarahchin@google.com
+dbright@google.com
\ No newline at end of file
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 1ee8518..3c3ebe2 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -110,7 +110,6 @@
 import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
-import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder;
 import com.android.systemui.statusbar.phone.AutoHideController;
 import com.android.systemui.statusbar.phone.BiometricUnlockController;
 import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
@@ -277,7 +276,6 @@
             NotificationGutsManager notificationGutsManager,
             NotificationLogger notificationLogger,
             NotificationEntryManager notificationEntryManager,
-            NotificationRowContentBinder notificationRowContentBinder,
             NotificationInterruptionStateProvider notificationInterruptionStateProvider,
             NotificationViewHierarchyManager notificationViewHierarchyManager,
             KeyguardViewMediator keyguardViewMediator,
@@ -365,7 +363,6 @@
                 notificationGutsManager,
                 notificationLogger,
                 notificationEntryManager,
-                notificationRowContentBinder,
                 notificationInterruptionStateProvider,
                 notificationViewHierarchyManager,
                 keyguardViewMediator,
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
index 7108e65..a1eccce 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
@@ -70,7 +70,6 @@
 import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
-import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder;
 import com.android.systemui.statusbar.phone.AutoHideController;
 import com.android.systemui.statusbar.phone.BiometricUnlockController;
 import com.android.systemui.statusbar.phone.DozeParameters;
@@ -147,7 +146,6 @@
             NotificationGutsManager notificationGutsManager,
             NotificationLogger notificationLogger,
             NotificationEntryManager notificationEntryManager,
-            NotificationRowContentBinder notificationRowContentBinder,
             NotificationInterruptionStateProvider notificationInterruptionStateProvider,
             NotificationViewHierarchyManager notificationViewHierarchyManager,
             KeyguardViewMediator keyguardViewMediator,
@@ -234,7 +232,6 @@
                 notificationGutsManager,
                 notificationLogger,
                 notificationEntryManager,
-                notificationRowContentBinder,
                 notificationInterruptionStateProvider,
                 notificationViewHierarchyManager,
                 keyguardViewMediator,
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
index a2bd210..a784e04 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
@@ -148,17 +151,18 @@
     }
 
     public boolean connect(BluetoothDevice device) {
-        if (mService == null) return false;
-        return mService.connect(device);
+        if (mService == null) {
+            return false;
+        }
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
     }
 
     public boolean disconnect(BluetoothDevice device) {
-        if (mService == null) return false;
-        // Downgrade priority as user is disconnecting the headset.
-        if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        if (mService == null) {
+            return false;
         }
-        return mService.disconnect(device);
+
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
     }
 
     public int getConnectionStatus(BluetoothDevice device) {
@@ -182,12 +186,12 @@
         if (mService == null) {
             return false;
         }
-        return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
     public int getPreferred(BluetoothDevice device) {
         if (mService == null) {
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
@@ -197,11 +201,11 @@
             return;
         }
         if (preferred) {
-            if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+            if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
     }
     boolean isA2dpPlaying() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
index bc03c34..8ca5a74 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothA2dpSink;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
@@ -116,18 +119,15 @@
         if (mService == null) {
             return false;
         }
-        return mService.connect(device);
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
     }
 
     public boolean disconnect(BluetoothDevice device) {
         if (mService == null) {
             return false;
         }
-        // Downgrade priority as user is disconnecting the headset.
-        if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
-        }
-        return mService.disconnect(device);
+
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
     }
 
     public int getConnectionStatus(BluetoothDevice device) {
@@ -141,12 +141,12 @@
         if (mService == null) {
             return false;
         }
-        return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
     public int getPreferred(BluetoothDevice device) {
         if (mService == null) {
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
@@ -156,11 +156,11 @@
             return;
         }
         if (preferred) {
-            if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+            if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
index 560cb3b..d65b5da 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
@@ -112,18 +115,15 @@
         if (mService == null) {
             return false;
         }
-        return mService.connect(device);
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
     }
 
     public boolean disconnect(BluetoothDevice device) {
         if (mService == null) {
             return false;
         }
-        // Downgrade priority as user is disconnecting the headset.
-        if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
-        }
-        return mService.disconnect(device);
+
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
     }
 
     public int getConnectionStatus(BluetoothDevice device) {
@@ -165,12 +165,12 @@
         if (mService == null) {
             return false;
         }
-        return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
     public int getPreferred(BluetoothDevice device) {
         if (mService == null) {
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
@@ -180,11 +180,11 @@
             return;
         }
         if (preferred) {
-            if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+            if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
index b4b55f3..9f1af66 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
@@ -146,17 +149,18 @@
     }
 
     public boolean connect(BluetoothDevice device) {
-        if (mService == null) return false;
-        return mService.connect(device);
+        if (mService == null) {
+            return false;
+        }
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
     }
 
     public boolean disconnect(BluetoothDevice device) {
-        if (mService == null) return false;
-        // Downgrade priority as user is disconnecting the hearing aid.
-        if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        if (mService == null) {
+            return false;
         }
-        return mService.disconnect(device);
+
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
     }
 
     public int getConnectionStatus(BluetoothDevice device) {
@@ -180,12 +184,12 @@
         if (mService == null) {
             return false;
         }
-        return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
     public int getPreferred(BluetoothDevice device) {
         if (mService == null) {
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
@@ -195,11 +199,11 @@
             return;
         }
         if (preferred) {
-            if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+            if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java
index a372e23..678f2e3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
@@ -126,7 +129,7 @@
         if (mService == null) {
             return false;
         }
-        return mService.connect(device);
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
     }
 
     @Override
@@ -134,11 +137,8 @@
         if (mService == null) {
             return false;
         }
-        // Downgrade priority as user is disconnecting the headset.
-        if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
-        }
-        return mService.disconnect(device);
+
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
     }
 
     @Override
@@ -154,13 +154,13 @@
         if (mService == null) {
             return false;
         }
-        return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
     @Override
     public int getPreferred(BluetoothDevice device) {
         if (mService == null) {
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
@@ -171,11 +171,11 @@
             return;
         }
         if (preferred) {
-            if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+            if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
index 975a1e6..588083e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
@@ -99,13 +102,17 @@
     }
 
     public boolean connect(BluetoothDevice device) {
-        if (mService == null) return false;
-        return mService.connect(device);
+        if (mService == null) {
+            return false;
+        }
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
     }
 
     public boolean disconnect(BluetoothDevice device) {
-        if (mService == null) return false;
-        return mService.disconnect(device);
+        if (mService == null) {
+            return false;
+        }
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
     }
 
     public int getConnectionStatus(BluetoothDevice device) {
@@ -119,12 +126,12 @@
         if (mService == null) {
             return false;
         }
-        return mService.getConnectionPolicy(device) != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return mService.getConnectionPolicy(device) != CONNECTION_POLICY_FORBIDDEN;
     }
 
     public int getPreferred(BluetoothDevice device) {
         if (mService == null) {
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
@@ -132,11 +139,11 @@
     public void setPreferred(BluetoothDevice device, boolean preferred) {
         if (mService == null) return;
         if (preferred) {
-            if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+            if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java
index 95139a1..7d121aa 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
@@ -115,18 +118,15 @@
         if (mService == null) {
             return false;
         }
-        return mService.connect(device);
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
     }
 
     public boolean disconnect(BluetoothDevice device) {
         if (mService == null) {
             return false;
         }
-        // Downgrade priority as user is disconnecting.
-        if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
-        }
-        return mService.disconnect(device);
+
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
     }
 
     public int getConnectionStatus(BluetoothDevice device) {
@@ -140,12 +140,12 @@
         if (mService == null) {
             return false;
         }
-        return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
     public int getPreferred(BluetoothDevice device) {
         if (mService == null) {
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
@@ -155,11 +155,11 @@
             return;
         }
         if (preferred) {
-            if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+            if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
index 31a0eea..a96a4e7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
@@ -16,6 +16,8 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
@@ -119,10 +121,8 @@
         if (mService == null) {
             return false;
         }
-        if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
-        }
-        return mService.disconnect(device);
+
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
     }
 
     public int getConnectionStatus(BluetoothDevice device) {
@@ -136,12 +136,12 @@
         if (mService == null) {
             return false;
         }
-        return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
     public int getPreferred(BluetoothDevice device) {
         if (mService == null) {
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
@@ -155,7 +155,7 @@
                 mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
index 4ea0df6..56267fc 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
@@ -129,7 +132,7 @@
             return false;
         }
         Log.d(TAG,"PBAPClientProfile attempting to connect to " + device.getAddress());
-        return mService.connect(device);
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
     }
 
     public boolean disconnect(BluetoothDevice device) {
@@ -137,7 +140,7 @@
         if (mService == null) {
             return false;
         }
-        return mService.disconnect(device);
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
     }
 
     public int getConnectionStatus(BluetoothDevice device) {
@@ -151,12 +154,12 @@
         if (mService == null) {
             return false;
         }
-        return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
     public int getPreferred(BluetoothDevice device) {
         if (mService == null) {
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
@@ -166,11 +169,11 @@
             return;
         }
         if (preferred) {
-            if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+            if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
index 3f920a8..f7c0bf5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
@@ -16,6 +16,8 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
@@ -96,8 +98,10 @@
     }
 
     public boolean disconnect(BluetoothDevice device) {
-        if (mService == null) return false;
-        return mService.disconnect(device);
+        if (mService == null) {
+            return false;
+        }
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
     }
 
     public int getConnectionStatus(BluetoothDevice device) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java
index 0ca4d61..3022c5b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
@@ -112,17 +115,15 @@
         if (mService == null) {
             return false;
         }
-        return mService.connect(device);
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
     }
 
     public boolean disconnect(BluetoothDevice device) {
         if (mService == null) {
             return false;
         }
-        if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
-        }
-        return mService.disconnect(device);
+
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
     }
 
     public int getConnectionStatus(BluetoothDevice device) {
@@ -136,12 +137,12 @@
         if (mService == null) {
             return false;
         }
-        return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
     public int getPreferred(BluetoothDevice device) {
         if (mService == null) {
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
@@ -151,11 +152,11 @@
             return;
         }
         if (preferred) {
-            if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+            if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
     }
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java
index 976445e..ccb6646 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.verify;
@@ -67,13 +70,13 @@
     @Test
     public void connect_shouldConnectBluetoothA2dpSink() {
         mProfile.connect(mBluetoothDevice);
-        verify(mService).connect(mBluetoothDevice);
+        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED);
     }
 
     @Test
     public void disconnect_shouldDisconnectBluetoothA2dpSink() {
         mProfile.disconnect(mBluetoothDevice);
-        verify(mService).disconnect(mBluetoothDevice);
+        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN);
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java
index 69c020d..9180760 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.verify;
@@ -67,13 +70,13 @@
     @Test
     public void connect_shouldConnectBluetoothHeadsetClient() {
         mProfile.connect(mBluetoothDevice);
-        verify(mService).connect(mBluetoothDevice);
+        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED);
     }
 
     @Test
     public void disconnect_shouldDisconnectBluetoothHeadsetClient() {
         mProfile.disconnect(mBluetoothDevice);
-        verify(mService).disconnect(mBluetoothDevice);
+        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN);
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java
index 6f66709..1425c38 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.verify;
@@ -67,13 +70,13 @@
     @Test
     public void connect_shouldConnectBluetoothMapClient() {
         mProfile.connect(mBluetoothDevice);
-        verify(mService).connect(mBluetoothDevice);
+        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED);
     }
 
     @Test
     public void disconnect_shouldDisconnectBluetoothMapClient() {
         mProfile.disconnect(mBluetoothDevice);
-        verify(mService).disconnect(mBluetoothDevice);
+        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN);
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java
index b21ec9c3..15f560b 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.verify;
@@ -67,13 +70,13 @@
     @Test
     public void connect_shouldConnectBluetoothPbapClient() {
         mProfile.connect(mBluetoothDevice);
-        verify(mService).connect(mBluetoothDevice);
+        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED);
     }
 
     @Test
     public void disconnect_shouldDisconnectBluetoothPbapClient() {
         mProfile.disconnect(mBluetoothDevice);
-        verify(mService).disconnect(mBluetoothDevice);
+        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN);
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java
index ec88034..4f978a8 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.verify;
@@ -66,13 +69,13 @@
     @Test
     public void connect_shouldConnectBluetoothSap() {
         mProfile.connect(mBluetoothDevice);
-        verify(mService).connect(mBluetoothDevice);
+        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED);
     }
 
     @Test
     public void disconnect_shouldDisconnectBluetoothSap() {
         mProfile.disconnect(mBluetoothDevice);
-        verify(mService).disconnect(mBluetoothDevice);
+        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN);
     }
 
     @Test
diff --git a/packages/SystemUI/docs/broadcasts.md b/packages/SystemUI/docs/broadcasts.md
index 56a637f..28657f2 100644
--- a/packages/SystemUI/docs/broadcasts.md
+++ b/packages/SystemUI/docs/broadcasts.md
@@ -42,24 +42,29 @@
 
 ```kotlin
 /**
-    * Register a receiver for broadcast with the dispatcher
-    *
-    * @param receiver A receiver to dispatch the [Intent]
-    * @param filter A filter to determine what broadcasts should be dispatched to this receiver.
-    *               It will only take into account actions and categories for filtering. It must
-    *               have at least one action.
-    * @param handler A handler to dispatch [BroadcastReceiver.onReceive]. By default, it is the
-    *                main handler. Pass `null` to use the default.
-    * @param user A user handle to determine which broadcast should be dispatched to this receiver.
-    *             By default, it is the current user.
-    * @throws IllegalArgumentException if the filter has other constraints that are not actions or
-    *                                  categories or the filter has no actions.
-    */
+ * Register a receiver for broadcast with the dispatcher
+ *
+ * @param receiver A receiver to dispatch the [Intent]
+ * @param filter A filter to determine what broadcasts should be dispatched to this receiver.
+ *               It will only take into account actions and categories for filtering. It must
+ *               have at least one action.
+ * @param executor An executor to dispatch [BroadcastReceiver.onReceive]. Pass null to use an
+ *                 executor in the main thread (default).
+ * @param user A user handle to determine which broadcast should be dispatched to this receiver.
+ *             By default, it is the current user.
+ * @throws IllegalArgumentException if the filter has other constraints that are not actions or
+ *                                  categories or the filter has no actions.
+ */
 @JvmOverloads
-fun registerReceiver(BroadcastReceiver, IntentFilter, Handler? = mainHandler, UserHandle = context.user)
+fun registerReceiver(
+        BroadcastReceiver, 
+        IntentFilter, 
+        Executor? = context.mainExecutor,
+        UserHandle = context.user
+) {
 ```
 
-All subscriptions are done with the same overloaded method. As specified in the doc, in order to pass a `UserHandle` with the default `Handler`, pass `null` for the `Handler`.
+All subscriptions are done with the same overloaded method. As specified in the doc, in order to pass a `UserHandle` with the default `Executor`, pass `null` for the `Executor`.
 
 In the same way as with `Context`, subscribing the same `BroadcastReceiver` for the same user using different filters will result on two subscriptions, not in replacing the filter.
 
diff --git a/packages/SystemUI/res/layout/media_carousel.xml b/packages/SystemUI/res/layout/media_carousel.xml
new file mode 100644
index 0000000..e91f840f
--- /dev/null
+++ b/packages/SystemUI/res/layout/media_carousel.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 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
+  -->
+
+<!-- Carousel for media controls -->
+<HorizontalScrollView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/qs_media_height"
+    android:padding="@dimen/qs_media_padding"
+    android:scrollbars="none"
+    android:visibility="gone"
+    >
+    <LinearLayout
+        android:id="@+id/media_carousel"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        >
+        <!-- QSMediaPlayers will be added here dynamically -->
+    </LinearLayout>
+</HorizontalScrollView>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 431862f..a58e3d7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1638,7 +1638,7 @@
         filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
         filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
         filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
-        broadcastDispatcher.registerReceiver(mBroadcastReceiver, filter, mHandler);
+        broadcastDispatcher.registerReceiverWithHandler(mBroadcastReceiver, filter, mHandler);
 
         final IntentFilter allUserFilter = new IntentFilter();
         allUserFilter.addAction(Intent.ACTION_USER_INFO_CHANGED);
@@ -1649,8 +1649,8 @@
         allUserFilter.addAction(ACTION_USER_UNLOCKED);
         allUserFilter.addAction(ACTION_USER_STOPPED);
         allUserFilter.addAction(ACTION_USER_REMOVED);
-        broadcastDispatcher.registerReceiver(mBroadcastAllReceiver, allUserFilter, mHandler,
-                UserHandle.ALL);
+        broadcastDispatcher.registerReceiverWithHandler(mBroadcastAllReceiver, allUserFilter,
+                mHandler, UserHandle.ALL);
 
         mSubscriptionManager.addOnSubscriptionsChangedListener(mSubscriptionListener);
         try {
diff --git a/packages/SystemUI/src/com/android/systemui/DumpController.kt b/packages/SystemUI/src/com/android/systemui/DumpController.kt
index 8c7075b..f14c4cd 100644
--- a/packages/SystemUI/src/com/android/systemui/DumpController.kt
+++ b/packages/SystemUI/src/com/android/systemui/DumpController.kt
@@ -30,6 +30,14 @@
 /**
  * Controller that allows any [Dumpable] to subscribe and be dumped along with other SystemUI
  * dependencies.
+ *
+ * To dump a specific dumpable on-demand:
+ *
+ * ```
+ * $ adb shell dumpsys activity service com.android.systemui/.SystemUIService dependency DumpController <tag1>,<tag2>,<tag3>
+ * ```
+ *
+ * Where tag1, tag2, etc. are the tags of the dumpables you want to dump.
  */
 @Singleton
 class DumpController @Inject constructor() : Dumpable {
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 0e736dc..c533755 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -266,7 +266,7 @@
 
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_USER_SWITCHED);
-        mBroadcastDispatcher.registerReceiver(mIntentReceiver, filter, mHandler);
+        mBroadcastDispatcher.registerReceiverWithHandler(mIntentReceiver, filter, mHandler);
 
         mOverlay.addOnLayoutChangeListener(new OnLayoutChangeListener() {
             @Override
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
index 8cb0cc5..cedf7c3 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
@@ -20,6 +20,7 @@
 import android.content.Context
 import android.content.IntentFilter
 import android.os.Handler
+import android.os.HandlerExecutor
 import android.os.Looper
 import android.os.Message
 import android.os.UserHandle
@@ -32,13 +33,14 @@
 import com.android.systemui.dagger.qualifiers.Main
 import java.io.FileDescriptor
 import java.io.PrintWriter
+import java.util.concurrent.Executor
 import javax.inject.Inject
 import javax.inject.Singleton
 
 data class ReceiverData(
     val receiver: BroadcastReceiver,
     val filter: IntentFilter,
-    val handler: Handler,
+    val executor: Executor,
     val user: UserHandle
 )
 
@@ -76,8 +78,33 @@
      * @param filter A filter to determine what broadcasts should be dispatched to this receiver.
      *               It will only take into account actions and categories for filtering. It must
      *               have at least one action.
-     * @param handler A handler to dispatch [BroadcastReceiver.onReceive]. By default, it is the
-     *                main handler. Pass `null` to use the default.
+     * @param handler A handler to dispatch [BroadcastReceiver.onReceive].
+     * @param user A user handle to determine which broadcast should be dispatched to this receiver.
+     *             By default, it is the current user.
+     * @throws IllegalArgumentException if the filter has other constraints that are not actions or
+     *                                  categories or the filter has no actions.
+     */
+    @Deprecated(message = "Replacing Handler for Executor in SystemUI",
+            replaceWith = ReplaceWith("registerReceiver(receiver, filter, executor, user)"))
+    @JvmOverloads
+    fun registerReceiverWithHandler(
+        receiver: BroadcastReceiver,
+        filter: IntentFilter,
+        handler: Handler,
+        user: UserHandle = context.user
+    ) {
+        registerReceiver(receiver, filter, HandlerExecutor(handler), user)
+    }
+
+    /**
+     * Register a receiver for broadcast with the dispatcher
+     *
+     * @param receiver A receiver to dispatch the [Intent]
+     * @param filter A filter to determine what broadcasts should be dispatched to this receiver.
+     *               It will only take into account actions and categories for filtering. It must
+     *               have at least one action.
+     * @param executor An executor to dispatch [BroadcastReceiver.onReceive]. Pass null to use an
+     *                 executor in the main thread (default).
      * @param user A user handle to determine which broadcast should be dispatched to this receiver.
      *             By default, it is the current user.
      * @throws IllegalArgumentException if the filter has other constraints that are not actions or
@@ -85,15 +112,15 @@
      */
     @JvmOverloads
     fun registerReceiver(
-        receiver: BroadcastReceiver,
-        filter: IntentFilter,
-        handler: Handler? = mainHandler,
-        user: UserHandle = context.user
+            receiver: BroadcastReceiver,
+            filter: IntentFilter,
+            executor: Executor? = context.mainExecutor,
+            user: UserHandle = context.user
     ) {
         checkFilter(filter)
         this.handler
                 .obtainMessage(MSG_ADD_RECEIVER,
-                ReceiverData(receiver, filter, handler ?: mainHandler, user))
+                        ReceiverData(receiver, filter, executor ?: context.mainExecutor, user))
                 .sendToTarget()
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
index b2942bb..0c631aa 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
@@ -193,7 +193,7 @@
                         it.filter.hasAction(intent.action) &&
                             it.filter.matchCategories(intent.categories) == null }
                     ?.forEach {
-                        it.handler.post {
+                        it.executor.execute {
                             if (DEBUG) Log.w(TAG,
                                     "[$index] Dispatching ${intent.action} to ${it.receiver}")
                             it.receiver.pendingResult = pendingResult
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
index 59d68bc..6528f37 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
@@ -97,13 +97,11 @@
     private boolean mSpringingBubbleToTouch = false;
 
     private int mExpandedViewPadding;
-    private float mLauncherGridDiff;
 
     public ExpandedAnimationController(Point displaySize, int expandedViewPadding,
             int orientation) {
         updateOrientation(orientation, displaySize);
         mExpandedViewPadding = expandedViewPadding;
-        mLauncherGridDiff = 30f;
     }
 
     /**
@@ -569,15 +567,7 @@
      * @return Space between bubbles in row above expanded view.
      */
     private float getSpaceBetweenBubbles() {
-        /**
-         * Ordered left to right:
-         *  Screen edge
-         *      [mExpandedViewPadding]
-         *  Expanded view edge
-         *      [launcherGridDiff] --- arbitrary value until launcher exports widths
-         *  Launcher's app icon grid edge that we must match
-         */
-        final float rowMargins = (mExpandedViewPadding + mLauncherGridDiff) * 2;
+        final float rowMargins = mExpandedViewPadding * 2;
         final float maxRowWidth = getWidthForDisplayingBubbles() - rowMargins;
 
         final float totalBubbleWidth = mBubblesMaxRendered * mBubbleSizePx;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
index c9faf69..4e88726 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
@@ -91,7 +91,7 @@
         if (mDebuggable) {
             IntentFilter filter = new IntentFilter();
             filter.addAction(ACTION_AOD_BRIGHTNESS);
-            mBroadcastDispatcher.registerReceiver(this, filter, handler, UserHandle.ALL);
+            mBroadcastDispatcher.registerReceiverWithHandler(this, filter, handler, UserHandle.ALL);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index 59ac329..48750fa 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -233,7 +233,7 @@
             filter.addAction(Intent.ACTION_SCREEN_OFF);
             filter.addAction(Intent.ACTION_SCREEN_ON);
             filter.addAction(Intent.ACTION_USER_SWITCHED);
-            mBroadcastDispatcher.registerReceiver(this, filter, mHandler);
+            mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mHandler);
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt
index f710f7f..f66a1ec 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt
@@ -68,7 +68,7 @@
     override fun updateResources(): Boolean {
         with(mContext.resources) {
             smallTileSize = getDimensionPixelSize(R.dimen.qs_quick_tile_size)
-            cellMarginHorizontal = getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal)
+            cellMarginHorizontal = getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal) / 2
             cellMarginVertical = getDimensionPixelSize(R.dimen.new_qs_vertical_margin)
         }
         requestLayout()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
index f7e4c79..1077834e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
@@ -124,7 +124,7 @@
                     }
                 }
             });
-            btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_replay));
+            btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_play));
             btn.setImageTintList(ColorStateList.valueOf(mForegroundColor));
             btn.setVisibility(View.VISIBLE);
 
@@ -199,8 +199,7 @@
         List<ResolveInfo> info = pm.queryBroadcastReceiversAsUser(it, 0, mContext.getUser());
         if (info != null) {
             for (ResolveInfo inf : info) {
-                if (inf.activityInfo.packageName.equals(notif.contentIntent.getCreatorPackage())) {
-                    Log.d(TAG, "Found receiver for package: " + inf);
+                if (inf.activityInfo.packageName.equals(mController.getPackageName())) {
                     mRecvComponent = inf.getComponentInfo().getComponentName();
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 51e352b..35b8312 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -184,21 +184,10 @@
 
         // Add media carousel
         if (useQsMediaPlayer(context)) {
-            HorizontalScrollView mediaScrollView = new HorizontalScrollView(mContext);
-            mediaScrollView.setHorizontalScrollBarEnabled(false);
-            int playerHeight = (int) getResources().getDimension(R.dimen.qs_media_height);
-            int padding = (int) getResources().getDimension(R.dimen.qs_media_padding);
-            LayoutParams lpView = new LayoutParams(LayoutParams.MATCH_PARENT, playerHeight);
-            lpView.setMarginStart(padding);
-            lpView.setMarginEnd(padding);
-            addView(mediaScrollView, lpView);
-
-            LayoutParams lpCarousel = new LayoutParams(LayoutParams.MATCH_PARENT,
-                    LayoutParams.WRAP_CONTENT);
-            mMediaCarousel = new LinearLayout(mContext);
-            mMediaCarousel.setOrientation(LinearLayout.HORIZONTAL);
-            mediaScrollView.addView(mMediaCarousel, lpCarousel);
-            mediaScrollView.setVisibility(View.GONE);
+            HorizontalScrollView mediaScrollView = (HorizontalScrollView) LayoutInflater.from(
+                    mContext).inflate(R.layout.media_carousel, this, false);
+            mMediaCarousel = mediaScrollView.findViewById(R.id.media_carousel);
+            addView(mediaScrollView);
         } else {
             mMediaCarousel = null;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
index d40e250..cec1cb2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
@@ -106,7 +106,7 @@
                     }
                 }
             });
-            btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_replay));
+            btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_play));
             btn.setImageTintList(ColorStateList.valueOf(mForegroundColor));
             btn.setVisibility(View.VISIBLE);
         }
@@ -136,14 +136,25 @@
      * @param actionsContainer a LinearLayout containing the media action buttons
      * @param actionsToShow indices of which actions to display in the mini player
      *                      (max 3: Notification.MediaStyle.MAX_MEDIA_BUTTONS_IN_COMPACT)
+     * @param contentIntent Intent to send when user taps on the view
      */
     public void setMediaSession(MediaSession.Token token, Icon icon, int iconColor, int bgColor,
-            View actionsContainer, int[] actionsToShow) {
-        Log.d(TAG, "Setting media session: " + token);
+            View actionsContainer, int[] actionsToShow, PendingIntent contentIntent) {
         mToken = token;
         mForegroundColor = iconColor;
         mBackgroundColor = bgColor;
-        mController = new MediaController(mContext, token);
+
+        String oldPackage = "";
+        if (mController != null) {
+            oldPackage = mController.getPackageName();
+        }
+        MediaController controller = new MediaController(mContext, token);
+        boolean samePlayer = mToken.equals(token) && oldPackage.equals(controller.getPackageName());
+        if (mController != null && !samePlayer && !isPlaying(controller)) {
+            // Only update if this is a different session and currently playing
+            return;
+        }
+        mController = controller;
         MediaMetadata mMediaMetadata = mController.getMetadata();
 
         // Try to find a receiver for the media button that matches this app
@@ -153,7 +164,6 @@
         if (info != null) {
             for (ResolveInfo inf : info) {
                 if (inf.activityInfo.packageName.equals(mController.getPackageName())) {
-                    Log.d(TAG, "Found receiver for package: " + inf);
                     mRecvComponent = inf.getComponentInfo().getComponentName();
                 }
             }
@@ -165,6 +175,16 @@
             return;
         }
 
+        // Action
+        mMediaNotifView.setOnClickListener(v -> {
+            try {
+                contentIntent.send();
+                mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+            } catch (PendingIntent.CanceledException e) {
+                Log.e(TAG, "Pending intent was canceled: " + e.getMessage());
+            }
+        });
+
         // Album art
         addAlbumArtBackground(mMediaMetadata, mBackgroundColor);
 
@@ -237,12 +257,12 @@
      * Check whether the media controlled by this player is currently playing
      * @return whether it is playing, or false if no controller information
      */
-    public boolean isPlaying() {
-        if (mController == null) {
+    public boolean isPlaying(MediaController controller) {
+        if (controller == null) {
             return false;
         }
 
-        PlaybackState state = mController.getPlaybackState();
+        PlaybackState state = controller.getPlaybackState();
         if (state == null) {
             return false;
         }
@@ -261,12 +281,11 @@
     private void addAlbumArtBackground(MediaMetadata metadata, int bgColor) {
         Bitmap albumArt = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
         float radius = mContext.getResources().getDimension(R.dimen.qs_media_corner_radius);
-        if (albumArt != null) {
-            Rect bounds = new Rect();
-            mMediaNotifView.getBoundsOnScreen(bounds);
-            int width = bounds.width();
-            int height = bounds.height();
-
+        Rect bounds = new Rect();
+        mMediaNotifView.getBoundsOnScreen(bounds);
+        int width = bounds.width();
+        int height = bounds.height();
+        if (albumArt != null && width > 0 && height > 0) {
             Bitmap original = albumArt.copy(Bitmap.Config.ARGB_8888, true);
             Bitmap scaled = scaleBitmap(original, width, height);
             Canvas canvas = new Canvas(scaled);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index db52e7d..b05d4fd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -85,20 +85,19 @@
             mHorizontalLinearLayout.setClipChildren(false);
             mHorizontalLinearLayout.setClipToPadding(false);
 
-            LayoutParams lp = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1);
-
             mTileLayout = new DoubleLineTileLayout(context);
             mMediaTileLayout = mTileLayout;
             mRegularTileLayout = new HeaderTileLayout(context);
+            LayoutParams lp = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1);
             lp.setMarginEnd(10);
             lp.setMarginStart(0);
             mHorizontalLinearLayout.addView((View) mTileLayout, lp);
 
             mMediaPlayer = new QuickQSMediaPlayer(mContext, mHorizontalLinearLayout);
-
-            lp.setMarginEnd(0);
-            lp.setMarginStart(10);
-            mHorizontalLinearLayout.addView(mMediaPlayer.getView(), lp);
+            LayoutParams lp2 = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1);
+            lp2.setMarginEnd(0);
+            lp2.setMarginStart(25);
+            mHorizontalLinearLayout.addView(mMediaPlayer.getView(), lp2);
 
             sDefaultMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns);
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
index bbff117..ad79cad 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
@@ -322,7 +322,7 @@
         filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
         try {
             mUserReceiverRegistered.set(true);
-            mBroadcastDispatcher.registerReceiver(this, filter, mHandler, mUser);
+            mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mHandler, mUser);
         } catch (Exception ex) {
             mUserReceiverRegistered.set(false);
             Log.e(TAG, "Could not register unlock receiver", ex);
diff --git a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java
index 077d260..9599d77 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java
@@ -98,7 +98,8 @@
             if (!mReceiverRegistered) {
                 mCurrentUserId = ActivityManager.getCurrentUser();
                 IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
-                mBroadcastDispatcher.registerReceiver(this, filter, null, UserHandle.ALL);
+                mBroadcastDispatcher.registerReceiver(this, filter, null,
+                        UserHandle.ALL);
                 mReceiverRegistered = true;
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index 667e721..43d0399 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -584,15 +584,7 @@
 
     public void bindRow(ExpandableNotificationRow row) {
         row.setRemoteInputController(mRemoteInputController);
-    }
-
-    /**
-     * Return on-click handler for notification remote views
-     *
-     * @return on-click handler
-     */
-    public RemoteViews.OnClickHandler getRemoteViewsOnClickHandler() {
-        return mOnClickHandler;
+        row.setRemoteViewClickHandler(mOnClickHandler);
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java
index ec8dbea..d1f6ebf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java
@@ -18,8 +18,6 @@
 
 import android.content.Context;
 
-import com.android.systemui.statusbar.notification.row.NotificationRowModule;
-
 import javax.inject.Singleton;
 
 import dagger.Module;
@@ -28,7 +26,7 @@
 /**
  * Dagger Module providing common dependencies of StatusBar.
  */
-@Module(includes = {NotificationRowModule.class})
+@Module
 public class StatusBarDependenciesModule {
     /**
      * Provides our instance of CommandQueue which is considered optional.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java
index ec1efa5..b960b42 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java
@@ -24,7 +24,6 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
-import java.util.Objects;
 
 /**
  * Represents a set of grouped notifications. The final notification list is usually a mix of
@@ -58,22 +57,15 @@
 
     @VisibleForTesting
     public void setSummary(@Nullable NotificationEntry summary) {
-        if (!Objects.equals(mSummary, summary)) {
-            mSummary = summary;
-            onGroupingUpdated();
-        }
+        mSummary = summary;
     }
 
     void clearChildren() {
-        if (mChildren.size() != 0) {
-            mChildren.clear();
-            onGroupingUpdated();
-        }
+        mChildren.clear();
     }
 
     void addChild(NotificationEntry child) {
         mChildren.add(child);
-        onGroupingUpdated();
     }
 
     void sortChildren(Comparator<? super NotificationEntry> c) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
index 601b3e0..dc68c4b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
@@ -18,38 +18,20 @@
 
 import android.annotation.Nullable;
 
-import com.android.systemui.Dependency;
-import com.android.systemui.statusbar.notification.collection.provider.DerivedMember;
-import com.android.systemui.statusbar.notification.collection.provider.IsHighPriorityProvider;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.Objects;
-
 /**
  * Abstract superclass for top-level entries, i.e. things that can appear in the final notification
  * list shown to users. In practice, this means either GroupEntries or NotificationEntries.
  */
 public abstract class ListEntry {
     private final String mKey;
-    private final IsHighPriorityProvider mIsHighPriorityProvider = new IsHighPriorityProvider();
-    private final List<DerivedMember> mDerivedMemberList = Arrays.asList(mIsHighPriorityProvider);
 
     @Nullable private GroupEntry mParent;
     @Nullable private GroupEntry mPreviousParent;
     private int mSection;
     int mFirstAddedIteration = -1;
 
-    // TODO: (b/145659174) remove groupManager when moving to NewNotifPipeline. Logic
-    //  replaced in GroupEntry and NotifListBuilderImpl
-    private final NotificationGroupManager mGroupManager;
-
     ListEntry(String key) {
         mKey = key;
-
-        // TODO: (b/145659174) remove
-        mGroupManager = Dependency.get(NotificationGroupManager.class);
     }
 
     public String getKey() {
@@ -68,11 +50,7 @@
     }
 
     void setParent(@Nullable GroupEntry parent) {
-        if (!Objects.equals(mParent, parent)) {
-            invalidateParent();
-            mParent = parent;
-            onGroupingUpdated();
-        }
+        mParent = parent;
     }
 
     @Nullable public GroupEntry getPreviousParent() {
@@ -91,58 +69,4 @@
     void setSection(int section) {
         mSection = section;
     }
-
-    /**
-     * Resets the cached values of DerivedMembers.
-     */
-    void invalidateDerivedMembers() {
-        for (int i = 0; i < mDerivedMemberList.size(); i++) {
-            mDerivedMemberList.get(i).invalidate();
-        }
-    }
-
-    /**
-     * Whether this notification is shown to the user as a high priority notification: visible on
-     * the lock screen/status bar and in the top section in the shade.
-     */
-    public boolean isHighPriority() {
-        return mIsHighPriorityProvider.get(this);
-    }
-
-    private void invalidateParent() {
-        // invalidate our parent (GroupEntry) since DerivedMembers may be dependent on children
-        if (getParent() != null) {
-            getParent().invalidateDerivedMembers();
-        }
-
-        // TODO: (b/145659174) remove
-        final NotificationEntry notifEntry = getRepresentativeEntry();
-        if (notifEntry != null && mGroupManager.isGroupChild(notifEntry.getSbn())) {
-            NotificationEntry summary = mGroupManager.getLogicalGroupSummary(notifEntry.getSbn());
-            if (summary != null) {
-                summary.invalidateDerivedMembers();
-            }
-        }
-    }
-
-    void onGroupingUpdated() {
-        for (int i = 0; i < mDerivedMemberList.size(); i++) {
-            mDerivedMemberList.get(i).onGroupingUpdated();
-        }
-        invalidateParent();
-    }
-
-    void onSbnUpdated() {
-        for (int i = 0; i < mDerivedMemberList.size(); i++) {
-            mDerivedMemberList.get(i).onSbnUpdated();
-        }
-        invalidateParent();
-    }
-
-    void onRankingUpdated() {
-        for (int i = 0; i < mDerivedMemberList.size(); i++) {
-            mDerivedMemberList.get(i).onRankingUpdated();
-        }
-        invalidateParent();
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 28e486d..7301fe1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -204,11 +204,8 @@
                     + " doesn't match existing key " + mKey);
         }
 
-        if (!Objects.equals(mSbn, sbn)) {
-            mSbn = sbn;
-            mBubbleMetadata = mSbn.getNotification().getBubbleMetadata();
-            onSbnUpdated();
-        }
+        mSbn = sbn;
+        mBubbleMetadata = mSbn.getNotification().getBubbleMetadata();
     }
 
     /**
@@ -233,10 +230,7 @@
                     + " doesn't match existing key " + mKey);
         }
 
-        if (!Objects.equals(mRanking, ranking)) {
-            mRanking = ranking;
-            onRankingUpdated();
-        }
+        mRanking = ranking;
     }
 
     /*
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
index 7010943..3bbd722 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.statusbar.NotificationMediaManager
 import com.android.systemui.statusbar.notification.NotificationFilter
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
 import com.android.systemui.statusbar.notification.logging.NotifEvent
 import com.android.systemui.statusbar.notification.logging.NotifLog
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
@@ -54,7 +55,8 @@
     private val notifFilter: NotificationFilter,
     private val notifLog: NotifLog,
     sectionsFeatureManager: NotificationSectionsFeatureManager,
-    private val peopleNotificationIdentifier: PeopleNotificationIdentifier
+    private val peopleNotificationIdentifier: PeopleNotificationIdentifier,
+    private val highPriorityProvider: HighPriorityProvider
 ) {
 
     var rankingMap: RankingMap? = null
@@ -81,6 +83,9 @@
         val aHeadsUp = a.isRowHeadsUp
         val bHeadsUp = b.isRowHeadsUp
 
+        val aIsHighPriority = a.isHighPriority()
+        val bIsHighPriority = b.isHighPriority()
+
         when {
             usePeopleFiltering && aIsPeople != bIsPeople -> if (aIsPeople) -1 else 1
             aHeadsUp != bHeadsUp -> if (aHeadsUp) -1 else 1
@@ -90,8 +95,8 @@
             aMedia != bMedia -> if (aMedia) -1 else 1
             // Upsort PRIORITY_MAX system notifications
             aSystemMax != bSystemMax -> if (aSystemMax) -1 else 1
-            a.isHighPriority != b.isHighPriority ->
-                -1 * a.isHighPriority.compareTo(b.isHighPriority)
+            aIsHighPriority != bIsHighPriority ->
+                -1 * aIsHighPriority.compareTo(bIsHighPriority)
             aRank != bRank -> aRank - bRank
             else -> nb.notification.`when`.compareTo(na.notification.`when`)
         }
@@ -154,7 +159,7 @@
     ) {
         if (usePeopleFiltering && entry.isPeopleNotification()) {
             entry.bucket = BUCKET_PEOPLE
-        } else if (isHeadsUp || isMedia || isSystemMax || entry.isHighPriority) {
+        } else if (isHeadsUp || isMedia || isSystemMax || entry.isHighPriority()) {
             entry.bucket = BUCKET_ALERTING
         } else {
             entry.bucket = BUCKET_SILENT
@@ -178,10 +183,6 @@
                         // TODO: notify group manager here?
                         groupManager.onEntryUpdated(entry, oldSbn)
                     }
-
-                    // TODO: (b/145659174) remove after moving to new NotifPipeline
-                    // (should be able to remove all groupManager code post-migration)
-                    entry.invalidateDerivedMembers()
                 }
             }
         }
@@ -191,6 +192,9 @@
             sbn.isPeopleNotification()
     private fun StatusBarNotification.isPeopleNotification() =
             peopleNotificationIdentifier.isPeopleNotification(this)
+
+    private fun NotificationEntry.isHighPriority() =
+            highPriorityProvider.isHighPriority(this)
 }
 
 // Convenience functions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java
index 20f206b..6dc647d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java
@@ -41,8 +41,8 @@
 import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.NotificationContentInflater;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
-import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder;
 import com.android.systemui.statusbar.notification.row.RowInflaterTask;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -65,7 +65,6 @@
             Dependency.get(NotificationInterruptionStateProvider.class);
 
     private final Context mContext;
-    private final NotificationRowContentBinder mRowContentBinder;
     private final NotificationMessagingUtil mMessagingUtil;
     private final ExpandableNotificationRow.ExpansionLogger mExpansionLogger =
             this::logNotificationExpansion;
@@ -77,7 +76,7 @@
     private NotificationPresenter mPresenter;
     private NotificationListContainer mListContainer;
     private HeadsUpManager mHeadsUpManager;
-    private NotificationRowContentBinder.InflationCallback mInflationCallback;
+    private NotificationContentInflater.InflationCallback mInflationCallback;
     private ExpandableNotificationRow.OnAppOpsClickListener mOnAppOpsClickListener;
     private BindRowCallback mBindRowCallback;
     private NotificationClicker mNotificationClicker;
@@ -85,13 +84,11 @@
 
     public NotificationRowBinderImpl(
             Context context,
-            NotificationRowContentBinder rowContentBinder,
             boolean allowLongPress,
             KeyguardBypassController keyguardBypassController,
             StatusBarStateController statusBarStateController,
             NotificationLogger logger) {
         mContext = context;
-        mRowContentBinder = rowContentBinder;
         mMessagingUtil = new NotificationMessagingUtil(context);
         mAllowLongPress = allowLongPress;
         mKeyguardBypassController = keyguardBypassController;
@@ -120,7 +117,7 @@
         mOnAppOpsClickListener = mGutsManager::openGuts;
     }
 
-    public void setInflationCallback(NotificationRowContentBinder.InflationCallback callback) {
+    public void setInflationCallback(NotificationContentInflater.InflationCallback callback) {
         mInflationCallback = callback;
     }
 
@@ -159,6 +156,19 @@
     private void bindRow(NotificationEntry entry, PackageManager pmUser,
             StatusBarNotification sbn, ExpandableNotificationRow row,
             Runnable onDismissRunnable) {
+        row.setExpansionLogger(mExpansionLogger, entry.getSbn().getKey());
+        row.setBypassController(mKeyguardBypassController);
+        row.setStatusBarStateController(mStatusBarStateController);
+        row.setGroupManager(mGroupManager);
+        row.setHeadsUpManager(mHeadsUpManager);
+        row.setOnExpandClickListener(mPresenter);
+        row.setInflationCallback(mInflationCallback);
+        if (mAllowLongPress) {
+            row.setLongPressListener(mGutsManager::openGuts);
+        }
+        mListContainer.bindRow(row);
+        getRemoteInputManager().bindRow(row);
+
         // Get the app name.
         // Note that Notification.Builder#bindHeaderAppName has similar logic
         // but since this field is used in the guts, it must be accurate.
@@ -176,33 +186,15 @@
         } catch (PackageManager.NameNotFoundException e) {
             // Do nothing
         }
-
-        row.initialize(
-                appname,
-                sbn.getKey(),
-                mExpansionLogger,
-                mKeyguardBypassController,
-                mGroupManager,
-                mHeadsUpManager,
-                mRowContentBinder,
-                mPresenter);
-
-        // TODO: Either move these into ExpandableNotificationRow#initialize or out of row entirely
-        row.setStatusBarStateController(mStatusBarStateController);
-        row.setInflationCallback(mInflationCallback);
-        row.setAppOpsOnClickListener(mOnAppOpsClickListener);
-        if (mAllowLongPress) {
-            row.setLongPressListener(mGutsManager::openGuts);
-        }
-        mListContainer.bindRow(row);
-        getRemoteInputManager().bindRow(row);
-
+        row.setAppName(appname);
         row.setOnDismissRunnable(onDismissRunnable);
         row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
         if (ENABLE_REMOTE_INPUT) {
             row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
         }
 
+        row.setAppOpsOnClickListener(mOnAppOpsClickListener);
+
         mBindRowCallback.onBindRow(entry, pmUser, sbn, row);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
index 9312c22..db107f5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
@@ -43,6 +43,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import javax.inject.Inject;
@@ -62,6 +63,7 @@
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final StatusBarStateController mStatusBarStateController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private final HighPriorityProvider mHighPriorityProvider;
 
     @Inject
     public KeyguardCoordinator(
@@ -71,15 +73,16 @@
             NotificationLockscreenUserManager lockscreenUserManager,
             BroadcastDispatcher broadcastDispatcher,
             StatusBarStateController statusBarStateController,
-            KeyguardUpdateMonitor keyguardUpdateMonitor) {
+            KeyguardUpdateMonitor keyguardUpdateMonitor,
+            HighPriorityProvider highPriorityProvider) {
         mContext = context;
         mMainHandler = mainThreadHandler;
         mKeyguardStateController = keyguardStateController;
         mLockscreenUserManager = lockscreenUserManager;
-
         mBroadcastDispatcher = broadcastDispatcher;
         mStatusBarStateController = statusBarStateController;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        mHighPriorityProvider = highPriorityProvider;
     }
 
     @Override
@@ -151,7 +154,7 @@
         }
         if (NotificationUtils.useNewInterruptionModel(mContext)
                 && hideSilentNotificationsOnLockscreen()) {
-            return entry.isHighPriority();
+            return mHighPriorityProvider.isHighPriority(entry);
         } else {
             return entry.getRepresentativeEntry() != null
                     && !entry.getRepresentativeEntry().getRanking().isAmbient();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DerivedMember.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DerivedMember.java
deleted file mode 100644
index 815e6f7..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DerivedMember.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2019 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.systemui.statusbar.notification.collection.provider;
-/**
- * Caches a computed value until invalidate() is called
- * @param <Parent> Object used to computeValue
- * @param <Value> type of value to cache until invalidate is called
- */
-public abstract class DerivedMember<Parent, Value> {
-    private Value mValue;
-    protected abstract Value computeValue(Parent parent);
-
-    /**
-     * Gets the last cached value, else recomputes the value.
-     */
-    public Value get(Parent parent) {
-        if (mValue == null) {
-            mValue = computeValue(parent);
-        }
-        return mValue;
-    }
-
-    /**
-     * Resets the cached value.
-     * Next time "get" is called, the value is recomputed.
-     */
-    public void invalidate() {
-        mValue = null;
-    }
-
-    /**
-     * Called when a NotificationEntry's status bar notification has updated.
-     * Derived members can invalidate here.
-     */
-    public void onSbnUpdated() {}
-
-    /**
-     * Called when a NotificationEntry's Ranking has updated.
-     * Derived members can invalidate here.
-     */
-    public void onRankingUpdated() {}
-
-    /**
-     * Called when a ListEntry's grouping information (parent or children) has changed.
-     * Derived members can invalidate here.
-     */
-    public void onGroupingUpdated() {}
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java
new file mode 100644
index 0000000..3cc5e62
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2020 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.systemui.statusbar.notification.collection.provider;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+
+import com.android.systemui.statusbar.notification.collection.GroupEntry;
+import com.android.systemui.statusbar.notification.collection.ListEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Determines whether a notification is considered 'high priority'.
+ *
+ * Notifications that are high priority are visible on the lock screen/status bar and in the top
+ * section in the shade.
+ */
+@Singleton
+public class HighPriorityProvider {
+    private final PeopleNotificationIdentifier mPeopleNotificationIdentifier;
+
+    @Inject
+    public HighPriorityProvider(PeopleNotificationIdentifier peopleNotificationIdentifier) {
+        mPeopleNotificationIdentifier = peopleNotificationIdentifier;
+    }
+
+    /**
+     * @return true if the ListEntry is high priority, else false
+     *
+     * A NotificationEntry is considered high priority if it:
+     *  - has importance greater than or equal to IMPORTANCE_DEFAULT
+     *  OR
+     *  - their importance has NOT been set to a low priority option by the user AND the
+     *  notification fulfills one of the following:
+     *      - has a person associated with it
+     *      - has a media session associated with it
+     *      - has messaging style
+     *
+     * A GroupEntry is considered high priority if its representativeEntry (summary) or children are
+     * high priority
+     */
+    public boolean isHighPriority(ListEntry entry) {
+        if (entry == null) {
+            return false;
+        }
+
+        final NotificationEntry notifEntry = entry.getRepresentativeEntry();
+        return notifEntry.getRanking().getImportance() >= NotificationManager.IMPORTANCE_DEFAULT
+                || hasHighPriorityCharacteristics(notifEntry)
+                || hasHighPriorityChild(entry);
+    }
+
+
+    private boolean hasHighPriorityChild(ListEntry entry) {
+        if (entry instanceof GroupEntry) {
+            for (NotificationEntry child : ((GroupEntry) entry).getChildren()) {
+                if (isHighPriority(child)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private boolean hasHighPriorityCharacteristics(NotificationEntry entry) {
+        return !hasUserSetImportance(entry)
+                && (isImportantOngoing(entry)
+                || entry.getSbn().getNotification().hasMediaSession()
+                || isPeopleNotification(entry)
+                || isMessagingStyle(entry));
+    }
+
+    private boolean isImportantOngoing(NotificationEntry entry) {
+        return entry.getSbn().getNotification().isForegroundService()
+                && entry.getRanking().getImportance() >= NotificationManager.IMPORTANCE_LOW;
+    }
+
+    private boolean isMessagingStyle(NotificationEntry entry) {
+        return Notification.MessagingStyle.class.equals(
+                entry.getSbn().getNotification().getNotificationStyle());
+    }
+
+    private boolean isPeopleNotification(NotificationEntry entry) {
+        return mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn());
+    }
+
+    private boolean hasUserSetImportance(NotificationEntry entry) {
+        return entry.getRanking().getChannel() != null
+                && entry.getRanking().getChannel().hasUserSetImportance();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProvider.java
deleted file mode 100644
index 76e256b..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProvider.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright (C) 2019 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.systemui.statusbar.notification.collection.provider;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.Person;
-
-import com.android.systemui.Dependency;
-import com.android.systemui.statusbar.notification.collection.GroupEntry;
-import com.android.systemui.statusbar.notification.collection.ListEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Whether the ListEntry is shown to the user as a high priority notification: visible on
- * the lock screen/status bar and in the top section in the shade.
- *
- * A NotificationEntry is considered high priority if it:
- *  - has importance greater than or equal to IMPORTANCE_DEFAULT
- *  OR
- *  - their importance has NOT been set to a low priority option by the user AND the notification
- *  fulfills one of the following:
- *      - has a person associated with it
- *      - has a media session associated with it
- *      - has messaging style
- *
- * A GroupEntry is considered high priority if its representativeEntry (summary) or children are
- * high priority
- */
-public class IsHighPriorityProvider extends DerivedMember<ListEntry, Boolean> {
-    // TODO: (b/145659174) remove groupManager when moving to NewNotifPipeline. Logic
-    //  replaced in GroupEntry and NotifListBuilderImpl
-    private final NotificationGroupManager mGroupManager;
-
-
-    public IsHighPriorityProvider() {
-        // TODO: (b/145659174) remove
-        mGroupManager = Dependency.get(NotificationGroupManager.class);
-    }
-
-    @Override
-    protected Boolean computeValue(ListEntry entry) {
-        if (entry == null) {
-            return false;
-        }
-
-        return isHighPriority(entry);
-    }
-
-    private boolean isHighPriority(ListEntry listEntry) {
-        // requires groups have been set (AFTER PipelineState.STATE_TRANSFORMING)
-        final NotificationEntry notifEntry = listEntry.getRepresentativeEntry();
-        return notifEntry.getRanking().getImportance() >= NotificationManager.IMPORTANCE_DEFAULT
-                || hasHighPriorityCharacteristics(notifEntry)
-                || hasHighPriorityChild(listEntry);
-
-    }
-
-    private boolean hasHighPriorityChild(ListEntry entry) {
-        // TODO: (b/145659174) remove
-        if (entry instanceof NotificationEntry) {
-            NotificationEntry notifEntry = (NotificationEntry) entry;
-            if (mGroupManager.isSummaryOfGroup(notifEntry.getSbn())) {
-                List<NotificationEntry> logicalChildren =
-                        mGroupManager.getLogicalChildren(notifEntry.getSbn());
-                for (NotificationEntry child : logicalChildren) {
-                    if (child.isHighPriority()) {
-                        return true;
-                    }
-                }
-            }
-        }
-
-        if (entry instanceof GroupEntry) {
-            for (NotificationEntry child : ((GroupEntry) entry).getChildren()) {
-                if (child.isHighPriority()) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    private boolean hasHighPriorityCharacteristics(NotificationEntry entry) {
-        return !hasUserSetImportance(entry)
-                && (isImportantOngoing(entry)
-                || entry.getSbn().getNotification().hasMediaSession()
-                || hasPerson(entry)
-                || isMessagingStyle(entry));
-    }
-
-    private boolean isImportantOngoing(NotificationEntry entry) {
-        return entry.getSbn().getNotification().isForegroundService()
-                && entry.getRanking().getImportance() >= NotificationManager.IMPORTANCE_LOW;
-    }
-
-    private boolean isMessagingStyle(NotificationEntry entry) {
-        return Notification.MessagingStyle.class.equals(
-                entry.getSbn().getNotification().getNotificationStyle());
-    }
-
-    private boolean hasPerson(NotificationEntry entry) {
-        // TODO: cache favorite and recent contacts to check contact affinity
-        Notification notification = entry.getSbn().getNotification();
-        ArrayList<Person> people = notification.extras != null
-                ? notification.extras.getParcelableArrayList(Notification.EXTRA_PEOPLE_LIST)
-                : new ArrayList<>();
-        return people != null && !people.isEmpty();
-    }
-
-    private boolean hasUserSetImportance(NotificationEntry entry) {
-        return entry.getRanking().getChannel() != null
-                && entry.getRanking().getChannel().hasUserSetImportance();
-    }
-
-    @Override
-    public void onSbnUpdated() {
-        invalidate();
-    }
-
-    @Override
-    public void onRankingUpdated() {
-        invalidate();
-    }
-
-    @Override
-    public void onGroupingUpdated() {
-        invalidate();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index a8a35d0..3c247df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -65,6 +65,7 @@
 import android.widget.Chronometer;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
+import android.widget.RemoteViews;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
@@ -149,7 +150,7 @@
     private StatusBarStateController mStatusbarStateController;
     private KeyguardBypassController mBypassController;
     private LayoutListener mLayoutListener;
-    private NotificationRowContentBinder mNotificationContentBinder;
+    private final NotificationContentInflater mNotificationInflater;
     private int mIconTransformContentShift;
     private int mIconTransformContentShiftNoIcon;
     private int mMaxHeadsUpHeightBeforeN;
@@ -463,7 +464,7 @@
      * Inflate views based off the inflation flags set. Inflation happens asynchronously.
      */
     public void inflateViews() {
-        mNotificationContentBinder.bindContent(mEntry, this, mInflationFlags, mBindParams,
+        mNotificationInflater.bindContent(mEntry, this, mInflationFlags, mBindParams,
                 false /* forceInflate */, mInflationCallback);
     }
 
@@ -477,7 +478,7 @@
         // View should not be reinflated in the future
         clearInflationFlags(inflationFlag);
         Runnable freeViewRunnable =
-                () -> mNotificationContentBinder.unbindContent(mEntry, this, inflationFlag);
+                () -> mNotificationInflater.unbindContent(mEntry, this, inflationFlag);
         switch (inflationFlag) {
             case FLAG_CONTENT_VIEW_HEADS_UP:
                 getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_HEADSUP,
@@ -741,10 +742,23 @@
         return mIsHeadsUp || mHeadsupDisappearRunning;
     }
 
+
+    public void setGroupManager(NotificationGroupManager groupManager) {
+        mGroupManager = groupManager;
+        mPrivateLayout.setGroupManager(groupManager);
+    }
+
     public void setRemoteInputController(RemoteInputController r) {
         mPrivateLayout.setRemoteInputController(r);
     }
 
+    public void setAppName(String appName) {
+        mAppName = appName;
+        if (mMenuRow != null && mMenuRow.getMenuView() != null) {
+            mMenuRow.setAppName(mAppName);
+        }
+    }
+
     public void addChildNotification(ExpandableNotificationRow row) {
         addChildNotification(row, -1);
     }
@@ -838,7 +852,7 @@
             mIsChildInGroup = isChildInGroup;
             if (mIsLowPriority) {
                 int flags = FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED;
-                mNotificationContentBinder.bindContent(mEntry, this, flags, mBindParams,
+                mNotificationInflater.bindContent(mEntry, this, flags, mBindParams,
                         false /* forceInflate */, mInflationCallback);
             }
         }
@@ -1091,6 +1105,10 @@
         return mPrivateLayout.getContractedNotificationHeader();
     }
 
+    public void setOnExpandClickListener(OnExpandClickListener onExpandClickListener) {
+        mOnExpandClickListener = onExpandClickListener;
+    }
+
     public void setLongPressListener(LongPressListener longPressListener) {
         mLongPressListener = longPressListener;
     }
@@ -1113,6 +1131,10 @@
         }
     }
 
+    public void setHeadsUpManager(HeadsUpManager headsUpManager) {
+        mHeadsUpManager = headsUpManager;
+    }
+
     public HeadsUpManager getHeadsUpManager() {
         return mHeadsUpManager;
     }
@@ -1237,7 +1259,7 @@
             l.reInflateViews();
         }
         mEntry.getSbn().clearPackageContext();
-        mNotificationContentBinder.bindContent(mEntry, this, mInflationFlags, mBindParams,
+        mNotificationInflater.bindContent(mEntry, this, mInflationFlags, mBindParams,
                 true /* forceInflate */, mInflationCallback);
     }
 
@@ -1612,6 +1634,10 @@
         mBindParams.usesIncreasedHeadsUpHeight = use;
     }
 
+    public void setRemoteViewClickHandler(RemoteViews.OnClickHandler remoteViewClickHandler) {
+        mNotificationInflater.setRemoteViewClickHandler(remoteViewClickHandler);
+    }
+
     /**
      * Set callback for notification content inflation
      *
@@ -1626,7 +1652,7 @@
             mNeedsRedaction = needsRedaction;
             if (needsRedaction) {
                 setInflationFlags(FLAG_CONTENT_VIEW_PUBLIC);
-                mNotificationContentBinder.bindContent(mEntry, this, FLAG_CONTENT_VIEW_PUBLIC,
+                mNotificationInflater.bindContent(mEntry, this, FLAG_CONTENT_VIEW_PUBLIC,
                         mBindParams, false /* forceInflate */, mInflationCallback);
             } else {
                 clearInflationFlags(FLAG_CONTENT_VIEW_PUBLIC);
@@ -1635,12 +1661,18 @@
         }
     }
 
+    @VisibleForTesting
+    public NotificationContentInflater getNotificationInflater() {
+        return mNotificationInflater;
+    }
+
     public interface ExpansionLogger {
         void logNotificationExpansion(String key, boolean userAction, boolean expanded);
     }
 
     public ExpandableNotificationRow(Context context, AttributeSet attrs) {
         super(context, attrs);
+        mNotificationInflater = new NotificationContentInflater();
         mMenuRow = new NotificationMenuRow(mContext);
         mImageResolver = new NotificationInlineImageResolver(context,
                 new NotificationInlineImageCache());
@@ -1648,30 +1680,8 @@
         initDimens();
     }
 
-    /**
-     * Initialize row.
-     */
-    public void initialize(
-            String appName,
-            String notificationKey,
-            ExpansionLogger logger,
-            KeyguardBypassController bypassController,
-            NotificationGroupManager groupManager,
-            HeadsUpManager headsUpManager,
-            NotificationRowContentBinder rowContentBinder,
-            OnExpandClickListener onExpandClickListener) {
-        mAppName = appName;
-        if (mMenuRow != null && mMenuRow.getMenuView() != null) {
-            mMenuRow.setAppName(mAppName);
-        }
-        mLogger = logger;
-        mLoggingKey = notificationKey;
+    public void setBypassController(KeyguardBypassController bypassController) {
         mBypassController = bypassController;
-        mGroupManager = groupManager;
-        mPrivateLayout.setGroupManager(groupManager);
-        mHeadsUpManager = headsUpManager;
-        mNotificationContentBinder = rowContentBinder;
-        mOnExpandClickListener = onExpandClickListener;
     }
 
     public void setStatusBarStateController(StatusBarStateController statusBarStateController) {
@@ -2910,6 +2920,11 @@
         return 0;
     }
 
+    public void setExpansionLogger(ExpansionLogger logger, String key) {
+        mLogger = logger;
+        mLoggingKey = key;
+    }
+
     public void onExpandedByGesture(boolean userExpanded) {
         int event = MetricsEvent.ACTION_NOTIFICATION_GESTURE_EXPANDER;
         if (mGroupManager.isSummaryOfGroup(mEntry.getSbn())) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCache.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCache.java
deleted file mode 100644
index c11c60f..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCache.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2020 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.systemui.statusbar.notification.row;
-
-import android.widget.RemoteViews;
-
-import androidx.annotation.Nullable;
-
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
-
-/**
- * Caches {@link RemoteViews} for a notification's content views.
- */
-public interface NotifRemoteViewCache {
-
-    /**
-     * Whether the notification has the remote view cached
-     *
-     * @param entry notification
-     * @param flag inflation flag for content view
-     * @return true if the remote view is cached
-     */
-    boolean hasCachedView(NotificationEntry entry, @InflationFlag int flag);
-
-    /**
-     * Get the remote view for the content flag specified.
-     *
-     * @param entry notification
-     * @param flag inflation flag for the content view
-     * @return the remote view if it is cached, null otherwise
-     */
-    @Nullable RemoteViews getCachedView(NotificationEntry entry, @InflationFlag int flag);
-
-    /**
-     * Cache a remote view for a given content flag on a notification.
-     *
-     * @param entry notification
-     * @param flag inflation flag for the content view
-     * @param remoteView remote view to store
-     */
-    void putCachedView(
-            NotificationEntry entry,
-            @InflationFlag int flag,
-            RemoteViews remoteView);
-
-    /**
-     * Remove a cached remote view for a given content flag on a notification.
-     *
-     * @param entry notification
-     * @param flag inflation flag for the content view
-     */
-    void removeCachedView(NotificationEntry entry, @InflationFlag int flag);
-
-    /**
-     * Clear a notification's remote view cache.
-     *
-     * @param entry notification
-     */
-    void clearCache(NotificationEntry entry);
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java
deleted file mode 100644
index a19099a..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2020 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.systemui.statusbar.notification.row;
-
-import android.util.ArrayMap;
-import android.util.SparseArray;
-import android.widget.RemoteViews;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.internal.statusbar.NotificationVisibility;
-import com.android.systemui.statusbar.notification.NotificationEntryListener;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
-
-import java.util.Map;
-
-import javax.inject.Inject;
-
-/**
- * Implementation of remote view cache that keeps remote views cached for all active notifications.
- */
-public class NotifRemoteViewCacheImpl implements NotifRemoteViewCache {
-    private final Map<NotificationEntry, SparseArray<RemoteViews>> mNotifCachedContentViews =
-            new ArrayMap<>();
-
-    @Inject
-    NotifRemoteViewCacheImpl(NotificationEntryManager entryManager) {
-        entryManager.addNotificationEntryListener(mEntryListener);
-    }
-
-    @Override
-    public boolean hasCachedView(NotificationEntry entry, @InflationFlag int flag) {
-        return getCachedView(entry, flag) != null;
-    }
-
-    @Override
-    public @Nullable RemoteViews getCachedView(NotificationEntry entry, @InflationFlag int flag) {
-        return getContentViews(entry).get(flag);
-    }
-
-    @Override
-    public void putCachedView(
-            NotificationEntry entry,
-            @InflationFlag int flag,
-            RemoteViews remoteView) {
-        getContentViews(entry).put(flag, remoteView);
-    }
-
-    @Override
-    public void removeCachedView(NotificationEntry entry, @InflationFlag int flag) {
-        getContentViews(entry).remove(flag);
-    }
-
-    @Override
-    public void clearCache(NotificationEntry entry) {
-        getContentViews(entry).clear();
-    }
-
-    private @NonNull SparseArray<RemoteViews> getContentViews(NotificationEntry entry) {
-        SparseArray<RemoteViews> contentViews = mNotifCachedContentViews.get(entry);
-        if (contentViews == null) {
-            throw new IllegalStateException(
-                    String.format("Remote view cache was never created for notification %s",
-                            entry.getKey()));
-        }
-        return contentViews;
-    }
-
-    private final NotificationEntryListener mEntryListener = new NotificationEntryListener() {
-        @Override
-        public void onPendingEntryAdded(NotificationEntry entry) {
-            mNotifCachedContentViews.put(entry, new SparseArray<>());
-        }
-
-        @Override
-        public void onEntryRemoved(
-                NotificationEntry entry,
-                @Nullable NotificationVisibility visibility,
-                boolean removedByUser) {
-            mNotifCachedContentViews.remove(entry);
-        }
-    };
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index e1a6747..30f22ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.notification.row;
 
-import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
 import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED;
 import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP;
 
@@ -27,6 +26,7 @@
 import android.os.AsyncTask;
 import android.os.CancellationSignal;
 import android.service.notification.StatusBarNotification;
+import android.util.ArrayMap;
 import android.util.Log;
 import android.view.View;
 import android.widget.RemoteViews;
@@ -35,7 +35,6 @@
 import com.android.internal.widget.ImageMessageConsumer;
 import com.android.systemui.Dependency;
 import com.android.systemui.statusbar.InflationTask;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.SmartReplyController;
 import com.android.systemui.statusbar.notification.InflationException;
 import com.android.systemui.statusbar.notification.MediaNotificationProcessor;
@@ -50,30 +49,17 @@
 
 import java.util.HashMap;
 
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
 /**
  * {@link NotificationContentInflater} binds content to a {@link ExpandableNotificationRow} by
  * asynchronously building the content's {@link RemoteViews} and applying it to the row.
  */
-@Singleton
-@VisibleForTesting(visibility = PACKAGE)
 public class NotificationContentInflater implements NotificationRowContentBinder {
 
     public static final String TAG = "NotifContentInflater";
 
+    private RemoteViews.OnClickHandler mRemoteViewClickHandler;
     private boolean mInflateSynchronously = false;
-    private final NotificationRemoteInputManager mRemoteInputManager;
-    private final NotifRemoteViewCache mRemoteViewCache;
-
-    @Inject
-    public NotificationContentInflater(
-            NotifRemoteViewCache remoteViewCache,
-            NotificationRemoteInputManager remoteInputManager) {
-        mRemoteViewCache = remoteViewCache;
-        mRemoteInputManager = remoteInputManager;
-    }
+    private final ArrayMap<Integer, RemoteViews> mCachedContentViews = new ArrayMap<>();
 
     @Override
     public void bindContent(
@@ -90,27 +76,27 @@
             return;
         }
 
-        StatusBarNotification sbn = entry.getSbn();
+        StatusBarNotification sbn = row.getEntry().getSbn();
 
         // To check if the notification has inline image and preload inline image if necessary.
         row.getImageResolver().preloadImages(sbn.getNotification());
 
         if (forceInflate) {
-            mRemoteViewCache.clearCache(entry);
+            mCachedContentViews.clear();
         }
 
         AsyncInflationTask task = new AsyncInflationTask(
+                sbn,
                 mInflateSynchronously,
                 contentToBind,
-                mRemoteViewCache,
-                entry,
+                mCachedContentViews,
                 row,
                 bindParams.isLowPriority,
                 bindParams.isChildInGroup,
                 bindParams.usesIncreasedHeight,
                 bindParams.usesIncreasedHeadsUpHeight,
                 callback,
-                mRemoteInputManager.getRemoteViewsOnClickHandler());
+                mRemoteViewClickHandler);
         if (mInflateSynchronously) {
             task.onPostExecute(task.doInBackground());
         } else {
@@ -137,15 +123,13 @@
         result = inflateSmartReplyViews(result, reInflateFlags, entry,
                 row.getContext(), packageContext, row.getHeadsUpManager(),
                 row.getExistingSmartRepliesAndActions());
-
         apply(
                 inflateSynchronously,
                 result,
                 reInflateFlags,
-                mRemoteViewCache,
-                entry,
+                mCachedContentViews,
                 row,
-                mRemoteInputManager.getRemoteViewsOnClickHandler(),
+                mRemoteViewClickHandler,
                 null);
         return result;
     }
@@ -165,7 +149,7 @@
         int curFlag = 1;
         while (contentToUnbind != 0) {
             if ((contentToUnbind & curFlag) != 0) {
-                freeNotificationView(entry, row, curFlag);
+                freeNotificationView(row, curFlag);
             }
             contentToUnbind &= ~curFlag;
             curFlag = curFlag << 1;
@@ -173,25 +157,34 @@
     }
 
     /**
+     * Set click handler for notification remote views
+     *
+     * @param remoteViewClickHandler click handler for remote views
+     */
+    public void setRemoteViewClickHandler(RemoteViews.OnClickHandler remoteViewClickHandler) {
+        mRemoteViewClickHandler = remoteViewClickHandler;
+    }
+
+    /**
      * Frees the content view associated with the inflation flag.  Will only succeed if the
      * view is safe to remove.
      *
      * @param inflateFlag the flag corresponding to the content view which should be freed
      */
-    private void freeNotificationView(NotificationEntry entry, ExpandableNotificationRow row,
+    private void freeNotificationView(ExpandableNotificationRow row,
             @InflationFlag int inflateFlag) {
         switch (inflateFlag) {
             case FLAG_CONTENT_VIEW_HEADS_UP:
                 if (row.getPrivateLayout().isContentViewInactive(VISIBLE_TYPE_HEADSUP)) {
                     row.getPrivateLayout().setHeadsUpChild(null);
-                    mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP);
+                    mCachedContentViews.remove(FLAG_CONTENT_VIEW_HEADS_UP);
                     row.getPrivateLayout().setHeadsUpInflatedSmartReplies(null);
                 }
                 break;
             case FLAG_CONTENT_VIEW_PUBLIC:
                 if (row.getPublicLayout().isContentViewInactive(VISIBLE_TYPE_CONTRACTED)) {
                     row.getPublicLayout().setContractedChild(null);
-                    mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC);
+                    mCachedContentViews.remove(FLAG_CONTENT_VIEW_PUBLIC);
                 }
                 break;
             case FLAG_CONTENT_VIEW_CONTRACTED:
@@ -252,12 +245,11 @@
         return result;
     }
 
-    private static CancellationSignal apply(
+    public static CancellationSignal apply(
             boolean inflateSynchronously,
             InflationProgress result,
             @InflationFlag int reInflateFlags,
-            NotifRemoteViewCache remoteViewCache,
-            NotificationEntry entry,
+            ArrayMap<Integer, RemoteViews> cachedContentViews,
             ExpandableNotificationRow row,
             RemoteViews.OnClickHandler remoteViewClickHandler,
             @Nullable InflationCallback callback) {
@@ -269,7 +261,7 @@
         if ((reInflateFlags & flag) != 0) {
             boolean isNewView =
                     !canReapplyRemoteView(result.newContentView,
-                            remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED));
+                            cachedContentViews.get(FLAG_CONTENT_VIEW_CONTRACTED));
             ApplyCallback applyCallback = new ApplyCallback() {
                 @Override
                 public void setResultView(View v) {
@@ -281,8 +273,8 @@
                     return result.newContentView;
                 }
             };
-            applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, remoteViewCache,
-                    entry, row, isNewView, remoteViewClickHandler, callback, privateLayout,
+            applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, cachedContentViews,
+                    row, isNewView, remoteViewClickHandler, callback, privateLayout,
                     privateLayout.getContractedChild(), privateLayout.getVisibleWrapper(
                             NotificationContentView.VISIBLE_TYPE_CONTRACTED),
                     runningInflations, applyCallback);
@@ -293,7 +285,7 @@
             if (result.newExpandedView != null) {
                 boolean isNewView =
                         !canReapplyRemoteView(result.newExpandedView,
-                                remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED));
+                                cachedContentViews.get(FLAG_CONTENT_VIEW_EXPANDED));
                 ApplyCallback applyCallback = new ApplyCallback() {
                     @Override
                     public void setResultView(View v) {
@@ -305,8 +297,8 @@
                         return result.newExpandedView;
                     }
                 };
-                applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, remoteViewCache,
-                        entry, row, isNewView, remoteViewClickHandler,
+                applyRemoteView(inflateSynchronously, result, reInflateFlags, flag,
+                        cachedContentViews, row, isNewView, remoteViewClickHandler,
                         callback, privateLayout, privateLayout.getExpandedChild(),
                         privateLayout.getVisibleWrapper(
                                 NotificationContentView.VISIBLE_TYPE_EXPANDED), runningInflations,
@@ -319,7 +311,7 @@
             if (result.newHeadsUpView != null) {
                 boolean isNewView =
                         !canReapplyRemoteView(result.newHeadsUpView,
-                                remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP));
+                                cachedContentViews.get(FLAG_CONTENT_VIEW_HEADS_UP));
                 ApplyCallback applyCallback = new ApplyCallback() {
                     @Override
                     public void setResultView(View v) {
@@ -331,8 +323,8 @@
                         return result.newHeadsUpView;
                     }
                 };
-                applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, remoteViewCache,
-                        entry, row, isNewView, remoteViewClickHandler,
+                applyRemoteView(inflateSynchronously, result, reInflateFlags, flag,
+                        cachedContentViews, row, isNewView, remoteViewClickHandler,
                         callback, privateLayout, privateLayout.getHeadsUpChild(),
                         privateLayout.getVisibleWrapper(
                                 VISIBLE_TYPE_HEADSUP), runningInflations,
@@ -344,7 +336,7 @@
         if ((reInflateFlags & flag) != 0) {
             boolean isNewView =
                     !canReapplyRemoteView(result.newPublicView,
-                            remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC));
+                            cachedContentViews.get(FLAG_CONTENT_VIEW_PUBLIC));
             ApplyCallback applyCallback = new ApplyCallback() {
                 @Override
                 public void setResultView(View v) {
@@ -356,16 +348,15 @@
                     return result.newPublicView;
                 }
             };
-            applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, remoteViewCache,
-                    entry, row, isNewView, remoteViewClickHandler, callback,
+            applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, cachedContentViews,
+                    row, isNewView, remoteViewClickHandler, callback,
                     publicLayout, publicLayout.getContractedChild(),
                     publicLayout.getVisibleWrapper(NotificationContentView.VISIBLE_TYPE_CONTRACTED),
                     runningInflations, applyCallback);
         }
 
         // Let's try to finish, maybe nobody is even inflating anything
-        finishIfDone(result, reInflateFlags, remoteViewCache, runningInflations, callback, entry,
-                row);
+        finishIfDone(result, reInflateFlags, cachedContentViews, runningInflations, callback, row);
         CancellationSignal cancellationSignal = new CancellationSignal();
         cancellationSignal.setOnCancelListener(
                 () -> runningInflations.values().forEach(CancellationSignal::cancel));
@@ -378,8 +369,7 @@
             final InflationProgress result,
             final @InflationFlag int reInflateFlags,
             @InflationFlag int inflationId,
-            final NotifRemoteViewCache remoteViewCache,
-            final NotificationEntry entry,
+            final ArrayMap<Integer, RemoteViews> cachedContentViews,
             final ExpandableNotificationRow row,
             boolean isNewView,
             RemoteViews.OnClickHandler remoteViewClickHandler,
@@ -432,8 +422,8 @@
                     existingWrapper.onReinflated();
                 }
                 runningInflations.remove(inflationId);
-                finishIfDone(result, reInflateFlags, remoteViewCache, runningInflations,
-                        callback, entry, row);
+                finishIfDone(result, reInflateFlags, cachedContentViews, runningInflations,
+                        callback, row);
             }
 
             @Override
@@ -498,11 +488,11 @@
      * @return true if the inflation was finished
      */
     private static boolean finishIfDone(InflationProgress result,
-            @InflationFlag int reInflateFlags, NotifRemoteViewCache remoteViewCache,
+            @InflationFlag int reInflateFlags, ArrayMap<Integer, RemoteViews> cachedContentViews,
             HashMap<Integer, CancellationSignal> runningInflations,
-            @Nullable InflationCallback endListener, NotificationEntry entry,
-            ExpandableNotificationRow row) {
+            @Nullable InflationCallback endListener, ExpandableNotificationRow row) {
         Assert.isMainThread();
+        NotificationEntry entry = row.getEntry();
         NotificationContentView privateLayout = row.getPrivateLayout();
         NotificationContentView publicLayout = row.getPublicLayout();
         if (runningInflations.isEmpty()) {
@@ -510,27 +500,23 @@
                 if (result.inflatedContentView != null) {
                     // New view case
                     privateLayout.setContractedChild(result.inflatedContentView);
-                    remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED,
-                            result.newContentView);
-                } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED)) {
+                    cachedContentViews.put(FLAG_CONTENT_VIEW_CONTRACTED, result.newContentView);
+                } else if (cachedContentViews.get(FLAG_CONTENT_VIEW_CONTRACTED) != null) {
                     // Reinflation case. Only update if it's still cached (i.e. view has not been
                     // freed while inflating).
-                    remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED,
-                            result.newContentView);
+                    cachedContentViews.put(FLAG_CONTENT_VIEW_CONTRACTED, result.newContentView);
                 }
             }
 
             if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0) {
                 if (result.inflatedExpandedView != null) {
                     privateLayout.setExpandedChild(result.inflatedExpandedView);
-                    remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED,
-                            result.newExpandedView);
+                    cachedContentViews.put(FLAG_CONTENT_VIEW_EXPANDED, result.newExpandedView);
                 } else if (result.newExpandedView == null) {
                     privateLayout.setExpandedChild(null);
-                    remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED);
-                } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)) {
-                    remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED,
-                            result.newExpandedView);
+                    cachedContentViews.put(FLAG_CONTENT_VIEW_EXPANDED, null);
+                } else if (cachedContentViews.get(FLAG_CONTENT_VIEW_EXPANDED) != null) {
+                    cachedContentViews.put(FLAG_CONTENT_VIEW_EXPANDED, result.newExpandedView);
                 }
                 if (result.newExpandedView != null) {
                     privateLayout.setExpandedInflatedSmartReplies(
@@ -544,14 +530,12 @@
             if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) {
                 if (result.inflatedHeadsUpView != null) {
                     privateLayout.setHeadsUpChild(result.inflatedHeadsUpView);
-                    remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP,
-                            result.newHeadsUpView);
+                    cachedContentViews.put(FLAG_CONTENT_VIEW_HEADS_UP, result.newHeadsUpView);
                 } else if (result.newHeadsUpView == null) {
                     privateLayout.setHeadsUpChild(null);
-                    remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP);
-                } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)) {
-                    remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP,
-                            result.newHeadsUpView);
+                    cachedContentViews.put(FLAG_CONTENT_VIEW_HEADS_UP, null);
+                } else if (cachedContentViews.get(FLAG_CONTENT_VIEW_HEADS_UP) != null) {
+                    cachedContentViews.put(FLAG_CONTENT_VIEW_HEADS_UP, result.newHeadsUpView);
                 }
                 if (result.newHeadsUpView != null) {
                     privateLayout.setHeadsUpInflatedSmartReplies(
@@ -564,18 +548,16 @@
             if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) {
                 if (result.inflatedPublicView != null) {
                     publicLayout.setContractedChild(result.inflatedPublicView);
-                    remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC,
-                            result.newPublicView);
-                } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC)) {
-                    remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC,
-                            result.newPublicView);
+                    cachedContentViews.put(FLAG_CONTENT_VIEW_PUBLIC, result.newPublicView);
+                } else if (cachedContentViews.get(FLAG_CONTENT_VIEW_PUBLIC) != null) {
+                    cachedContentViews.put(FLAG_CONTENT_VIEW_PUBLIC, result.newPublicView);
                 }
             }
 
             entry.headsUpStatusBarText = result.headsUpStatusBarText;
             entry.headsUpStatusBarTextPublic = result.headsUpStatusBarTextPublic;
             if (endListener != null) {
-                endListener.onAsyncInflationFinished(entry, reInflateFlags);
+                endListener.onAsyncInflationFinished(row.getEntry(), reInflateFlags);
             }
             return true;
         }
@@ -633,7 +615,7 @@
     public static class AsyncInflationTask extends AsyncTask<Void, Void, InflationProgress>
             implements InflationCallback, InflationTask {
 
-        private final NotificationEntry mEntry;
+        private final StatusBarNotification mSbn;
         private final Context mContext;
         private final boolean mInflateSynchronously;
         private final boolean mIsLowPriority;
@@ -642,17 +624,17 @@
         private final InflationCallback mCallback;
         private final boolean mUsesIncreasedHeadsUpHeight;
         private @InflationFlag int mReInflateFlags;
-        private final NotifRemoteViewCache mRemoteViewCache;
+        private final ArrayMap<Integer, RemoteViews> mCachedContentViews;
         private ExpandableNotificationRow mRow;
         private Exception mError;
         private RemoteViews.OnClickHandler mRemoteViewClickHandler;
         private CancellationSignal mCancellationSignal;
 
         private AsyncInflationTask(
+                StatusBarNotification notification,
                 boolean inflateSynchronously,
                 @InflationFlag int reInflateFlags,
-                NotifRemoteViewCache cache,
-                NotificationEntry entry,
+                ArrayMap<Integer, RemoteViews> cachedContentViews,
                 ExpandableNotificationRow row,
                 boolean isLowPriority,
                 boolean isChildInGroup,
@@ -660,11 +642,11 @@
                 boolean usesIncreasedHeadsUpHeight,
                 InflationCallback callback,
                 RemoteViews.OnClickHandler remoteViewClickHandler) {
-            mEntry = entry;
             mRow = row;
+            mSbn = notification;
             mInflateSynchronously = inflateSynchronously;
             mReInflateFlags = reInflateFlags;
-            mRemoteViewCache = cache;
+            mCachedContentViews = cachedContentViews;
             mContext = mRow.getContext();
             mIsLowPriority = isLowPriority;
             mIsChildInGroup = isChildInGroup;
@@ -672,6 +654,7 @@
             mUsesIncreasedHeadsUpHeight = usesIncreasedHeadsUpHeight;
             mRemoteViewClickHandler = remoteViewClickHandler;
             mCallback = callback;
+            NotificationEntry entry = row.getEntry();
             entry.setInflationTask(this);
         }
 
@@ -684,13 +667,12 @@
         @Override
         protected InflationProgress doInBackground(Void... params) {
             try {
-                final StatusBarNotification sbn = mEntry.getSbn();
                 final Notification.Builder recoveredBuilder
                         = Notification.Builder.recoverBuilder(mContext,
-                        sbn.getNotification());
+                        mSbn.getNotification());
 
-                Context packageContext = sbn.getPackageContext(mContext);
-                Notification notification = sbn.getNotification();
+                Context packageContext = mSbn.getPackageContext(mContext);
+                Notification notification = mSbn.getNotification();
                 if (notification.isMediaNotification()) {
                     MediaNotificationProcessor processor = new MediaNotificationProcessor(mContext,
                             packageContext);
@@ -699,7 +681,7 @@
                 InflationProgress inflationProgress = createRemoteViews(mReInflateFlags,
                         recoveredBuilder, mIsLowPriority, mIsChildInGroup, mUsesIncreasedHeight,
                         mUsesIncreasedHeadsUpHeight, packageContext);
-                return inflateSmartReplyViews(inflationProgress, mReInflateFlags, mEntry,
+                return inflateSmartReplyViews(inflationProgress, mReInflateFlags, mRow.getEntry(),
                         mRow.getContext(), packageContext, mRow.getHeadsUpManager(),
                         mRow.getExistingSmartRepliesAndActions());
             } catch (Exception e) {
@@ -712,15 +694,15 @@
         protected void onPostExecute(InflationProgress result) {
             if (mError == null) {
                 mCancellationSignal = apply(mInflateSynchronously, result, mReInflateFlags,
-                        mRemoteViewCache, mEntry, mRow, mRemoteViewClickHandler, this);
+                        mCachedContentViews, mRow, mRemoteViewClickHandler, this);
             } else {
                 handleError(mError);
             }
         }
 
         private void handleError(Exception e) {
-            mEntry.onInflationTaskFinished();
-            StatusBarNotification sbn = mEntry.getSbn();
+            mRow.getEntry().onInflationTaskFinished();
+            StatusBarNotification sbn = mRow.getEntry().getSbn();
             final String ident = sbn.getPackageName() + "/0x"
                     + Integer.toHexString(sbn.getId());
             Log.e(StatusBar.TAG, "couldn't inflate view for notification " + ident, e);
@@ -754,10 +736,10 @@
         @Override
         public void onAsyncInflationFinished(NotificationEntry entry,
                 @InflationFlag int inflatedFlags) {
-            mEntry.onInflationTaskFinished();
+            mRow.getEntry().onInflationTaskFinished();
             mRow.onNotificationUpdated();
             if (mCallback != null) {
-                mCallback.onAsyncInflationFinished(mEntry, inflatedFlags);
+                mCallback.onAsyncInflationFinished(mRow.getEntry(), inflatedFlags);
             }
 
             // Notify the resolver that the inflation task has finished,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 6f2abba..779a224 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -53,6 +53,7 @@
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.StatusBar;
@@ -81,6 +82,7 @@
     private final Context mContext;
     private final VisualStabilityManager mVisualStabilityManager;
     private final AccessibilityManager mAccessibilityManager;
+    private final HighPriorityProvider mHighPriorityProvider;
 
     // Dependencies:
     private final NotificationLockscreenUserManager mLockscreenUserManager =
@@ -109,12 +111,14 @@
     @Inject
     public NotificationGutsManager(Context context, VisualStabilityManager visualStabilityManager,
             Lazy<StatusBar> statusBarLazy, @Main Handler mainHandler,
-            AccessibilityManager accessibilityManager) {
+            AccessibilityManager accessibilityManager,
+            HighPriorityProvider highPriorityProvider) {
         mContext = context;
         mVisualStabilityManager = visualStabilityManager;
         mStatusBarLazy = statusBarLazy;
         mMainHandler = mainHandler;
         mAccessibilityManager = accessibilityManager;
+        mHighPriorityProvider = highPriorityProvider;
     }
 
     public void setUpWithPresenter(NotificationPresenter presenter,
@@ -331,8 +335,7 @@
                 row.getIsNonblockable(),
                 isForBlockingHelper,
                 row.getEntry().getImportance(),
-                row.getEntry().isHighPriority());
-
+                mHighPriorityProvider.isHighPriority(row.getEntry()));
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
index b4ccb56..edfd1b4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
@@ -46,6 +46,7 @@
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.statusbar.AlphaOptimizedImageView;
 import com.android.systemui.statusbar.notification.row.NotificationGuts.GutsContent;
+import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 
 import java.util.ArrayList;
@@ -269,7 +270,8 @@
         }
         mAppOpsItem = createAppOpsItem(mContext);
         if (mIsUsingBidirectionalSwipe) {
-            mInfoItem = createInfoItem(mContext, !mParent.getEntry().isHighPriority());
+            mInfoItem = createInfoItem(mContext,
+                    mParent.getEntry().getBucket() == NotificationSectionsManager.BUCKET_SILENT);
         } else {
             mInfoItem = createInfoItem(mContext);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
deleted file mode 100644
index df8653c..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2020 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.systemui.statusbar.notification.row;
-
-import javax.inject.Singleton;
-
-import dagger.Binds;
-import dagger.Module;
-
-/**
- * Dagger Module containing notification row and view inflation implementations.
- */
-@Module
-public abstract class NotificationRowModule {
-    /**
-     * Provides notification row content binder instance.
-     */
-    @Binds
-    @Singleton
-    public abstract NotificationRowContentBinder provideNotificationRowContentBinder(
-            NotificationContentInflater contentBinderImpl);
-
-    /**
-     * Provides notification remote view cache instance.
-     */
-    @Binds
-    @Singleton
-    public abstract NotifRemoteViewCache provideNotifRemoteViewCache(
-            NotifRemoteViewCacheImpl cacheImpl);
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
index 352ba0f..76fdfc6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
@@ -28,6 +28,7 @@
 import android.media.session.PlaybackState;
 import android.metrics.LogMaker;
 import android.os.Handler;
+import android.service.notification.StatusBarNotification;
 import android.text.format.DateUtils;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -176,27 +177,30 @@
         final MediaSession.Token token = mRow.getEntry().getSbn().getNotification().extras
                 .getParcelable(Notification.EXTRA_MEDIA_SESSION);
 
-        if (Utils.useQsMediaPlayer(mContext)) {
+        if (Utils.useQsMediaPlayer(mContext) && token != null) {
             final int[] compactActions = mRow.getEntry().getSbn().getNotification().extras
                     .getIntArray(Notification.EXTRA_COMPACT_ACTIONS);
             int tintColor = getNotificationHeader().getOriginalIconColor();
             StatusBarWindowController ctrl = Dependency.get(StatusBarWindowController.class);
             QuickQSPanel panel = ctrl.getStatusBarView().findViewById(
                     com.android.systemui.R.id.quick_qs_panel);
+            StatusBarNotification sbn = mRow.getEntry().getSbn();
+            Notification notif = sbn.getNotification();
             panel.getMediaPlayer().setMediaSession(token,
-                    mRow.getEntry().getSbn().getNotification().getSmallIcon(),
+                    notif.getSmallIcon(),
                     tintColor,
                     mBackgroundColor,
                     mActions,
-                    compactActions);
+                    compactActions,
+                    notif.contentIntent);
             QSPanel bigPanel = ctrl.getStatusBarView().findViewById(
                     com.android.systemui.R.id.quick_settings_panel);
             bigPanel.addMediaSession(token,
-                    mRow.getEntry().getSbn().getNotification().getSmallIcon(),
+                    notif.getSmallIcon(),
                     tintColor,
                     mBackgroundColor,
                     mActions,
-                    mRow.getEntry().getSbn());
+                    sbn);
         }
 
         boolean showCompactSeekbar = mMediaManager.getShowCompactMediaSeekbar();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index a3b1b5f..a6842ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -366,8 +366,8 @@
         IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
         filter.addAction(Intent.ACTION_SCREEN_ON);
         filter.addAction(Intent.ACTION_USER_SWITCHED);
-        mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter, Handler.getMain(),
-                UserHandle.ALL);
+        mBroadcastDispatcher.registerReceiverWithHandler(mBroadcastReceiver, filter,
+                Handler.getMain(), UserHandle.ALL);
         notifyNavigationBarScreenOn();
 
         mOverviewProxyService.addCallback(mOverviewProxyListener);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
index 896b6e5..fe0739f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
@@ -32,6 +32,7 @@
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.NotificationContentInflater.AsyncInflationTask;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 import com.android.systemui.statusbar.phone.NotificationGroupManager.NotificationGroup;
 import com.android.systemui.statusbar.phone.NotificationGroupManager.OnGroupChangeListener;
@@ -427,7 +428,7 @@
          * The notification is still pending inflation but we've decided that we no longer need
          * the content view (e.g. suppression might have changed and we decided we need to transfer
          * back). However, there is no way to abort just this inflation if other inflation requests
-         * have started (see {@link InflationTask#supersedeTask(InflationTask)}). So instead
+         * have started (see {@link AsyncInflationTask#supersedeTask(InflationTask)}). So instead
          * we just flag it as aborted and free when it's inflated.
          */
         boolean mAbortOnInflation;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 5b34aa7..00e38f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -179,7 +179,7 @@
         filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
         filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
         filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
-        broadcastDispatcher.registerReceiver(mIntentReceiver, filter, mHandler);
+        broadcastDispatcher.registerReceiverWithHandler(mIntentReceiver, filter, mHandler);
 
         // listen for user / profile change.
         try {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 30825ed..ccc86b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -205,7 +205,6 @@
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
-import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -414,7 +413,6 @@
     private final NotificationGutsManager mGutsManager;
     private final NotificationLogger mNotificationLogger;
     private final NotificationEntryManager mEntryManager;
-    private final NotificationRowContentBinder mRowContentBinder;
     private NotificationListController mNotificationListController;
     private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
     private final NotificationViewHierarchyManager mViewHierarchyManager;
@@ -636,7 +634,6 @@
             NotificationGutsManager notificationGutsManager,
             NotificationLogger notificationLogger,
             NotificationEntryManager notificationEntryManager,
-            NotificationRowContentBinder notificationRowContentBinder,
             NotificationInterruptionStateProvider notificationInterruptionStateProvider,
             NotificationViewHierarchyManager notificationViewHierarchyManager,
             KeyguardViewMediator keyguardViewMediator,
@@ -718,7 +715,6 @@
         mGutsManager = notificationGutsManager;
         mNotificationLogger = notificationLogger;
         mEntryManager = notificationEntryManager;
-        mRowContentBinder = notificationRowContentBinder;
         mNotificationInterruptionStateProvider = notificationInterruptionStateProvider;
         mViewHierarchyManager = notificationViewHierarchyManager;
         mKeyguardViewMediator = keyguardViewMediator;
@@ -1244,7 +1240,6 @@
         final NotificationRowBinderImpl rowBinder =
                 new NotificationRowBinderImpl(
                         mContext,
-                        mRowContentBinder,
                         mAllowNotificationLongPress,
                         mKeyguardBypassController,
                         mStatusBarStateController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java
index df74107..153ca22 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java
@@ -69,7 +69,6 @@
 import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
-import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder;
 import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -126,7 +125,6 @@
             NotificationGutsManager notificationGutsManager,
             NotificationLogger notificationLogger,
             NotificationEntryManager notificationEntryManager,
-            NotificationRowContentBinder notificationRowContentBinder,
             NotificationInterruptionStateProvider notificationInterruptionStateProvider,
             NotificationViewHierarchyManager notificationViewHierarchyManager,
             KeyguardViewMediator keyguardViewMediator,
@@ -209,7 +207,6 @@
                 notificationGutsManager,
                 notificationLogger,
                 notificationEntryManager,
-                notificationRowContentBinder,
                 notificationInterruptionStateProvider,
                 notificationViewHierarchyManager,
                 keyguardViewMediator,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index ddacc3a..4f0af9e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -188,7 +188,7 @@
             // NOTE: This receiver could run before this method returns, as it's not dispatching
             // on the main thread and BroadcastDispatcher may not need to register with Context.
             // The receiver will return immediately if the view does not have a Handler yet.
-            mBroadcastDispatcher.registerReceiver(mIntentReceiver, filter,
+            mBroadcastDispatcher.registerReceiverWithHandler(mIntentReceiver, filter,
                     Dependency.get(Dependency.TIME_TICK_HANDLER), UserHandle.ALL);
             Dependency.get(TunerService.class).addTunable(this, CLOCK_SECONDS,
                     StatusBarIconController.ICON_BLACKLIST);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java
index 2e26711..b4c154a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java
@@ -96,7 +96,7 @@
         filter.addAction(Intent.ACTION_TIME_CHANGED);
         filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
         filter.addAction(Intent.ACTION_LOCALE_CHANGED);
-        mBroadcastDispatcher.registerReceiver(mIntentReceiver, filter,
+        mBroadcastDispatcher.registerReceiverWithHandler(mIntentReceiver, filter,
                 Dependency.get(Dependency.TIME_TICK_HANDLER));
 
         updateClock();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
index 570f153..cb40d77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
@@ -79,7 +79,8 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(LocationManager.HIGH_POWER_REQUEST_CHANGE_ACTION);
         filter.addAction(LocationManager.MODE_CHANGED_ACTION);
-        mBroadcastDispatcher.registerReceiver(this, filter, new Handler(bgLooper), UserHandle.ALL);
+        mBroadcastDispatcher.registerReceiverWithHandler(this, filter,
+                new Handler(bgLooper), UserHandle.ALL);
 
         mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
         mStatusBarManager
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index f640d03..679fa7e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -326,7 +326,7 @@
         filter.addAction(ConnectivityManager.INET_CONDITION_ACTION);
         filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
         filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
-        mBroadcastDispatcher.registerReceiver(this, filter, mReceiverHandler);
+        mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mReceiverHandler);
         mListening = true;
 
         updateMobileControllers();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index 019ef3b..312c4ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -126,7 +126,8 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(KeyChain.ACTION_TRUST_STORE_CHANGED);
         filter.addAction(Intent.ACTION_USER_UNLOCKED);
-        broadcastDispatcher.registerReceiver(mBroadcastReceiver, filter, bgHandler, UserHandle.ALL);
+        broadcastDispatcher.registerReceiverWithHandler(mBroadcastReceiver, filter, bgHandler,
+                UserHandle.ALL);
 
         // TODO: re-register network callback on user change.
         mConnectivityManager.registerNetworkCallback(REQUEST, mNetworkCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 7758aba..31b9952 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -88,7 +88,7 @@
         final IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_USER_SWITCHED);
         filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
-        mBroadcastDispatcher.registerReceiver(new BroadcastReceiver() {
+        mBroadcastDispatcher.registerReceiverWithHandler(new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
                 if (DEBUG) Log.d(TAG, "Updating overlays for user switch / profile added.");
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index a4ed31d..112ae6f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -1008,7 +1008,7 @@
             filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
             filter.addAction(Intent.ACTION_SCREEN_OFF);
             filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
-            mBroadcastDispatcher.registerReceiver(this, filter, mWorker);
+            mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mWorker);
         }
 
         public void destroy() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 0c22aae..2e0fb3b 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -150,10 +150,10 @@
 
     @Test
     public void testReceiversRegistered() {
-        verify(mBroadcastDispatcher, atLeastOnce()).registerReceiver(
+        verify(mBroadcastDispatcher, atLeastOnce()).registerReceiverWithHandler(
                 eq(mKeyguardUpdateMonitor.mBroadcastReceiver),
                 any(IntentFilter.class), any(Handler.class));
-        verify(mBroadcastDispatcher, atLeastOnce()).registerReceiver(
+        verify(mBroadcastDispatcher, atLeastOnce()).registerReceiverWithHandler(
                 eq(mKeyguardUpdateMonitor.mBroadcastAllReceiver),
                 any(IntentFilter.class), any(Handler.class), eq(UserHandle.ALL));
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
index 42fbf59..22b1837 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
@@ -27,6 +27,8 @@
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
 import junit.framework.Assert.assertSame
 import org.junit.Before
 import org.junit.Test
@@ -39,6 +41,7 @@
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
+import java.util.concurrent.Executor
 
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper
@@ -73,6 +76,8 @@
     @Mock
     private lateinit var mockHandler: Handler
 
+    private lateinit var executor: Executor
+
     @Captor
     private lateinit var argumentCaptor: ArgumentCaptor<ReceiverData>
 
@@ -83,6 +88,7 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         testableLooper = TestableLooper.get(this)
+        executor = FakeExecutor(FakeSystemClock())
 
         broadcastDispatcher = TestBroadcastDispatcher(
                 mockContext,
@@ -98,8 +104,9 @@
 
     @Test
     fun testAddingReceiverToCorrectUBR() {
-        broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler, user0)
-        broadcastDispatcher.registerReceiver(
+        broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter,
+                mockHandler, user0)
+        broadcastDispatcher.registerReceiverWithHandler(
                 broadcastReceiverOther, intentFilterOther, mockHandler, user1)
 
         testableLooper.processAllMessages()
@@ -115,9 +122,29 @@
     }
 
     @Test
+    fun testAddingReceiverToCorrectUBR_executor() {
+        broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, executor, user0)
+        broadcastDispatcher.registerReceiver(
+                broadcastReceiverOther, intentFilterOther, executor, user1)
+
+        testableLooper.processAllMessages()
+
+        verify(mockUBRUser0).registerReceiver(capture(argumentCaptor))
+
+        assertSame(broadcastReceiver, argumentCaptor.value.receiver)
+        assertSame(intentFilter, argumentCaptor.value.filter)
+
+        verify(mockUBRUser1).registerReceiver(capture(argumentCaptor))
+        assertSame(broadcastReceiverOther, argumentCaptor.value.receiver)
+        assertSame(intentFilterOther, argumentCaptor.value.filter)
+    }
+
+    @Test
     fun testRemovingReceiversRemovesFromAllUBR() {
-        broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler, user0)
-        broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler, user1)
+        broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter,
+                mockHandler, user0)
+        broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter,
+                mockHandler, user1)
 
         broadcastDispatcher.unregisterReceiver(broadcastReceiver)
 
@@ -129,8 +156,10 @@
 
     @Test
     fun testRemoveReceiverFromUser() {
-        broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler, user0)
-        broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler, user1)
+        broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter,
+                mockHandler, user0)
+        broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter,
+                mockHandler, user1)
 
         broadcastDispatcher.unregisterReceiverForUser(broadcastReceiver, user0)
 
@@ -143,8 +172,8 @@
     @Test
     fun testRegisterCurrentAsActualUser() {
         setUserMock(mockContext, user1)
-        broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler,
-                UserHandle.CURRENT)
+        broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter,
+                mockHandler, UserHandle.CURRENT)
 
         testableLooper.processAllMessages()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
index 21ed155..7821ae2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
@@ -26,6 +26,8 @@
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
 import junit.framework.Assert.assertEquals
 import junit.framework.Assert.assertFalse
 import junit.framework.Assert.assertTrue
@@ -69,8 +71,6 @@
     @Mock
     private lateinit var mockContext: Context
     @Mock
-    private lateinit var mockHandler: Handler
-    @Mock
     private lateinit var mPendingResult: BroadcastReceiver.PendingResult
 
     @Captor
@@ -81,12 +81,14 @@
     private lateinit var intentFilter: IntentFilter
     private lateinit var intentFilterOther: IntentFilter
     private lateinit var handler: Handler
+    private lateinit var fakeExecutor: FakeExecutor
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         testableLooper = TestableLooper.get(this)
         handler = Handler(testableLooper.looper)
+        fakeExecutor = FakeExecutor(FakeSystemClock())
 
         userBroadcastDispatcher = UserBroadcastDispatcher(
                 mockContext, USER_ID, handler, testableLooper.looper)
@@ -108,7 +110,7 @@
         intentFilter = IntentFilter(ACTION_1)
 
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiver, intentFilter, mockHandler, USER_HANDLE))
+                ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
         testableLooper.processAllMessages()
 
         assertTrue(userBroadcastDispatcher.isRegistered())
@@ -128,7 +130,7 @@
         intentFilter = IntentFilter(ACTION_1)
 
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiver, intentFilter, mockHandler, USER_HANDLE))
+                ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
         testableLooper.processAllMessages()
         reset(mockContext)
 
@@ -151,9 +153,9 @@
         }
 
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiver, intentFilter, mockHandler, USER_HANDLE))
+                ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiverOther, intentFilterOther, mockHandler, USER_HANDLE))
+                ReceiverData(broadcastReceiverOther, intentFilterOther, fakeExecutor, USER_HANDLE))
 
         testableLooper.processAllMessages()
         assertTrue(userBroadcastDispatcher.isRegistered())
@@ -179,14 +181,15 @@
         intentFilterOther = IntentFilter(ACTION_2)
 
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiver, intentFilter, handler, USER_HANDLE))
+                ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiverOther, intentFilterOther, handler, USER_HANDLE))
+                ReceiverData(broadcastReceiverOther, intentFilterOther, fakeExecutor, USER_HANDLE))
 
         val intent = Intent(ACTION_2)
 
         userBroadcastDispatcher.onReceive(mockContext, intent)
         testableLooper.processAllMessages()
+        fakeExecutor.runAllReady()
 
         verify(broadcastReceiver, never()).onReceive(any(), any())
         verify(broadcastReceiverOther).onReceive(mockContext, intent)
@@ -198,14 +201,15 @@
         intentFilterOther = IntentFilter(ACTION_2)
 
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiver, intentFilter, handler, USER_HANDLE))
+                ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiver, intentFilterOther, handler, USER_HANDLE))
+                ReceiverData(broadcastReceiver, intentFilterOther, fakeExecutor, USER_HANDLE))
 
         val intent = Intent(ACTION_2)
 
         userBroadcastDispatcher.onReceive(mockContext, intent)
         testableLooper.processAllMessages()
+        fakeExecutor.runAllReady()
 
         verify(broadcastReceiver).onReceive(mockContext, intent)
     }
@@ -218,14 +222,15 @@
         intentFilterOther.addCategory(CATEGORY_2)
 
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiver, intentFilter, handler, USER_HANDLE))
+                ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiverOther, intentFilterOther, handler, USER_HANDLE))
+                ReceiverData(broadcastReceiverOther, intentFilterOther, fakeExecutor, USER_HANDLE))
 
         val intent = Intent(ACTION_1)
 
         userBroadcastDispatcher.onReceive(mockContext, intent)
         testableLooper.processAllMessages()
+        fakeExecutor.runAllReady()
 
         verify(broadcastReceiver).onReceive(mockContext, intent)
         verify(broadcastReceiverOther).onReceive(mockContext, intent)
@@ -235,12 +240,13 @@
     fun testPendingResult() {
         intentFilter = IntentFilter(ACTION_1)
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiver, intentFilter, handler, USER_HANDLE))
+                ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
 
         val intent = Intent(ACTION_1)
         userBroadcastDispatcher.onReceive(mockContext, intent)
 
         testableLooper.processAllMessages()
+        fakeExecutor.runAllReady()
 
         verify(broadcastReceiver).onReceive(mockContext, intent)
         verify(broadcastReceiver).pendingResult = mPendingResult
@@ -250,15 +256,16 @@
     fun testRemoveReceiverReferences() {
         intentFilter = IntentFilter(ACTION_1)
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiver, intentFilter, handler, USER_HANDLE))
+                ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
 
         intentFilterOther = IntentFilter(ACTION_1)
         intentFilterOther.addAction(ACTION_2)
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiverOther, intentFilterOther, handler, USER_HANDLE))
+                ReceiverData(broadcastReceiverOther, intentFilterOther, fakeExecutor, USER_HANDLE))
 
         userBroadcastDispatcher.unregisterReceiver(broadcastReceiver)
         testableLooper.processAllMessages()
+        fakeExecutor.runAllReady()
 
         assertFalse(userBroadcastDispatcher.isReceiverReferenceHeld(broadcastReceiver))
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
index 167f361..548da8e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
@@ -110,7 +110,7 @@
     @Test
     public void testReceiverIsRegisteredToDispatcherOnStart() {
         mPowerUI.start();
-        verify(mBroadcastDispatcher).registerReceiver(
+        verify(mBroadcastDispatcher).registerReceiverWithHandler(
                 any(BroadcastReceiver.class),
                 any(IntentFilter.class),
                 any(Handler.class)); //PowerUI does not call with User
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
index 3fdbd3e..77659df 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
@@ -47,9 +47,6 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpansionLogger;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener;
-import com.android.systemui.statusbar.notification.row.NotifRemoteViewCache;
 import com.android.systemui.statusbar.notification.row.NotificationContentInflater;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
@@ -75,7 +72,6 @@
     public static final UserHandle USER_HANDLE = UserHandle.of(ActivityManager.getCurrentUser());
 
     private static final String GROUP_KEY = "gruKey";
-    private static final String APP_NAME = "appName";
 
     private final Context mContext;
     private int mId;
@@ -307,6 +303,9 @@
                 null /* root */,
                 false /* attachToRoot */);
         ExpandableNotificationRow row = mRow;
+        row.setGroupManager(mGroupManager);
+        row.setHeadsUpManager(mHeadsUpManager);
+        row.setAboveShelfChangedListener(aboveShelf -> {});
 
         final NotificationChannel channel =
                 new NotificationChannel(
@@ -330,23 +329,6 @@
         entry.setRow(row);
         entry.createIcons(mContext, entry.getSbn());
         row.setEntry(entry);
-
-        NotificationContentInflater contentBinder = new NotificationContentInflater(
-                mock(NotifRemoteViewCache.class),
-                mock(NotificationRemoteInputManager.class));
-        contentBinder.setInflateSynchronously(true);
-
-        row.initialize(
-                APP_NAME,
-                entry.getKey(),
-                mock(ExpansionLogger.class),
-                mock(KeyguardBypassController.class),
-                mGroupManager,
-                mHeadsUpManager,
-                contentBinder,
-                mock(OnExpandClickListener.class));
-        row.setAboveShelfChangedListener(aboveShelf -> { });
-
         row.setInflationFlags(extraInflationFlags);
         inflateAndWait(row);
 
@@ -359,6 +341,7 @@
 
     private static void inflateAndWait(ExpandableNotificationRow row) throws Exception {
         CountDownLatch countDownLatch = new CountDownLatch(1);
+        row.getNotificationInflater().setInflateSynchronously(true);
         NotificationContentInflater.InflationCallback callback =
                 new NotificationContentInflater.InflationCallback() {
                     @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index 1c29453..cd33cf9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -83,14 +83,12 @@
 import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
 import com.android.systemui.statusbar.notification.collection.NotificationRowBinder;
 import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl;
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 import com.android.systemui.statusbar.notification.logging.NotifLog;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.NotifRemoteViewCache;
-import com.android.systemui.statusbar.notification.row.NotificationContentInflater;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
-import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder;
 import com.android.systemui.statusbar.notification.row.RowInflaterTask;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -236,7 +234,8 @@
                         mock(NotificationFilter.class),
                         mNotifLog,
                         mock(NotificationSectionsFeatureManager.class),
-                        mock(PeopleNotificationIdentifier.class)),
+                        mock(PeopleNotificationIdentifier.class),
+                        mock(HighPriorityProvider.class)),
                 mEnvironment,
                 mFeatureFlags
         );
@@ -244,14 +243,8 @@
         mEntryManager.addNotificationEntryListener(mEntryListener);
         mEntryManager.setNotificationRemoveInterceptor(mRemoveInterceptor);
 
-        NotificationRowContentBinder contentBinder = new NotificationContentInflater(
-                mock(NotifRemoteViewCache.class),
-                mRemoteInputManager);
-
         NotificationRowBinderImpl notificationRowBinder =
-                new NotificationRowBinderImpl(mContext,
-                        contentBinder,
-                        true, /* allowLongPress */
+                new NotificationRowBinderImpl(mContext, true, /* allowLongPress */
                         mock(KeyguardBypassController.class),
                         mock(StatusBarStateController.class),
                         mock(NotificationLogger.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryTest.java
deleted file mode 100644
index a06d6c1..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryTest.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright (C) 2019 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.systemui.statusbar.notification.collection;
-
-import static android.app.NotificationManager.IMPORTANCE_HIGH;
-import static android.app.NotificationManager.IMPORTANCE_MIN;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.testing.AndroidTestingRunner;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.RankingBuilder;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class GroupEntryTest extends SysuiTestCase {
-    @Test
-    public void testIsHighPriority_addChild() {
-        // GIVEN a GroupEntry with a lowPrioritySummary and no children
-        final GroupEntry parentEntry = new GroupEntry("test_group_key");
-        final NotificationEntry lowPrioritySummary = createNotifEntry(false);
-        setSummary(parentEntry, lowPrioritySummary);
-        assertFalse(parentEntry.isHighPriority());
-
-        // WHEN we add a high priority child and invalidate derived members
-        addChild(parentEntry, createNotifEntry(true));
-        parentEntry.invalidateDerivedMembers();
-
-        // THEN the GroupEntry's priority is updated to high even though the summary is still low
-        // priority
-        assertTrue(parentEntry.isHighPriority());
-        assertFalse(lowPrioritySummary.isHighPriority());
-    }
-
-    @Test
-    public void testIsHighPriority_clearChildren() {
-        // GIVEN a GroupEntry with a lowPrioritySummary and high priority children
-        final GroupEntry parentEntry = new GroupEntry("test_group_key");
-        setSummary(parentEntry, createNotifEntry(false));
-        addChild(parentEntry, createNotifEntry(true));
-        addChild(parentEntry, createNotifEntry(true));
-        addChild(parentEntry, createNotifEntry(true));
-        assertTrue(parentEntry.isHighPriority());
-
-        // WHEN we clear the children and invalidate derived members
-        parentEntry.clearChildren();
-        parentEntry.invalidateDerivedMembers();
-
-        // THEN the parentEntry isn't high priority anymore
-        assertFalse(parentEntry.isHighPriority());
-    }
-
-    @Test
-    public void testIsHighPriority_summaryUpdated() {
-        // GIVEN a GroupEntry with a lowPrioritySummary and no children
-        final GroupEntry parentEntry = new GroupEntry("test_group_key");
-        final NotificationEntry lowPrioritySummary = createNotifEntry(false);
-        setSummary(parentEntry, lowPrioritySummary);
-        assertFalse(parentEntry.isHighPriority());
-
-        // WHEN the summary changes to high priority and invalidates its derived members
-        lowPrioritySummary.setRanking(
-                new RankingBuilder()
-                        .setKey(lowPrioritySummary.getKey())
-                        .setImportance(IMPORTANCE_HIGH)
-                        .build());
-        lowPrioritySummary.invalidateDerivedMembers();
-        assertTrue(lowPrioritySummary.isHighPriority());
-
-        // THEN the GroupEntry's priority is updated to high
-        assertTrue(parentEntry.isHighPriority());
-    }
-
-    @Test
-    public void testIsHighPriority_checkChildrenToCalculatePriority() {
-        // GIVEN:
-        // GroupEntry = parentEntry, summary = lowPrioritySummary
-        //      NotificationEntry = lowPriorityChild
-        //      NotificationEntry = highPriorityChild
-        final GroupEntry parentEntry = new GroupEntry("test_group_key");
-        setSummary(parentEntry, createNotifEntry(false));
-        addChild(parentEntry, createNotifEntry(false));
-        addChild(parentEntry, createNotifEntry(true));
-
-        // THEN the GroupEntry parentEntry is high priority since it has a high priority child
-        assertTrue(parentEntry.isHighPriority());
-    }
-
-    @Test
-    public void testIsHighPriority_childEntryRankingUpdated() {
-        // GIVEN:
-        // GroupEntry = parentEntry, summary = lowPrioritySummary
-        //      NotificationEntry = lowPriorityChild
-        final GroupEntry parentEntry = new GroupEntry("test_group_key");
-        final NotificationEntry lowPriorityChild = createNotifEntry(false);
-        setSummary(parentEntry, createNotifEntry(false));
-        addChild(parentEntry, lowPriorityChild);
-
-        // WHEN the child entry ranking changes to high priority and invalidates its derived members
-        lowPriorityChild.setRanking(
-                new RankingBuilder()
-                        .setKey(lowPriorityChild.getKey())
-                        .setImportance(IMPORTANCE_HIGH)
-                        .build());
-        lowPriorityChild.invalidateDerivedMembers();
-
-        // THEN the parent entry's high priority value is updated - but not the parent's summary
-        assertTrue(parentEntry.isHighPriority());
-        assertFalse(parentEntry.getSummary().isHighPriority());
-    }
-
-    private NotificationEntry createNotifEntry(boolean highPriority) {
-        return new NotificationEntryBuilder()
-                .setImportance(highPriority ? IMPORTANCE_HIGH : IMPORTANCE_MIN)
-                .build();
-    }
-
-    private void setSummary(GroupEntry parent, NotificationEntry summary) {
-        parent.setSummary(summary);
-        summary.setParent(parent);
-    }
-
-    private void addChild(GroupEntry parent, NotificationEntry child) {
-        parent.addChild(child);
-        child.setParent(parent);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java
new file mode 100644
index 0000000..93909dc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2020 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.systemui.statusbar.notification.collection;
+
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.app.NotificationManager.IMPORTANCE_MIN;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.RankingBuilder;
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class HighPriorityProviderTest extends SysuiTestCase {
+    @Mock private PeopleNotificationIdentifier mPeopleNotificationIdentifier;
+    private HighPriorityProvider mHighPriorityProvider;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mHighPriorityProvider = new HighPriorityProvider(mPeopleNotificationIdentifier);
+    }
+
+    @Test
+    public void highImportance() {
+        // GIVEN notification has high importance
+        final NotificationEntry entry = new NotificationEntryBuilder()
+                .setImportance(IMPORTANCE_HIGH)
+                .build();
+        when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(false);
+
+        // THEN it has high priority
+        assertTrue(mHighPriorityProvider.isHighPriority(entry));
+    }
+
+    @Test
+    public void peopleNotification() {
+        // GIVEN notification is low importance and is a people notification
+        final Notification notification = new Notification.Builder(mContext, "test")
+                .build();
+        final NotificationEntry entry = new NotificationEntryBuilder()
+                .setNotification(notification)
+                .setImportance(IMPORTANCE_LOW)
+                .build();
+        when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(true);
+
+        // THEN it has high priority
+        assertTrue(mHighPriorityProvider.isHighPriority(entry));
+    }
+
+    @Test
+    public void messagingStyle() {
+        // GIVEN notification is low importance but has messaging style
+        final Notification notification = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.MessagingStyle(""))
+                .build();
+        final NotificationEntry entry = new NotificationEntryBuilder()
+                .setNotification(notification)
+                .build();
+        when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(false);
+
+        // THEN it has high priority
+        assertTrue(mHighPriorityProvider.isHighPriority(entry));
+    }
+
+    @Test
+    public void lowImportanceForeground() {
+        // GIVEN notification is low importance and is associated with a foreground service
+        final Notification notification = mock(Notification.class);
+        when(notification.isForegroundService()).thenReturn(true);
+
+        final NotificationEntry entry = new NotificationEntryBuilder()
+                .setNotification(notification)
+                .setImportance(IMPORTANCE_LOW)
+                .build();
+        when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(false);
+
+        // THEN it has high priority
+        assertTrue(mHighPriorityProvider.isHighPriority(entry));
+    }
+
+    @Test
+    public void minImportanceForeground() {
+        // GIVEN notification is low importance and is associated with a foreground service
+        final Notification notification = mock(Notification.class);
+        when(notification.isForegroundService()).thenReturn(true);
+
+        final NotificationEntry entry = new NotificationEntryBuilder()
+                .setNotification(notification)
+                .setImportance(IMPORTANCE_MIN)
+                .build();
+        when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(false);
+
+        // THEN it does NOT have high priority
+        assertFalse(mHighPriorityProvider.isHighPriority(entry));
+    }
+
+    @Test
+    public void userChangeTrumpsHighPriorityCharacteristics() {
+        // GIVEN notification has high priority characteristics but the user changed the importance
+        // to less than IMPORTANCE_DEFAULT (ie: IMPORTANCE_LOW or IMPORTANCE_MIN)
+        final Notification notification = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.MessagingStyle(""))
+                .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
+                .build();
+        final NotificationChannel channel = new NotificationChannel("a", "a",
+                IMPORTANCE_LOW);
+        channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
+
+        final NotificationEntry entry = new NotificationEntryBuilder()
+                .setNotification(notification)
+                .setChannel(channel)
+                .build();
+        when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(true);
+
+        // THEN it does NOT have high priority
+        assertFalse(mHighPriorityProvider.isHighPriority(entry));
+    }
+
+    @Test
+    public void testIsHighPriority_summaryUpdated() {
+        // GIVEN a GroupEntry with a lowPrioritySummary and no children
+        final GroupEntry parentEntry = new GroupEntry("test_group_key");
+        final NotificationEntry lowPrioritySummary = createNotifEntry(false);
+        setSummary(parentEntry, lowPrioritySummary);
+        assertFalse(mHighPriorityProvider.isHighPriority(parentEntry));
+
+        // WHEN the summary changes to high priority
+        lowPrioritySummary.setRanking(
+                new RankingBuilder()
+                        .setKey(lowPrioritySummary.getKey())
+                        .setImportance(IMPORTANCE_HIGH)
+                        .build());
+        assertTrue(mHighPriorityProvider.isHighPriority(lowPrioritySummary));
+
+        // THEN the GroupEntry's priority is updated to high
+        assertTrue(mHighPriorityProvider.isHighPriority(parentEntry));
+    }
+
+    @Test
+    public void testIsHighPriority_checkChildrenToCalculatePriority() {
+        // GIVEN:
+        // GroupEntry = parentEntry, summary = lowPrioritySummary
+        //      NotificationEntry = lowPriorityChild
+        //      NotificationEntry = highPriorityChild
+        final GroupEntry parentEntry = new GroupEntry("test_group_key");
+        setSummary(parentEntry, createNotifEntry(false));
+        addChild(parentEntry, createNotifEntry(false));
+        addChild(parentEntry, createNotifEntry(true));
+
+        // THEN the GroupEntry parentEntry is high priority since it has a high priority child
+        assertTrue(mHighPriorityProvider.isHighPriority(parentEntry));
+    }
+
+    @Test
+    public void testIsHighPriority_childEntryRankingUpdated() {
+        // GIVEN:
+        // GroupEntry = parentEntry, summary = lowPrioritySummary
+        //      NotificationEntry = lowPriorityChild
+        final GroupEntry parentEntry = new GroupEntry("test_group_key");
+        final NotificationEntry lowPriorityChild = createNotifEntry(false);
+        setSummary(parentEntry, createNotifEntry(false));
+        addChild(parentEntry, lowPriorityChild);
+
+        // WHEN the child entry ranking changes to high priority
+        lowPriorityChild.setRanking(
+                new RankingBuilder()
+                        .setKey(lowPriorityChild.getKey())
+                        .setImportance(IMPORTANCE_HIGH)
+                        .build());
+
+        // THEN the parent entry's high priority value is updated - but not the parent's summary
+        assertTrue(mHighPriorityProvider.isHighPriority(parentEntry));
+        assertFalse(mHighPriorityProvider.isHighPriority(parentEntry.getSummary()));
+    }
+
+    private NotificationEntry createNotifEntry(boolean highPriority) {
+        return new NotificationEntryBuilder()
+                .setImportance(highPriority ? IMPORTANCE_HIGH : IMPORTANCE_MIN)
+                .build();
+    }
+
+    private void setSummary(GroupEntry parent, NotificationEntry summary) {
+        parent.setSummary(summary);
+        summary.setParent(parent);
+    }
+
+    private void addChild(GroupEntry parent, NotificationEntry child) {
+        parent.addChild(child);
+        child.setParent(parent);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
index 39ae68a..5b0b668 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
@@ -21,8 +21,6 @@
 import static android.app.Notification.CATEGORY_EVENT;
 import static android.app.Notification.CATEGORY_MESSAGE;
 import static android.app.Notification.CATEGORY_REMINDER;
-import static android.app.NotificationManager.IMPORTANCE_HIGH;
-import static android.app.NotificationManager.IMPORTANCE_MIN;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
 
 import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
@@ -92,44 +90,6 @@
     }
 
     @Test
-    public void testIsHighPriority_notificationUpdates() {
-        // GIVEN a notification with high importance
-        final NotificationEntry entryHigh = new NotificationEntryBuilder()
-                .setImportance(IMPORTANCE_HIGH)
-                .build();
-
-        // WHEN we get the value for the high priority entry, we're caching the high priority value
-        assertTrue(entryHigh.isHighPriority());
-
-        // WHEN we change the ranking and derived members (high priority) are invalidated
-        entryHigh.setRanking(
-                new RankingBuilder()
-                        .setKey(entryHigh.getKey())
-                        .setImportance(IMPORTANCE_MIN)
-                        .build());
-        entryHigh.invalidateDerivedMembers();
-
-        // THEN the priority is recalculated and is now low
-        assertFalse(entryHigh.isHighPriority());
-
-        // WHEN the sbn is updated to have messaging style (high priority characteristic)
-        //  AND the entry invalidates its derived members
-        final Notification notification =
-                new Notification.Builder(mContext, "test")
-                        .setStyle(new Notification.MessagingStyle(""))
-                        .build();
-        final StatusBarNotification sbn = entryHigh.getSbn();
-        entryHigh.setSbn(new StatusBarNotification(
-                sbn.getPackageName(), sbn.getPackageName(),
-                sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(),
-                notification, sbn.getUser(), sbn.getOverrideGroupKey(), 0));
-        entryHigh.invalidateDerivedMembers();
-
-        // THEN the priority is recalculated and is now high
-        assertTrue(entryHigh.isHighPriority());
-    }
-
-    @Test
     public void testIsExemptFromDndVisualSuppression_foreground() {
         mEntry.getSbn().getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
index 10450fa..e273191 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.statusbar.NotificationMediaManager
 import com.android.systemui.statusbar.notification.NotificationFilter
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
 import com.android.systemui.statusbar.notification.logging.NotifLog
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
 import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING
@@ -62,7 +63,8 @@
                 mock(NotificationFilter::class.java),
                 mock(NotifLog::class.java),
                 mock(NotificationSectionsFeatureManager::class.java),
-                personNotificationIdentifier
+                personNotificationIdentifier,
+                HighPriorityProvider(personNotificationIdentifier)
         )
     }
 
@@ -182,7 +184,8 @@
         filter: NotificationFilter,
         notifLog: NotifLog,
         sectionsFeatureManager: NotificationSectionsFeatureManager,
-        peopleNotificationIdentifier: PeopleNotificationIdentifier
+        peopleNotificationIdentifier: PeopleNotificationIdentifier,
+        highPriorityProvider: HighPriorityProvider
     ) : NotificationRankingManager(
         mediaManager,
         groupManager,
@@ -190,7 +193,8 @@
         filter,
         notifLog,
         sectionsFeatureManager,
-        peopleNotificationIdentifier
+        peopleNotificationIdentifier,
+        highPriorityProvider
     ) {
         fun applyTestRankingMap(r: RankingMap) {
             rankingMap = r
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
index 979b8a9..f921cf9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
@@ -45,6 +45,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.Before;
@@ -66,6 +67,7 @@
     @Mock private BroadcastDispatcher mBroadcastDispatcher;
     @Mock private StatusBarStateController mStatusBarStateController;
     @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    @Mock private HighPriorityProvider mHighPriorityProvider;
     @Mock private NotifListBuilderImpl mNotifListBuilder;
 
     private NotificationEntry mEntry;
@@ -78,7 +80,7 @@
         mKeyguardCoordinator = new KeyguardCoordinator(
                 mContext, mMainHandler, mKeyguardStateController, mLockscreenUserManager,
                 mBroadcastDispatcher, mStatusBarStateController,
-                mKeyguardUpdateMonitor);
+                mKeyguardUpdateMonitor, mHighPriorityProvider);
 
         mEntry = new NotificationEntryBuilder()
                 .setUser(new UserHandle(NOTIF_USER_ID))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProviderTest.java
deleted file mode 100644
index 6fa1a89..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProviderTest.java
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * Copyright (C) 2019 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.systemui.statusbar.notification.collection.provider;
-
-import static android.app.NotificationManager.IMPORTANCE_HIGH;
-import static android.app.NotificationManager.IMPORTANCE_LOW;
-import static android.app.NotificationManager.IMPORTANCE_MIN;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.Person;
-import android.testing.AndroidTestingRunner;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class IsHighPriorityProviderTest extends SysuiTestCase {
-    private IsHighPriorityProvider mIsHighPriorityProvider;
-
-    @Before
-    public void setup() {
-        mIsHighPriorityProvider = new IsHighPriorityProvider();
-    }
-
-    @Test
-    public void testCache() {
-        // GIVEN a notification with high importance
-        final NotificationEntry entryHigh = new NotificationEntryBuilder()
-                .setImportance(IMPORTANCE_HIGH)
-                .build();
-
-        // GIVEN notification with min importance
-        final NotificationEntry entryMin = new NotificationEntryBuilder()
-                .setImportance(IMPORTANCE_MIN)
-                .build();
-
-        // WHEN we get the value for the high priority entry
-        assertTrue(mIsHighPriorityProvider.get(entryHigh));
-
-        // THEN the value is cached, so even when passed an entryMin, we still get high priority
-        assertTrue(mIsHighPriorityProvider.get(entryMin));
-
-        // UNTIL the provider is invalidated
-        mIsHighPriorityProvider.invalidate();
-
-        // THEN the priority is recalculated
-        assertFalse(mIsHighPriorityProvider.get(entryMin));
-    }
-
-    @Test
-    public void highImportance() {
-        // GIVEN notification has high importance
-        final NotificationEntry entry = new NotificationEntryBuilder()
-                .setImportance(IMPORTANCE_HIGH)
-                .build();
-
-        // THEN it has high priority
-        assertTrue(mIsHighPriorityProvider.get(entry));
-    }
-
-    @Test
-    public void peopleNotification() {
-        // GIVEN notification is low importance but has a person associated with it
-        final Notification notification = new Notification.Builder(mContext, "test")
-                .addPerson(
-                        new Person.Builder()
-                                .setName("name")
-                                .setKey("abc")
-                                .setUri("uri")
-                                .setBot(true)
-                                .build())
-                .build();
-
-        final NotificationEntry entry = new NotificationEntryBuilder()
-                .setNotification(notification)
-                .setImportance(IMPORTANCE_LOW)
-                .build();
-
-        // THEN it has high priority
-        assertTrue(mIsHighPriorityProvider.get(entry));
-    }
-
-    @Test
-    public void messagingStyle() {
-        // GIVEN notification is low importance but has messaging style
-        final Notification notification = new Notification.Builder(mContext, "test")
-                .setStyle(new Notification.MessagingStyle(""))
-                .build();
-
-        final NotificationEntry entry = new NotificationEntryBuilder()
-                .setNotification(notification)
-                .build();
-
-        // THEN it has high priority
-        assertTrue(mIsHighPriorityProvider.get(entry));
-    }
-
-    @Test
-    public void lowImportanceForeground() {
-        // GIVEN notification is low importance and is associated with a foreground service
-        final Notification notification = mock(Notification.class);
-        when(notification.isForegroundService()).thenReturn(true);
-
-        final NotificationEntry entry = new NotificationEntryBuilder()
-                .setNotification(notification)
-                .setImportance(IMPORTANCE_LOW)
-                .build();
-
-        // THEN it has high priority
-        assertTrue(mIsHighPriorityProvider.get(entry));
-    }
-
-    @Test
-    public void minImportanceForeground() {
-        // GIVEN notification is low importance and is associated with a foreground service
-        final Notification notification = mock(Notification.class);
-        when(notification.isForegroundService()).thenReturn(true);
-
-        final NotificationEntry entry = new NotificationEntryBuilder()
-                .setNotification(notification)
-                .setImportance(IMPORTANCE_MIN)
-                .build();
-
-        // THEN it does NOT have high priority
-        assertFalse(mIsHighPriorityProvider.get(entry));
-    }
-
-    @Test
-    public void userChangeTrumpsHighPriorityCharacteristics() {
-        // GIVEN notification has high priority characteristics but the user changed the importance
-        // to less than IMPORTANCE_DEFAULT (ie: IMPORTANCE_LOW or IMPORTANCE_MIN)
-        final Notification notification = new Notification.Builder(mContext, "test")
-                .addPerson(
-                        new Person.Builder()
-                                .setName("name")
-                                .setKey("abc")
-                                .setUri("uri")
-                                .setBot(true)
-                                .build())
-                .setStyle(new Notification.MessagingStyle(""))
-                .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
-                .build();
-
-        final NotificationChannel channel = new NotificationChannel("a", "a",
-                IMPORTANCE_LOW);
-        channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
-
-        final NotificationEntry entry = new NotificationEntryBuilder()
-                .setNotification(notification)
-                .setChannel(channel)
-                .build();
-
-        // THEN it does NOT have high priority
-        assertFalse(mIsHighPriorityProvider.get(entry));
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java
deleted file mode 100644
index d7214f3..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 2020 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.systemui.statusbar.notification.row;
-
-import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
-import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-
-import android.testing.AndroidTestingRunner;
-import android.widget.RemoteViews;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.notification.NotificationEntryListener;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class NotifRemoteViewCacheImplTest extends SysuiTestCase {
-
-    private NotifRemoteViewCacheImpl mNotifRemoteViewCache;
-    private NotificationEntry mEntry;
-    private NotificationEntryListener mEntryListener;
-    @Mock private RemoteViews mRemoteViews;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mEntry = new NotificationEntryBuilder().build();
-
-        NotificationEntryManager entryManager = mock(NotificationEntryManager.class);
-        mNotifRemoteViewCache = new NotifRemoteViewCacheImpl(entryManager);
-        ArgumentCaptor<NotificationEntryListener> entryListenerCaptor =
-                ArgumentCaptor.forClass(NotificationEntryListener.class);
-        verify(entryManager).addNotificationEntryListener(entryListenerCaptor.capture());
-        mEntryListener = entryListenerCaptor.getValue();
-    }
-
-    @Test
-    public void testPutCachedView() {
-        // GIVEN an initialized cache for an entry.
-        mEntryListener.onPendingEntryAdded(mEntry);
-
-        // WHEN a notification's cached remote views is put in.
-        mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews);
-
-        // THEN the remote view is cached.
-        assertTrue(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED));
-        assertEquals(
-                "Cached remote view is not the one we put in.",
-                mRemoteViews,
-                mNotifRemoteViewCache.getCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED));
-    }
-
-    @Test
-    public void testRemoveCachedView() {
-        // GIVEN a cache with a cached view.
-        mEntryListener.onPendingEntryAdded(mEntry);
-        mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews);
-
-        // WHEN we remove the cached view.
-        mNotifRemoteViewCache.removeCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED);
-
-        // THEN the remote view is not cached.
-        assertFalse(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED));
-    }
-
-    @Test
-    public void testClearCache() {
-        // GIVEN a non-empty cache.
-        mEntryListener.onPendingEntryAdded(mEntry);
-        mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews);
-        mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_EXPANDED, mRemoteViews);
-
-        // WHEN we clear the cache.
-        mNotifRemoteViewCache.clearCache(mEntry);
-
-        // THEN the cache is empty.
-        assertFalse(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED));
-        assertFalse(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_EXPANDED));
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index cb9da6a..f916fe5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -22,16 +22,11 @@
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
 import android.app.Notification;
 import android.content.Context;
@@ -40,17 +35,16 @@
 import android.os.Looper;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
+import android.util.ArrayMap;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.RemoteViews;
-import android.widget.TextView;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.filters.Suppress;
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.InflationTask;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams;
@@ -63,8 +57,6 @@
 import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
 
 import java.util.HashMap;
 import java.util.concurrent.CountDownLatch;
@@ -81,11 +73,8 @@
     private Notification.Builder mBuilder;
     private ExpandableNotificationRow mRow;
 
-    @Mock private NotifRemoteViewCache mCache;
-
     @Before
     public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
         mBuilder = new Notification.Builder(mContext).setSmallIcon(
                 R.drawable.ic_person)
                 .setContentTitle("Title")
@@ -94,9 +83,7 @@
         ExpandableNotificationRow row = new NotificationTestHelper(mContext, mDependency).createRow(
                 mBuilder.build());
         mRow = spy(row);
-        mNotificationInflater = new NotificationContentInflater(
-                mCache,
-                mock(NotificationRemoteInputManager.class));
+        mNotificationInflater = new NotificationContentInflater();
     }
 
     @Test
@@ -187,9 +174,7 @@
                 result,
                 FLAG_CONTENT_VIEW_EXPANDED,
                 0,
-                mock(NotifRemoteViewCache.class),
-                mRow.getEntry(),
-                mRow,
+                new ArrayMap() /* cachedContentViews */, mRow,
                 true /* isNewView */, (v, p, r) -> true,
                 new InflationCallback() {
                     @Override
@@ -259,71 +244,6 @@
                 NotificationContentInflater.canReapplyRemoteView(mediaView, decoratedMediaView));
     }
 
-    @Test
-    public void testUsesSameViewWhenCachedPossibleToReuse() throws Exception {
-        // GIVEN a cached view.
-        RemoteViews contractedRemoteView = mBuilder.createContentView();
-        when(mCache.hasCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED))
-                .thenReturn(true);
-        when(mCache.getCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED))
-                .thenReturn(contractedRemoteView);
-
-        // GIVEN existing bound view with same layout id.
-        View view = contractedRemoteView.apply(mContext, null /* parent */);
-        mRow.getPrivateLayout().setContractedChild(view);
-
-        // WHEN inflater inflates
-        inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, mRow);
-
-        // THEN the view should be re-used
-        assertEquals("Binder inflated a new view even though the old one was cached and usable.",
-                view, mRow.getPrivateLayout().getContractedChild());
-    }
-
-    @Test
-    public void testInflatesNewViewWhenCachedNotPossibleToReuse() throws Exception {
-        // GIVEN a cached remote view.
-        RemoteViews contractedRemoteView = mBuilder.createHeadsUpContentView();
-        when(mCache.hasCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED))
-                .thenReturn(true);
-        when(mCache.getCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED))
-                .thenReturn(contractedRemoteView);
-
-        // GIVEN existing bound view with different layout id.
-        View view = new TextView(mContext);
-        mRow.getPrivateLayout().setContractedChild(view);
-
-        // WHEN inflater inflates
-        inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, mRow);
-
-        // THEN the view should be a new view
-        assertNotEquals("Binder (somehow) used the same view when inflating.",
-                view, mRow.getPrivateLayout().getContractedChild());
-    }
-
-    @Test
-    public void testInflationCachesCreatedRemoteView() throws Exception {
-        // WHEN inflater inflates
-        inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, mRow);
-
-        // THEN inflater informs cache of the new remote view
-        verify(mCache).putCachedView(
-                eq(mRow.getEntry()),
-                eq(FLAG_CONTENT_VIEW_CONTRACTED),
-                any());
-    }
-
-    @Test
-    public void testUnbindRemovesCachedRemoteView() {
-        // WHEN inflated unbinds content
-        mNotificationInflater.unbindContent(mRow.getEntry(), mRow, FLAG_CONTENT_VIEW_HEADS_UP);
-
-        // THEN inflated informs cache to remove remote view
-        verify(mCache).removeCachedView(
-                eq(mRow.getEntry()),
-                eq(FLAG_CONTENT_VIEW_HEADS_UP));
-    }
-
     private static void inflateAndWait(NotificationContentInflater inflater,
             @InflationFlag int contentToInflate,
             ExpandableNotificationRow row)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index ccc9496..4e27770 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -70,6 +70,7 @@
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.phone.StatusBar;
@@ -113,6 +114,7 @@
     @Mock private DeviceProvisionedController mDeviceProvisionedController;
     @Mock private StatusBar mStatusBar;
     @Mock private AccessibilityManager mAccessibilityManager;
+    @Mock private HighPriorityProvider mHighPriorityProvider;
 
     @Before
     public void setUp() {
@@ -128,7 +130,7 @@
         when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
 
         mGutsManager = new NotificationGutsManager(mContext, mVisualStabilityManager,
-                () -> mStatusBar, mHandler, mAccessibilityManager);
+                () -> mStatusBar, mHandler, mAccessibilityManager, mHighPriorityProvider);
         mGutsManager.setUpWithPresenter(mPresenter, mStackScroller,
                 mCheckSaveListener, mOnSettingsClickListener);
         mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
@@ -391,6 +393,7 @@
                 .build();
 
         when(row.getIsNonblockable()).thenReturn(false);
+        when(mHighPriorityProvider.isHighPriority(entry)).thenReturn(true);
         StatusBarNotification statusBarNotification = entry.getSbn();
         mGutsManager.initializeNotificationInfo(row, notificationInfoView);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
index 003d803..518b670 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
@@ -267,8 +267,6 @@
                     ExpandableNotificationRow notifRow = mock(ExpandableNotificationRow.class,
                             RETURNS_DEEP_STUBS);
                     when(notifRow.getVisibility()).thenReturn(View.VISIBLE);
-                    when(notifRow.getEntry().isHighPriority())
-                            .thenReturn(children[i] == ChildType.HIPRI);
                     when(notifRow.getEntry().getBucket()).thenReturn(
                             children[i] == ChildType.HIPRI ? BUCKET_ALERTING : BUCKET_SILENT);
                     when(notifRow.getParent()).thenReturn(mNssl);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 39f037c..ea8d4ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -71,6 +71,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 import com.android.systemui.statusbar.notification.logging.NotifLog;
 import com.android.systemui.statusbar.notification.people.PeopleHubSectionFooterViewAdapter;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
@@ -165,7 +166,8 @@
                         mock(NotificationFilter.class),
                         mock(NotifLog.class),
                         mock(NotificationSectionsFeatureManager.class),
-                        mock(PeopleNotificationIdentifier.class)
+                        mock(PeopleNotificationIdentifier.class),
+                        mock(HighPriorityProvider.class)
                 ),
                 mock(NotificationEntryManager.KeyguardEnvironment.class),
                 mock(FeatureFlags.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
index 39afbe0..8f645b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
@@ -190,7 +190,7 @@
         mFragments.dispatchResume();
         processAllMessages();
 
-        verify(mBroadcastDispatcher).registerReceiver(
+        verify(mBroadcastDispatcher).registerReceiverWithHandler(
                 any(BroadcastReceiver.class),
                 any(IntentFilter.class),
                 any(Handler.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 3da87ea..7e485f4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -122,7 +122,6 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
-import com.android.systemui.statusbar.notification.row.NotificationContentInflater;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
@@ -248,7 +247,6 @@
     @Mock private DismissCallbackRegistry mDismissCallbackRegistry;
     @Mock private ScreenPinningRequest mScreenPinningRequest;
     @Mock private NotificationEntryManager mEntryManager;
-    @Mock private NotificationContentInflater mNotificationContentInflater;
     @Mock private LockscreenLockIconController mLockscreenLockIconController;
     @Mock private StatusBarNotificationActivityStarter.Builder
             mStatusBarNotificationActivityStarterBuilder;
@@ -358,7 +356,6 @@
                 mNotificationGutsManager,
                 notificationLogger,
                 mEntryManager,
-                mNotificationContentInflater,
                 mNotificationInterruptionStateProvider,
                 mNotificationViewHierarchyManager,
                 mKeyguardViewMediator,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index 2854665..80aa6f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
@@ -69,7 +69,7 @@
 
     @Test
     public void testRegisteredWithDispatcher() {
-        verify(mBroadcastDispatcher).registerReceiver(any(BroadcastReceiver.class),
+        verify(mBroadcastDispatcher).registerReceiverWithHandler(any(BroadcastReceiver.class),
                 any(IntentFilter.class),
                 any(Handler.class)); // VolumeDialogControllerImpl does not call with user
     }
diff --git a/packages/Tethering/Android.bp b/packages/Tethering/Android.bp
index 797b713..d297f3f 100644
--- a/packages/Tethering/Android.bp
+++ b/packages/Tethering/Android.bp
@@ -123,4 +123,5 @@
     use_embedded_native_libs: true,
     // The permission configuration *must* be included to ensure security of the device
     required: ["NetworkPermissionConfig"],
+    apex_available: ["com.android.tethering"],
 }
diff --git a/packages/Tethering/AndroidManifest.xml b/packages/Tethering/AndroidManifest.xml
index 87a8c3f..e99c2c5 100644
--- a/packages/Tethering/AndroidManifest.xml
+++ b/packages/Tethering/AndroidManifest.xml
@@ -33,6 +33,7 @@
     <uses-permission android:name="android.permission.MANAGE_USB" />
     <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
     <uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" />
+    <uses-permission android:name="android.permission.TETHER_PRIVILEGED" />
     <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
     <uses-permission android:name="android.permission.WRITE_SETTINGS" />
 
diff --git a/packages/Tethering/common/TetheringLib/Android.bp b/packages/Tethering/common/TetheringLib/Android.bp
index 5785707..264ce44 100644
--- a/packages/Tethering/common/TetheringLib/Android.bp
+++ b/packages/Tethering/common/TetheringLib/Android.bp
@@ -47,6 +47,16 @@
     libs: [
         "android_system_stubs_current",
     ],
+
+    hostdex: true, // for hiddenapi check
+    visibility: [
+        "//frameworks/base/packages/Tethering:__subpackages__",
+        //TODO(b/147200698) remove below lines when the platform is built with stubs
+        "//frameworks/base",
+        "//frameworks/base/services",
+        "//frameworks/base/services/core",
+    ],
+    apex_available: ["com.android.tethering"],
 }
 
 filegroup {
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java b/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
index fe3f517..5692a6f 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
@@ -16,9 +16,12 @@
 
 package com.android.server.connectivity.tethering;
 
+import static android.net.ConnectivityManager.TYPE_BLUETOOTH;
+import static android.net.ConnectivityManager.TYPE_ETHERNET;
+import static android.net.ConnectivityManager.TYPE_MOBILE;
 import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
 import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
-import static android.net.ConnectivityManager.TYPE_NONE;
+import static android.net.ConnectivityManager.TYPE_WIFI;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
@@ -35,8 +38,10 @@
 import android.net.util.SharedLog;
 import android.os.Handler;
 import android.util.Log;
+import android.util.SparseIntArray;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
 import com.android.internal.util.StateMachine;
 
 import java.util.HashMap;
@@ -77,11 +82,25 @@
     public static final int EVENT_ON_LINKPROPERTIES = 2;
     public static final int EVENT_ON_LOST           = 3;
     public static final int NOTIFY_LOCAL_PREFIXES   = 10;
+    // This value is used by deprecated preferredUpstreamIfaceTypes selection which is default
+    // disabled.
+    @VisibleForTesting
+    public static final int TYPE_NONE = -1;
 
     private static final int CALLBACK_LISTEN_ALL = 1;
     private static final int CALLBACK_DEFAULT_INTERNET = 2;
     private static final int CALLBACK_MOBILE_REQUEST = 3;
 
+    private static final SparseIntArray sLegacyTypeToTransport = new SparseIntArray();
+    static {
+        sLegacyTypeToTransport.put(TYPE_MOBILE,       NetworkCapabilities.TRANSPORT_CELLULAR);
+        sLegacyTypeToTransport.put(TYPE_MOBILE_DUN,   NetworkCapabilities.TRANSPORT_CELLULAR);
+        sLegacyTypeToTransport.put(TYPE_MOBILE_HIPRI, NetworkCapabilities.TRANSPORT_CELLULAR);
+        sLegacyTypeToTransport.put(TYPE_WIFI,         NetworkCapabilities.TRANSPORT_WIFI);
+        sLegacyTypeToTransport.put(TYPE_BLUETOOTH,    NetworkCapabilities.TRANSPORT_BLUETOOTH);
+        sLegacyTypeToTransport.put(TYPE_ETHERNET,     NetworkCapabilities.TRANSPORT_ETHERNET);
+    }
+
     private final Context mContext;
     private final SharedLog mLog;
     private final StateMachine mTarget;
@@ -202,7 +221,7 @@
         final int legacyType = mDunRequired ? TYPE_MOBILE_DUN : TYPE_MOBILE_HIPRI;
 
         final NetworkRequest mobileUpstreamRequest = new NetworkRequest.Builder()
-                .setCapabilities(ConnectivityManager.networkCapabilitiesForType(legacyType))
+                .setCapabilities(networkCapabilitiesForType(legacyType))
                 .build();
 
         // The existing default network and DUN callbacks will be notified.
@@ -354,16 +373,6 @@
         notifyTarget(EVENT_ON_LINKPROPERTIES, network);
     }
 
-    private void handleSuspended(Network network) {
-        if (!network.equals(mTetheringUpstreamNetwork)) return;
-        mLog.log("SUSPENDED current upstream: " + network);
-    }
-
-    private void handleResumed(Network network) {
-        if (!network.equals(mTetheringUpstreamNetwork)) return;
-        mLog.log("RESUMED current upstream: " + network);
-    }
-
     private void handleLost(Network network) {
         // There are few TODOs within ConnectivityService's rematching code
         // pertaining to spurious onLost() notifications.
@@ -453,20 +462,6 @@
         }
 
         @Override
-        public void onNetworkSuspended(Network network) {
-            if (mCallbackType == CALLBACK_LISTEN_ALL) {
-                handleSuspended(network);
-            }
-        }
-
-        @Override
-        public void onNetworkResumed(Network network) {
-            if (mCallbackType == CALLBACK_LISTEN_ALL) {
-                handleResumed(network);
-            }
-        }
-
-        @Override
         public void onLost(Network network) {
             if (mCallbackType == CALLBACK_DEFAULT_INTERNET) {
                 mDefaultInternetNetwork = null;
@@ -510,7 +505,7 @@
         for (int type : preferredTypes) {
             NetworkCapabilities nc;
             try {
-                nc = ConnectivityManager.networkCapabilitiesForType(type);
+                nc = networkCapabilitiesForType(type);
             } catch (IllegalArgumentException iae) {
                 Log.e(TAG, "No NetworkCapabilities mapping for legacy type: " + type);
                 continue;
@@ -572,4 +567,28 @@
 
         return null;
     }
+
+    /**
+     * Given a legacy type (TYPE_WIFI, ...) returns the corresponding NetworkCapabilities instance.
+     * This function is used for deprecated legacy type and be disabled by default.
+     */
+    @VisibleForTesting
+    public static NetworkCapabilities networkCapabilitiesForType(int type) {
+        final NetworkCapabilities nc = new NetworkCapabilities();
+
+        // Map from type to transports.
+        final int notFound = -1;
+        final int transport = sLegacyTypeToTransport.get(type, notFound);
+        Preconditions.checkArgument(transport != notFound, "unknown legacy type: " + type);
+        nc.addTransportType(transport);
+
+        if (type == TYPE_MOBILE_DUN) {
+            nc.addCapability(NetworkCapabilities.NET_CAPABILITY_DUN);
+            // DUN is restricted network, see NetworkCapabilities#FORCE_RESTRICTED_CAPABILITIES.
+            nc.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+        } else {
+            nc.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+        }
+        return nc;
+    }
 }
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
index c90abbb..5ed75bf 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
@@ -18,13 +18,14 @@
 
 import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
 import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
-import static android.net.ConnectivityManager.TYPE_NONE;
 import static android.net.ConnectivityManager.TYPE_WIFI;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 
+import static com.android.server.connectivity.tethering.UpstreamNetworkMonitor.TYPE_NONE;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -538,13 +539,15 @@
                 mUNM.selectPreferredUpstreamType(preferredTypes));
         verify(mEntitleMgr, times(1)).maybeRunProvisioning();
     }
+
     private void assertSatisfiesLegacyType(int legacyType, UpstreamNetworkState ns) {
         if (legacyType == TYPE_NONE) {
             assertTrue(ns == null);
             return;
         }
 
-        final NetworkCapabilities nc = ConnectivityManager.networkCapabilitiesForType(legacyType);
+        final NetworkCapabilities nc =
+                UpstreamNetworkMonitor.networkCapabilitiesForType(legacyType);
         assertTrue(nc.satisfiedByNetworkCapabilities(ns.networkCapabilities));
     }
 
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 6a6e2b2..58d3489 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -35,6 +35,7 @@
 import android.app.ActivityOptions;
 import android.app.AlertDialog;
 import android.app.PendingIntent;
+import android.app.RemoteAction;
 import android.appwidget.AppWidgetManagerInternal;
 import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
@@ -148,6 +149,8 @@
     //       their capabilities are ready.
     private static final int WAIT_MOTION_INJECTOR_TIMEOUT_MILLIS = 1000;
 
+    static final String FUNCTION_REGISTER_SYSTEM_ACTION = "registerSystemAction";
+    static final String FUNCTION_UNREGISTER_SYSTEM_ACTION = "unregisterSystemAction";
     private static final String FUNCTION_REGISTER_UI_TEST_AUTOMATION_SERVICE =
         "registerUiTestAutomationService";
 
@@ -253,6 +256,27 @@
         }
     }
 
+    @VisibleForTesting
+    AccessibilityManagerService(
+            Context context,
+            PackageManager packageManager,
+            AccessibilitySecurityPolicy securityPolicy,
+            SystemActionPerformer systemActionPerformer,
+            AccessibilityWindowManager a11yWindowManager,
+            AccessibilityDisplayListener a11yDisplayListener) {
+        mContext = context;
+        mPowerManager =  (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+        mWindowManagerService = LocalServices.getService(WindowManagerInternal.class);
+        mMainHandler = new MainHandler(mContext.getMainLooper());
+        mActivityTaskManagerService = LocalServices.getService(ActivityTaskManagerInternal.class);
+        mPackageManager = packageManager;
+        mSecurityPolicy = securityPolicy;
+        mSystemActionPerformer = systemActionPerformer;
+        mA11yWindowManager = a11yWindowManager;
+        mA11yDisplayListener = a11yDisplayListener;
+        init();
+    }
+
     /**
      * Creates a new instance.
      *
@@ -260,21 +284,24 @@
      */
     public AccessibilityManagerService(Context context) {
         mContext = context;
-        mPackageManager = mContext.getPackageManager();
-        mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+        mPowerManager =  (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
         mWindowManagerService = LocalServices.getService(WindowManagerInternal.class);
-        mSecurityPolicy = new AccessibilitySecurityPolicy(mContext, this);
         mMainHandler = new MainHandler(mContext.getMainLooper());
+        mActivityTaskManagerService = LocalServices.getService(ActivityTaskManagerInternal.class);
+        mPackageManager = mContext.getPackageManager();
+        mSecurityPolicy = new AccessibilitySecurityPolicy(mContext, this);
         mSystemActionPerformer = new SystemActionPerformer(mContext, mWindowManagerService);
         mA11yWindowManager = new AccessibilityWindowManager(mLock, mMainHandler,
                 mWindowManagerService, this, mSecurityPolicy, this);
         mA11yDisplayListener = new AccessibilityDisplayListener(mContext, mMainHandler);
-        mSecurityPolicy.setAccessibilityWindowManager(mA11yWindowManager);
-        mActivityTaskManagerService = LocalServices.getService(ActivityTaskManagerInternal.class);
+        init();
+    }
 
+    private void init() {
+        mSecurityPolicy.setAccessibilityWindowManager(mA11yWindowManager);
         registerBroadcastReceivers();
         new AccessibilityContentObserver(mMainHandler).register(
-                context.getContentResolver());
+                mContext.getContentResolver());
     }
 
     @Override
@@ -623,6 +650,30 @@
         event.recycle();
     }
 
+    /**
+     * This is the implementation of AccessibilityManager system API.
+     * System UI calls into this method through AccessibilityManager system API to register a
+     * system action.
+     */
+    @Override
+    public void registerSystemAction(RemoteAction action, int actionId) {
+        mSecurityPolicy.enforceCallingPermission(Manifest.permission.MANAGE_ACCESSIBILITY,
+                FUNCTION_REGISTER_SYSTEM_ACTION);
+        mSystemActionPerformer.registerSystemAction(actionId, action);
+    }
+
+    /**
+     * This is the implementation of AccessibilityManager system API.
+     * System UI calls into this method through AccessibilityManager system API to unregister a
+     * system action.
+     */
+    @Override
+    public void unregisterSystemAction(int actionId) {
+        mSecurityPolicy.enforceCallingPermission(Manifest.permission.MANAGE_ACCESSIBILITY,
+                FUNCTION_UNREGISTER_SYSTEM_ACTION);
+        mSystemActionPerformer.unregisterSystemAction(actionId);
+    }
+
     @Override
     public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(int userId) {
         synchronized (mLock) {
diff --git a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
index 1754926..11dcfef 100644
--- a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
+++ b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
@@ -157,8 +157,13 @@
     /**
      * This method is called to register a system action. If a system action is already registered
      * with the given id, the existing system action will be overwritten.
+     *
+     * This method is supposed to be package internal since this class is meant to be used by
+     * AccessibilityManagerService only. But Mockito has a bug which requiring this to be public
+     * to be mocked.
      */
-    void registerSystemAction(int id, RemoteAction action) {
+    @VisibleForTesting
+    public void registerSystemAction(int id, RemoteAction action) {
         synchronized (mSystemActionLock) {
             mRegisteredSystemActions.put(id, action);
         }
@@ -170,8 +175,13 @@
     /**
      * This method is called to unregister a system action previously registered through
      * registerSystemAction.
+     *
+     * This method is supposed to be package internal since this class is meant to be used by
+     * AccessibilityManagerService only. But Mockito has a bug which requiring this to be public
+     * to be mocked.
      */
-    void unregisterSystemAction(int id) {
+    @VisibleForTesting
+    public void unregisterSystemAction(int id) {
         synchronized (mSystemActionLock) {
             mRegisteredSystemActions.remove(id);
         }
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 95cd8fc..f34b5e7 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -112,7 +112,6 @@
 import com.android.server.inputmethod.InputMethodManagerInternal;
 
 import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -311,20 +310,9 @@
     @NonNull
     private final InputMethodManagerInternal mInputMethodManagerInternal;
 
-    @GuardedBy("mLock")
-    @Nullable
-    private CompletableFuture<InlineSuggestionsRequest> mSuggestionsRequestFuture;
-
-    @GuardedBy("mLock")
-    @Nullable
-    private CompletableFuture<IInlineSuggestionsResponseCallback>
-            mInlineSuggestionsResponseCallbackFuture;
-
     @Nullable
     private InlineSuggestionsRequestCallbackImpl mInlineSuggestionsRequestCallback;
 
-    private static final int INLINE_REQUEST_TIMEOUT_MS = 1000;
-
     /**
      * Receiver of assist data from the app's {@link Activity}.
      */
@@ -336,7 +324,9 @@
                         + "mForAugmentedAutofillOnly: %s", mForAugmentedAutofillOnly);
                 return;
             }
-            if (mCurrentViewId == null) {
+            // Keeps to prevent it is cleared on multiple threads.
+            final AutofillId currentViewId = mCurrentViewId;
+            if (currentViewId == null) {
                 Slog.w(TAG, "No current view id - session might have finished");
                 return;
             }
@@ -410,7 +400,7 @@
                 if (mContexts == null) {
                     mContexts = new ArrayList<>(1);
                 }
-                mContexts.add(new FillContext(requestId, structure, mCurrentViewId));
+                mContexts.add(new FillContext(requestId, structure, currentViewId));
 
                 cancelCurrentRequestLocked();
 
@@ -422,7 +412,9 @@
                 final ArrayList<FillContext> contexts =
                         mergePreviousSessionLocked(/* forSave= */ false);
 
-                final InlineSuggestionsRequest suggestionsRequest = getInlineSuggestionsRequest();
+                final InlineSuggestionsRequest suggestionsRequest =
+                        mInlineSuggestionsRequestCallback != null
+                                ? mInlineSuggestionsRequestCallback.getRequest() : null;
 
                 request = new FillRequest(requestId, contexts, mClientState, flags,
                         suggestionsRequest);
@@ -620,13 +612,7 @@
     private void maybeRequestInlineSuggestionsRequestThenFillLocked(@NonNull ViewState viewState,
             int newState, int flags) {
         if (isInlineSuggestionsEnabled()) {
-            mSuggestionsRequestFuture = new CompletableFuture<>();
-            mInlineSuggestionsResponseCallbackFuture = new CompletableFuture<>();
-
-            if (mInlineSuggestionsRequestCallback == null) {
-                mInlineSuggestionsRequestCallback = new InlineSuggestionsRequestCallbackImpl(this);
-            }
-
+            mInlineSuggestionsRequestCallback = new InlineSuggestionsRequestCallbackImpl();
             mInputMethodManagerInternal.onCreateInlineSuggestionsRequest(
                     mComponentName, mCurrentViewId, mInlineSuggestionsRequestCallback);
         }
@@ -636,10 +622,14 @@
 
     private static final class InlineSuggestionsRequestCallbackImpl
             extends IInlineSuggestionsRequestCallback.Stub {
-        private final WeakReference<Session> mSession;
+        private static final int INLINE_REQUEST_TIMEOUT_MS = 1000;
 
-        private InlineSuggestionsRequestCallbackImpl(Session session) {
-            mSession = new WeakReference<>(session);
+        private final CompletableFuture<InlineSuggestionsRequest> mRequest;
+        private final CompletableFuture<IInlineSuggestionsResponseCallback> mResponseCallback;
+
+        private InlineSuggestionsRequestCallbackImpl() {
+            mRequest = new CompletableFuture<>();
+            mResponseCallback = new CompletableFuture<>();
         }
 
         @Override
@@ -648,13 +638,8 @@
                 Log.d(TAG, "inline suggestions request unsupported, "
                         + "falling back to regular autofill");
             }
-            final Session session = mSession.get();
-            if (session != null) {
-                synchronized (session.mLock) {
-                    session.mSuggestionsRequestFuture.cancel(true);
-                    session.mInlineSuggestionsResponseCallbackFuture.cancel(true);
-                }
-            }
+            mRequest.cancel(true);
+            mResponseCallback.cancel(true);
         }
 
         @Override
@@ -663,13 +648,36 @@
             if (sDebug) {
                 Log.d(TAG, "onInlineSuggestionsRequest() received: " + request);
             }
-            final Session session = mSession.get();
-            if (session != null) {
-                synchronized (session.mLock) {
-                    session.mSuggestionsRequestFuture.complete(request);
-                    session.mInlineSuggestionsResponseCallbackFuture.complete(callback);
-                }
+            mRequest.complete(request);
+            mResponseCallback.complete(callback);
+        }
+
+        @Nullable
+        private InlineSuggestionsRequest getRequest() {
+            try {
+                return mRequest.get(INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+            } catch (TimeoutException e) {
+                Log.w(TAG, "Exception getting inline suggestions request in time: " + e);
+            } catch (CancellationException e) {
+                Log.w(TAG, "Inline suggestions request cancelled");
+            } catch (InterruptedException | ExecutionException e) {
+                throw new RuntimeException(e);
             }
+            return null;
+        }
+
+        @Nullable
+        private IInlineSuggestionsResponseCallback getResponseCallback() {
+            try {
+                return mResponseCallback.get(INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+            } catch (TimeoutException e) {
+                Log.w(TAG, "Exception getting inline suggestions callback in time: " + e);
+            } catch (CancellationException e) {
+                Log.w(TAG, "Inline suggestions callback cancelled");
+            } catch (InterruptedException | ExecutionException e) {
+                throw new RuntimeException(e);
+            }
+            return null;
         }
     }
 
@@ -2678,22 +2686,9 @@
      * Returns whether we made a request to show inline suggestions.
      */
     private boolean requestShowInlineSuggestions(FillResponse response) {
-        IInlineSuggestionsResponseCallback inlineContentCallback = null;
-        synchronized (mLock) {
-            if (mInlineSuggestionsResponseCallbackFuture != null) {
-                try {
-                    inlineContentCallback = mInlineSuggestionsResponseCallbackFuture.get(
-                            INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-                } catch (TimeoutException e) {
-                    Log.w(TAG, "Exception getting inline suggestions callback in time: " + e);
-                } catch (CancellationException e) {
-                    Log.w(TAG, "Inline suggestions callback cancelled");
-                } catch (InterruptedException | ExecutionException e) {
-                    throw new RuntimeException(e);
-                }
-            }
-        }
-
+        final IInlineSuggestionsResponseCallback inlineContentCallback =
+                mInlineSuggestionsRequestCallback != null
+                        ? mInlineSuggestionsRequestCallback.getResponseCallback() : null;
         if (inlineContentCallback == null) {
             Log.w(TAG, "Session input method callback is not set yet");
             return false;
@@ -3015,10 +3010,12 @@
 
         final AutofillId focusedId = AutofillId.withoutSession(mCurrentViewId);
 
-        final InlineSuggestionsRequest inlineSuggestionsRequest = getInlineSuggestionsRequest();
+        final InlineSuggestionsRequest inlineSuggestionsRequest =
+                mInlineSuggestionsRequestCallback != null
+                        ? mInlineSuggestionsRequestCallback.getRequest() : null;
         final IInlineSuggestionsResponseCallback inlineSuggestionsResponseCallback =
-                getInlineSuggestionsResponseCallback();
-
+                mInlineSuggestionsRequestCallback != null
+                        ? mInlineSuggestionsRequestCallback.getResponseCallback() : null;
         remoteService.onRequestAutofillLocked(id, mClient, taskId, mComponentName, focusedId,
                 currentValue, inlineSuggestionsRequest, inlineSuggestionsResponseCallback);
 
@@ -3028,40 +3025,6 @@
         return mAugmentedAutofillDestroyer;
     }
 
-    @Nullable
-    private InlineSuggestionsRequest getInlineSuggestionsRequest() {
-        if (mSuggestionsRequestFuture != null) {
-            try {
-                return mSuggestionsRequestFuture.get(
-                        INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-            } catch (TimeoutException e) {
-                Log.w(TAG, "Exception getting inline suggestions request in time: " + e);
-            } catch (CancellationException e) {
-                Log.w(TAG, "Inline suggestions request cancelled");
-            } catch (InterruptedException | ExecutionException e) {
-                throw new RuntimeException(e);
-            }
-        }
-        return null;
-    }
-
-    @Nullable
-    private IInlineSuggestionsResponseCallback getInlineSuggestionsResponseCallback() {
-        if (mInlineSuggestionsResponseCallbackFuture != null) {
-            try {
-                return mInlineSuggestionsResponseCallbackFuture.get(
-                        INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-            } catch (TimeoutException e) {
-                Log.w(TAG, "Exception getting inline suggestions callback in time: " + e);
-            } catch (CancellationException e) {
-                Log.w(TAG, "Inline suggestions callback cancelled");
-            } catch (InterruptedException | ExecutionException e) {
-                throw new RuntimeException(e);
-            }
-        }
-        return null;
-    }
-
     @GuardedBy("mLock")
     private void cancelAugmentedAutofillLocked() {
         final RemoteAugmentedAutofillService remoteService = mService
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 3bce322..d45a54e 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -1452,6 +1452,11 @@
         if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) {
             return;
         }
+        dumpWithoutCheckingPermission(fd, pw, args);
+    }
+
+    @VisibleForTesting
+    void dumpWithoutCheckingPermission(FileDescriptor fd, PrintWriter pw, String[] args) {
         int userId = binderGetCallingUserId();
         if (!isUserReadyForBackup(userId)) {
             pw.println("Inactive");
@@ -1460,7 +1465,16 @@
 
         if (args != null) {
             for (String arg : args) {
-                if ("users".equals(arg.toLowerCase())) {
+                if ("-h".equals(arg)) {
+                    pw.println("'dumpsys backup' optional arguments:");
+                    pw.println("  -h       : this help text");
+                    pw.println("  a[gents] : dump information about defined backup agents");
+                    pw.println("  transportclients : dump information about transport clients");
+                    pw.println("  transportstats : dump transport statts");
+                    pw.println("  users    : dump the list of users for which backup service "
+                            + "is running");
+                    return;
+                } else if ("users".equals(arg.toLowerCase())) {
                     pw.print(DUMP_RUNNING_USERS_MESSAGE);
                     for (int i = 0; i < mUserServices.size(); i++) {
                         pw.print(" " + mUserServices.keyAt(i));
@@ -1471,11 +1485,12 @@
             }
         }
 
-        UserBackupManagerService userBackupManagerService =
-                getServiceForUserIfCallerHasPermission(UserHandle.USER_SYSTEM, "dump()");
-
-        if (userBackupManagerService != null) {
-            userBackupManagerService.dump(fd, pw, args);
+        for (int i = 0; i < mUserServices.size(); i++) {
+            UserBackupManagerService userBackupManagerService =
+                    getServiceForUserIfCallerHasPermission(mUserServices.keyAt(i), "dump()");
+            if (userBackupManagerService != null) {
+                userBackupManagerService.dump(fd, pw, args);
+            }
         }
     }
 
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 064cd06..7b95ab5 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -3545,14 +3545,7 @@
         try {
             if (args != null) {
                 for (String arg : args) {
-                    if ("-h".equals(arg)) {
-                        pw.println("'dumpsys backup' optional arguments:");
-                        pw.println("  -h       : this help text");
-                        pw.println("  a[gents] : dump information about defined backup agents");
-                        pw.println("  users    : dump the list of users for which backup service "
-                                + "is running");
-                        return;
-                    } else if ("agents".startsWith(arg)) {
+                    if ("agents".startsWith(arg)) {
                         dumpAgents(pw);
                         return;
                     } else if ("transportclients".equals(arg.toLowerCase())) {
@@ -3583,8 +3576,10 @@
     }
 
     private void dumpInternal(PrintWriter pw) {
+        // Add prefix for only non-system users so that system user dumpsys is the same as before
+        String userPrefix = mUserId == UserHandle.USER_SYSTEM ? "" : "User " + mUserId + ":";
         synchronized (mQueueLock) {
-            pw.println("Backup Manager is " + (mEnabled ? "enabled" : "disabled")
+            pw.println(userPrefix + "Backup Manager is " + (mEnabled ? "enabled" : "disabled")
                     + " / " + (!mSetupComplete ? "not " : "") + "setup complete / "
                     + (this.mPendingInits.size() == 0 ? "not " : "") + "pending init");
             pw.println("Auto-restore is " + (mAutoRestore ? "enabled" : "disabled"));
@@ -3594,13 +3589,13 @@
                     + " (now = " + System.currentTimeMillis() + ')');
             pw.println("  next scheduled: " + KeyValueBackupJob.nextScheduled(mUserId));
 
-            pw.println("Transport whitelist:");
+            pw.println(userPrefix + "Transport whitelist:");
             for (ComponentName transport : mTransportManager.getTransportWhitelist()) {
                 pw.print("    ");
                 pw.println(transport.flattenToShortString());
             }
 
-            pw.println("Available transports:");
+            pw.println(userPrefix + "Available transports:");
             final String[] transports = listAllTransports();
             if (transports != null) {
                 for (String t : transports) {
@@ -3626,18 +3621,18 @@
 
             mTransportManager.dumpTransportClients(pw);
 
-            pw.println("Pending init: " + mPendingInits.size());
+            pw.println(userPrefix + "Pending init: " + mPendingInits.size());
             for (String s : mPendingInits) {
                 pw.println("    " + s);
             }
 
-            pw.print("Ancestral: ");
+            pw.print(userPrefix + "Ancestral: ");
             pw.println(Long.toHexString(mAncestralToken));
-            pw.print("Current:   ");
+            pw.print(userPrefix + "Current:   ");
             pw.println(Long.toHexString(mCurrentToken));
 
             int numPackages = mBackupParticipants.size();
-            pw.println("Participants:");
+            pw.println(userPrefix + "Participants:");
             for (int i = 0; i < numPackages; i++) {
                 int uid = mBackupParticipants.keyAt(i);
                 pw.print("  uid: ");
@@ -3648,7 +3643,7 @@
                 }
             }
 
-            pw.println("Ancestral packages: "
+            pw.println(userPrefix + "Ancestral packages: "
                     + (mAncestralPackages == null ? "none" : mAncestralPackages.size()));
             if (mAncestralPackages != null) {
                 for (String pkg : mAncestralPackages) {
@@ -3657,17 +3652,17 @@
             }
 
             Set<String> processedPackages = mProcessedPackagesJournal.getPackagesCopy();
-            pw.println("Ever backed up: " + processedPackages.size());
+            pw.println(userPrefix + "Ever backed up: " + processedPackages.size());
             for (String pkg : processedPackages) {
                 pw.println("    " + pkg);
             }
 
-            pw.println("Pending key/value backup: " + mPendingBackups.size());
+            pw.println(userPrefix + "Pending key/value backup: " + mPendingBackups.size());
             for (BackupRequest req : mPendingBackups.values()) {
                 pw.println("    " + req);
             }
 
-            pw.println("Full backup queue:" + mFullBackupQueue.size());
+            pw.println(userPrefix + "Full backup queue:" + mFullBackupQueue.size());
             for (FullBackupEntry entry : mFullBackupQueue) {
                 pw.print("    ");
                 pw.print(entry.lastBackup);
diff --git a/services/core/Android.bp b/services/core/Android.bp
index a1f57cb..b2fba73 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -165,9 +165,3 @@
     name: "protolog.conf.json.gz",
     src: ":services.core.json.gz",
 }
-
-platform_compat_config {
-    name: "services-core-platform-compat-config",
-    src: ":services.core.unboosted",
-}
-
diff --git a/services/core/java/com/android/server/GnssManagerService.java b/services/core/java/com/android/server/GnssManagerService.java
index bbcfdc6..32cdc41 100644
--- a/services/core/java/com/android/server/GnssManagerService.java
+++ b/services/core/java/com/android/server/GnssManagerService.java
@@ -47,7 +47,6 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.LocationManagerServiceUtils.LinkedListener;
 import com.android.server.LocationManagerServiceUtils.LinkedListenerBase;
-import com.android.server.location.AbstractLocationProvider;
 import com.android.server.location.CallerIdentity;
 import com.android.server.location.GnssBatchingProvider;
 import com.android.server.location.GnssCapabilitiesProvider;
@@ -116,11 +115,9 @@
     private final Handler mHandler;
 
     public GnssManagerService(LocationManagerService locationManagerService,
-            Context context,
-            AbstractLocationProvider.LocationProviderManager gnssProviderManager,
-            LocationUsageLogger locationUsageLogger) {
-        this(locationManagerService, context, new GnssLocationProvider(context, gnssProviderManager,
-                FgThread.getHandler().getLooper()), locationUsageLogger);
+            Context context, LocationUsageLogger locationUsageLogger) {
+        this(locationManagerService, context,
+                new GnssLocationProvider(context, FgThread.getHandler()), locationUsageLogger);
     }
 
     // Can use this constructor to inject GnssLocationProvider for testing
diff --git a/services/core/java/com/android/server/GraphicsStatsService.java b/services/core/java/com/android/server/GraphicsStatsService.java
index 70569db..5179fa7 100644
--- a/services/core/java/com/android/server/GraphicsStatsService.java
+++ b/services/core/java/com/android/server/GraphicsStatsService.java
@@ -38,11 +38,13 @@
 import android.view.IGraphicsStatsCallback;
 
 import com.android.internal.util.DumpUtils;
+import com.android.internal.util.FastPrintWriter;
 
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Calendar;
@@ -78,6 +80,8 @@
     private static final int SAVE_BUFFER = 1;
     private static final int DELETE_OLD = 2;
 
+    private static final int AID_STATSD = 1066; // Statsd uid is set to 1066 forever.
+
     // This isn't static because we need this to happen after registerNativeMethods, however
     // the class is loaded (and thus static ctor happens) before that occurs.
     private final int ASHMEM_SIZE = nGetAshmemSize();
@@ -121,6 +125,7 @@
                 return true;
             }
         });
+        nativeInit();
     }
 
     /**
@@ -186,6 +191,86 @@
         return pfd;
     }
 
+    // If lastFullDay is true, pullGraphicsStats returns stats for the last complete day/24h period
+    // that does not include today. If lastFullDay is false, pullGraphicsStats returns stats for the
+    // current day.
+    // This method is invoked from native code only.
+    @SuppressWarnings({"UnusedDeclaration"})
+    private long pullGraphicsStats(boolean lastFullDay) throws RemoteException {
+        int uid = Binder.getCallingUid();
+
+        // DUMP and PACKAGE_USAGE_STATS permissions are required to invoke this method.
+        // TODO: remove exception for statsd daemon after required permissions are granted. statsd
+        // TODO: should have these permissions granted by data/etc/platform.xml, but it does not.
+        if (uid != AID_STATSD) {
+            StringWriter sw = new StringWriter();
+            PrintWriter pw = new FastPrintWriter(sw);
+            if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) {
+                pw.flush();
+                throw new RemoteException(sw.toString());
+            }
+        }
+
+        long callingIdentity = Binder.clearCallingIdentity();
+        try {
+            return pullGraphicsStatsImpl(lastFullDay);
+        } finally {
+            Binder.restoreCallingIdentity(callingIdentity);
+        }
+    }
+
+    private long pullGraphicsStatsImpl(boolean lastFullDay) {
+        long targetDay;
+        if (lastFullDay) {
+            // Get stats from yesterday. Stats stay constant, because the day is over.
+            targetDay = normalizeDate(System.currentTimeMillis() - 86400000).getTimeInMillis();
+        } else {
+            // Get stats from today. Stats may change as more apps are run today.
+            targetDay = normalizeDate(System.currentTimeMillis()).getTimeInMillis();
+        }
+
+        // Find active buffers for targetDay.
+        ArrayList<HistoricalBuffer> buffers;
+        synchronized (mLock) {
+            buffers = new ArrayList<>(mActive.size());
+            for (int i = 0; i < mActive.size(); i++) {
+                ActiveBuffer buffer = mActive.get(i);
+                if (buffer.mInfo.startTime == targetDay) {
+                    try {
+                        buffers.add(new HistoricalBuffer(buffer));
+                    } catch (IOException ex) {
+                        // Ignore
+                    }
+                }
+            }
+        }
+
+        // Dump active and historic buffers for targetDay in a serialized
+        // GraphicsStatsServiceDumpProto proto.
+        long dump = nCreateDump(-1, true);
+        try {
+            synchronized (mFileAccessLock) {
+                HashSet<File> skipList = dumpActiveLocked(dump, buffers);
+                buffers.clear();
+                String subPath = String.format("%d", targetDay);
+                File dateDir = new File(mGraphicsStatsDir, subPath);
+                if (dateDir.exists()) {
+                    for (File pkg : dateDir.listFiles()) {
+                        for (File version : pkg.listFiles()) {
+                            File data = new File(version, "total");
+                            if (skipList.contains(data)) {
+                                continue;
+                            }
+                            nAddToDump(dump, data.getAbsolutePath());
+                        }
+                    }
+                }
+            }
+        } finally {
+            return nFinishDumpInMemory(dump);
+        }
+    }
+
     private ParcelFileDescriptor getPfd(MemoryFile file) {
         try {
             if (!file.getFileDescriptor().valid()) {
@@ -379,12 +464,21 @@
         }
     }
 
+    @Override
+    protected void finalize() throws Throwable {
+        nativeDestructor();
+    }
+
+    private native void nativeInit();
+    private static native void nativeDestructor();
+
     private static native int nGetAshmemSize();
     private static native long nCreateDump(int outFd, boolean isProto);
     private static native void nAddToDump(long dump, String path, String packageName,
             long versionCode, long startTime, long endTime, byte[] data);
     private static native void nAddToDump(long dump, String path);
     private static native void nFinishDump(long dump);
+    private static native long nFinishDumpInMemory(long dump);
     private static native void nSaveBuffer(String path, String packageName, long versionCode,
             long startTime, long endTime, byte[] data);
 
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 0fc5340..32128d5 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -23,8 +23,6 @@
 import static android.location.LocationManager.PASSIVE_PROVIDER;
 import static android.os.PowerManager.locationPowerSaveModeToString;
 
-import static com.android.internal.util.Preconditions.checkState;
-
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -74,7 +72,6 @@
 import android.os.UserManager;
 import android.os.WorkSource;
 import android.os.WorkSource.WorkChain;
-import android.provider.Settings;
 import android.stats.location.LocationStatsEnums;
 import android.text.TextUtils;
 import android.util.EventLog;
@@ -92,6 +89,7 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.server.location.AbstractLocationProvider;
+import com.android.server.location.AbstractLocationProvider.State;
 import com.android.server.location.ActivityRecognitionProxy;
 import com.android.server.location.CallerIdentity;
 import com.android.server.location.GeocoderProxy;
@@ -105,6 +103,7 @@
 import com.android.server.location.LocationSettingsStore;
 import com.android.server.location.LocationUsageLogger;
 import com.android.server.location.MockProvider;
+import com.android.server.location.MockableLocationProvider;
 import com.android.server.location.PassiveProvider;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
 
@@ -121,6 +120,8 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -196,6 +197,8 @@
     private final LocationSettingsStore mSettingsStore;
     private final LocationUsageLogger mLocationUsageLogger;
 
+    private final PassiveLocationProviderManager mPassiveManager;
+
     private AppOpsManager mAppOps;
     private PackageManager mPackageManager;
     private PowerManager mPowerManager;
@@ -205,21 +208,17 @@
     private GeofenceManager mGeofenceManager;
     private LocationFudger mLocationFudger;
     private GeocoderProxy mGeocodeProvider;
-    @Nullable
-    private GnssManagerService mGnssManagerService;
-    private PassiveProvider mPassiveProvider; // track passive provider for special cases
+    @Nullable private GnssManagerService mGnssManagerService;
+
     @GuardedBy("mLock")
     private String mExtraLocationControllerPackage;
+    @GuardedBy("mLock")
     private boolean mExtraLocationControllerPackageEnabled;
 
-    // list of currently active providers
-    @GuardedBy("mLock")
-    private final ArrayList<LocationProviderManager> mProviders = new ArrayList<>();
-
-    // list of non-mock providers, so that when mock providers replace real providers, they can be
-    // later re-replaced
-    @GuardedBy("mLock")
-    private final ArrayList<LocationProviderManager> mRealProviders = new ArrayList<>();
+    // @GuardedBy("mLock")
+    // hold lock for write or to prevent write, no lock for read
+    private final CopyOnWriteArrayList<LocationProviderManager> mProviderManagers =
+            new CopyOnWriteArrayList<>();
 
     @GuardedBy("mLock")
     private final HashMap<Object, Receiver> mReceivers = new HashMap<>();
@@ -238,9 +237,9 @@
     private final HashMap<String, Location> mLastLocationCoarseInterval =
             new HashMap<>();
 
-    // current active user on the device - other users are denied location data
-    private int mCurrentUserId = UserHandle.USER_SYSTEM;
-    private int[] mCurrentUserProfiles = new int[]{UserHandle.USER_SYSTEM};
+    // current active user on the device
+    private int mCurrentUserId;
+    private int[] mCurrentUserProfiles;
 
     @GuardedBy("mLock")
     @PowerManager.LocationPowerSaveMode
@@ -252,6 +251,17 @@
         mSettingsStore = new LocationSettingsStore(mContext, mHandler);
         mLocationUsageLogger = new LocationUsageLogger();
 
+        mCurrentUserId = UserHandle.USER_NULL;
+        mCurrentUserProfiles = new int[]{UserHandle.USER_NULL};
+
+        // set up passive provider -  we do this early because it has no dependencies on system
+        // services or external code that isn't ready yet, and because this allows the variable to
+        // be final. other more complex providers are initialized later, when system services are
+        // ready
+        mPassiveManager = new PassiveLocationProviderManager();
+        mProviderManagers.add(mPassiveManager);
+        mPassiveManager.setRealProvider(new PassiveProvider(mContext));
+
         // Let the package manager query which are the default location
         // providers as they get certain permissions granted by default.
         PermissionManagerServiceInternal permissionManagerInternal = LocalServices.getService(
@@ -415,15 +425,15 @@
         for (Receiver receiver : mReceivers.values()) {
             receiver.updateMonitoring(true);
         }
-        for (LocationProviderManager p : mProviders) {
-            applyRequirementsLocked(p);
+        for (LocationProviderManager manager : mProviderManagers) {
+            applyRequirementsLocked(manager);
         }
     }
 
     @GuardedBy("mLock")
     private void onPermissionsChangedLocked() {
-        for (LocationProviderManager p : mProviders) {
-            applyRequirementsLocked(p);
+        for (LocationProviderManager manager : mProviderManagers) {
+            applyRequirementsLocked(manager);
         }
     }
 
@@ -442,16 +452,16 @@
 
         mBatterySaverMode = newLocationMode;
 
-        for (LocationProviderManager p : mProviders) {
-            applyRequirementsLocked(p);
+        for (LocationProviderManager manager : mProviderManagers) {
+            applyRequirementsLocked(manager);
         }
     }
 
     @GuardedBy("mLock")
     private void onScreenStateChangedLocked() {
         if (mBatterySaverMode == PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF) {
-            for (LocationProviderManager p : mProviders) {
-                applyRequirementsLocked(p);
+            for (LocationProviderManager manager : mProviderManagers) {
+                applyRequirementsLocked(manager);
             }
         }
     }
@@ -466,8 +476,8 @@
         intent.putExtra(LocationManager.EXTRA_LOCATION_ENABLED, isLocationEnabledForUser(userId));
         mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
 
-        for (LocationProviderManager p : mProviders) {
-            p.onUseableChangedLocked(userId);
+        for (LocationProviderManager manager : mProviderManagers) {
+            manager.onUseableChangedLocked(userId);
         }
     }
 
@@ -521,22 +531,22 @@
 
     @GuardedBy("mLock")
     private void onBackgroundThrottleIntervalChangedLocked() {
-        for (LocationProviderManager provider : mProviders) {
-            applyRequirementsLocked(provider);
+        for (LocationProviderManager manager : mProviderManagers) {
+            applyRequirementsLocked(manager);
         }
     }
 
     @GuardedBy("mLock")
     private void onBackgroundThrottleWhitelistChangedLocked() {
-        for (LocationProviderManager p : mProviders) {
-            applyRequirementsLocked(p);
+        for (LocationProviderManager manager : mProviderManagers) {
+            applyRequirementsLocked(manager);
         }
     }
 
     @GuardedBy("lock")
     private void onIgnoreSettingsWhitelistChangedLocked() {
-        for (LocationProviderManager p : mProviders) {
-            applyRequirementsLocked(p);
+        for (LocationProviderManager manager : mProviderManagers) {
+            applyRequirementsLocked(manager);
         }
     }
 
@@ -623,22 +633,11 @@
 
     @GuardedBy("mLock")
     private void initializeProvidersLocked() {
-        // create a passive location provider, which is always enabled
-        LocationProviderManager passiveProviderManager = new LocationProviderManager(
-                PASSIVE_PROVIDER);
-        addProviderLocked(passiveProviderManager);
-        mPassiveProvider = new PassiveProvider(mContext, passiveProviderManager);
-        passiveProviderManager.attachLocked(mPassiveProvider);
-
         if (GnssManagerService.isGnssSupported()) {
-            // Create a gps location provider manager
-            LocationProviderManager gnssProviderManager = new LocationProviderManager(GPS_PROVIDER);
-            mRealProviders.add(gnssProviderManager);
-            addProviderLocked(gnssProviderManager);
-
-            mGnssManagerService = new GnssManagerService(this, mContext, gnssProviderManager,
-                    mLocationUsageLogger);
-            gnssProviderManager.attachLocked(mGnssManagerService.getGnssLocationProvider());
+            mGnssManagerService = new GnssManagerService(this, mContext, mLocationUsageLogger);
+            LocationProviderManager gnssManager = new LocationProviderManager(GPS_PROVIDER);
+            mProviderManagers.add(gnssManager);
+            gnssManager.setRealProvider(mGnssManagerService.getGnssLocationProvider());
         }
 
         /*
@@ -662,37 +661,31 @@
 
         ensureFallbackFusedProviderPresentLocked(pkgs);
 
-        // bind to network provider
-        LocationProviderManager networkProviderManager = new LocationProviderManager(
-                NETWORK_PROVIDER);
         LocationProviderProxy networkProvider = LocationProviderProxy.createAndBind(
                 mContext,
-                networkProviderManager,
                 NETWORK_LOCATION_SERVICE_ACTION,
                 com.android.internal.R.bool.config_enableNetworkLocationOverlay,
                 com.android.internal.R.string.config_networkLocationProviderPackageName,
                 com.android.internal.R.array.config_locationProviderPackageNames);
         if (networkProvider != null) {
-            mRealProviders.add(networkProviderManager);
-            addProviderLocked(networkProviderManager);
-            networkProviderManager.attachLocked(networkProvider);
+            LocationProviderManager networkManager = new LocationProviderManager(NETWORK_PROVIDER);
+            mProviderManagers.add(networkManager);
+            networkManager.setRealProvider(networkProvider);
         } else {
             Slog.w(TAG, "no network location provider found");
         }
 
         // bind to fused provider
-        LocationProviderManager fusedProviderManager = new LocationProviderManager(FUSED_PROVIDER);
         LocationProviderProxy fusedProvider = LocationProviderProxy.createAndBind(
                 mContext,
-                fusedProviderManager,
                 FUSED_LOCATION_SERVICE_ACTION,
                 com.android.internal.R.bool.config_enableFusedLocationOverlay,
                 com.android.internal.R.string.config_fusedLocationProviderPackageName,
                 com.android.internal.R.array.config_locationProviderPackageNames);
         if (fusedProvider != null) {
-            mRealProviders.add(fusedProviderManager);
-            addProviderLocked(fusedProviderManager);
-            fusedProviderManager.attachLocked(fusedProvider);
+            LocationProviderManager fusedManager = new LocationProviderManager(FUSED_PROVIDER);
+            mProviderManagers.add(fusedManager);
+            fusedManager.setRealProvider(fusedProvider);
         } else {
             Slog.e(TAG, "no fused location provider found",
                     new IllegalStateException("Location service needs a fused location provider"));
@@ -754,10 +747,7 @@
                     Boolean.parseBoolean(fragments[7]) /* supportsBearing */,
                     Integer.parseInt(fragments[8]) /* powerRequirement */,
                     Integer.parseInt(fragments[9]) /* accuracy */);
-            LocationProviderManager testProviderManager = new LocationProviderManager(name);
-            addProviderLocked(testProviderManager);
-            testProviderManager.attachLocked(
-                    new MockProvider(mContext, testProviderManager, properties));
+            addTestProvider(name, properties, mContext.getOpPackageName());
         }
     }
 
@@ -771,231 +761,202 @@
             Log.d(TAG, "foreground user is changing to " + userId);
         }
 
-        int oldUserId = userId;
+        int oldUserId = mCurrentUserId;
         mCurrentUserId = userId;
         onUserProfilesChangedLocked();
 
         // let providers know the current user has changed
-        for (LocationProviderManager p : mProviders) {
-            p.onUseableChangedLocked(oldUserId);
-            p.onUseableChangedLocked(mCurrentUserId);
+        for (LocationProviderManager manager : mProviderManagers) {
+            // update LOCATION_PROVIDERS_ALLOWED for best effort backwards compatibility
+            mSettingsStore.setLocationProviderAllowed(manager.getName(),
+                    manager.isUseable(mCurrentUserId), mCurrentUserId);
+
+            manager.onUseableChangedLocked(oldUserId);
+            manager.onUseableChangedLocked(mCurrentUserId);
         }
     }
 
     /**
      * Location provider manager, manages a LocationProvider.
      */
-    class LocationProviderManager implements AbstractLocationProvider.LocationProviderManager {
+    class LocationProviderManager implements MockableLocationProvider.Listener {
 
         private final String mName;
 
-        // remember to clear binder identity before invoking any provider operation
-        @GuardedBy("mLock")
-        @Nullable
-        protected AbstractLocationProvider mProvider;
+        // acquiring mLock makes operations on mProvider atomic, but is otherwise unnecessary
+        protected final MockableLocationProvider mProvider;
 
         @GuardedBy("mLock")
-        private SparseArray<Boolean> mUseable;  // combined state for each user id
-        @GuardedBy("mLock")
-        private boolean mEnabled;  // state of provider
-
-        @GuardedBy("mLock")
-        @Nullable
-        private ProviderProperties mProperties;
+        private final SparseArray<Boolean> mUseable;  // combined state for each user id
 
         private LocationProviderManager(String name) {
             mName = name;
-
-            mProvider = null;
             mUseable = new SparseArray<>(1);
-            mEnabled = false;
-            mProperties = null;
 
-            // update LOCATION_PROVIDERS_ALLOWED for best effort backwards compatibility
-            Settings.Secure.putStringForUser(
-                    mContext.getContentResolver(),
-                    Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
-                    "-" + mName,
-                    mCurrentUserId);
-        }
-
-        @GuardedBy("mLock")
-        public void attachLocked(AbstractLocationProvider provider) {
-            Objects.requireNonNull(provider);
-            checkState(mProvider == null);
-
-            if (D) {
-                Log.d(TAG, mName + " provider attached");
-            }
-
-            mProvider = provider;
-
-            // it would be more correct to call this for all users, but we know this can only
-            // affect the current user since providers are disabled for non-current users
-            onUseableChangedLocked(mCurrentUserId);
+            // initialize last since this lets our reference escape
+            mProvider = new MockableLocationProvider(mContext, mLock, this);
         }
 
         public String getName() {
             return mName;
         }
 
-        @GuardedBy("mLock")
-        public List<String> getPackagesLocked() {
-            if (mProvider == null) {
-                return Collections.emptyList();
-            } else {
-                // safe to not clear binder context since this doesn't call into the real provider
-                return mProvider.getProviderPackages();
-            }
+        public boolean hasProvider() {
+            return mProvider.getProvider() != null;
         }
 
-        public boolean isMock() {
-            return false;
+        public void setRealProvider(AbstractLocationProvider provider) {
+            mProvider.setRealProvider(provider);
         }
 
-        @GuardedBy("mLock")
-        public boolean isPassiveLocked() {
-            return mProvider == mPassiveProvider;
+        public void setMockProvider(@Nullable MockProvider provider) {
+            mProvider.setMockProvider(provider);
         }
 
-        @GuardedBy("mLock")
+        public Set<String> getPackages() {
+            return mProvider.getState().providerPackageNames;
+        }
+
         @Nullable
-        public ProviderProperties getPropertiesLocked() {
-            return mProperties;
+        public ProviderProperties getProperties() {
+            return mProvider.getState().properties;
         }
 
-        public void setRequest(ProviderRequest request, WorkSource workSource) {
-            // move calls going to providers onto a different thread to avoid deadlock
-            mHandler.post(() -> {
-                synchronized (mLock) {
-                    if (mProvider != null) {
-                        mProvider.onSetRequest(request, workSource);
-                    }
+        public void setMockProviderEnabled(boolean enabled) {
+            synchronized (mLock) {
+                if (!mProvider.isMock()) {
+                    throw new IllegalArgumentException(mName + " provider is not a test provider");
                 }
-            });
+
+                mProvider.setMockProviderEnabled(enabled);
+            }
         }
 
-        public void sendExtraCommand(String command, Bundle extras) {
-            int uid = Binder.getCallingUid();
-            int pid = Binder.getCallingPid();
-
-            // move calls going to providers onto a different thread to avoid deadlock
-            mHandler.post(() -> {
-                synchronized (mLock) {
-                    if (mProvider != null) {
-                        mProvider.onSendExtraCommand(uid, pid, command, extras);
-                    }
+        public void setMockProviderLocation(Location location) {
+            synchronized (mLock) {
+                if (!mProvider.isMock()) {
+                    throw new IllegalArgumentException(mName + " provider is not a test provider");
                 }
-            });
+
+                String locationProvider = location.getProvider();
+                if (!TextUtils.isEmpty(locationProvider) && !mName.equals(locationProvider)) {
+                    // The location has an explicit provider that is different from the mock
+                    // provider name. The caller may be trying to fool us via b/33091107.
+                    EventLog.writeEvent(0x534e4554, "33091107", Binder.getCallingUid(),
+                            mName + "!=" + locationProvider);
+                }
+
+                mProvider.setMockProviderLocation(location);
+            }
         }
 
-        @GuardedBy("mLock")
-        public void dumpLocked(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
-            pw.print(mName + " provider");
-            if (isMock()) {
-                pw.print(" [mock]");
-            }
-            pw.println(":");
-
-            pw.increaseIndent();
-
-            pw.println("useable=" + isUseableLocked(mCurrentUserId));
-            if (!isUseableLocked(mCurrentUserId)) {
-                pw.println("attached=" + (mProvider != null));
-                pw.println("enabled=" + mEnabled);
-            }
-
-            pw.println("properties=" + mProperties);
-
-            if (mProvider != null) {
-                // in order to be consistent with other provider APIs, this should be run on the
-                // location thread... but this likely isn't worth it just for dumping info.
-                long identity = Binder.clearCallingIdentity();
-                try {
-                    mProvider.dump(fd, pw, args);
-                } finally {
-                    Binder.restoreCallingIdentity(identity);
+        public List<LocationRequest> getMockProviderRequests() {
+            synchronized (mLock) {
+                if (!mProvider.isMock()) {
+                    throw new IllegalArgumentException(mName + " provider is not a test provider");
                 }
+
+                return mProvider.getCurrentRequest().locationRequests;
             }
+        }
+
+        public void setRequest(ProviderRequest request) {
+            mProvider.setRequest(request);
+        }
+
+        public void sendExtraCommand(int uid, int pid, String command, Bundle extras) {
+            mProvider.sendExtraCommand(uid, pid, command, extras);
+        }
+
+        public void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
+            synchronized (mLock) {
+                pw.print(mName + " provider");
+                if (mProvider.isMock()) {
+                    pw.print(" [mock]");
+                }
+                pw.println(":");
+
+                pw.increaseIndent();
+
+                pw.println("useable=" + isUseable(mCurrentUserId));
+                if (!isUseable(mCurrentUserId)) {
+                    pw.println("enabled=" + mProvider.getState().enabled);
+                }
+
+                pw.println("properties=" + mProvider.getState().properties);
+            }
+
+            mProvider.dump(fd, pw, args);
 
             pw.decreaseIndent();
         }
 
+        @GuardedBy("mLock")
         @Override
         public void onReportLocation(Location location) {
-            // likelihood of a 0,0 bug is far greater than this being a valid location
-            if (!isMock() && location.getLatitude() == 0 && location.getLongitude() == 0) {
-                Slog.w(TAG, "blocking 0,0 location from " + mName + " provider");
-                return;
+            // don't validate mock locations
+            if (!location.isFromMockProvider()) {
+                if (location.getLatitude() == 0 && location.getLongitude() == 0) {
+                    Slog.w(TAG, "blocking 0,0 location from " + mName + " provider");
+                    return;
+                }
             }
 
-            synchronized (mLock) {
-                handleLocationChangedLocked(location, this);
-            }
+            handleLocationChangedLocked(location, this);
         }
 
+        @GuardedBy("mLock")
         @Override
         public void onReportLocation(List<Location> locations) {
             if (mGnssManagerService == null) {
                 return;
             }
-            synchronized (mLock) {
-                LocationProviderManager gpsProvider = getLocationProviderLocked(GPS_PROVIDER);
-                if (gpsProvider == null || !gpsProvider.isUseableLocked()) {
-                    Slog.w(TAG, "reportLocationBatch() called without user permission");
-                    return;
-                }
 
-                mGnssManagerService.onReportLocation(locations);
+            if (!GPS_PROVIDER.equals(mName) || !isUseable()) {
+                Slog.w(TAG, "reportLocationBatch() called without user permission");
+                return;
             }
+
+            mGnssManagerService.onReportLocation(locations);
         }
 
+        @GuardedBy("mLock")
         @Override
-        public void onSetEnabled(boolean enabled) {
-            synchronized (mLock) {
-                if (enabled == mEnabled) {
-                    return;
-                }
-
-                if (D) {
-                    Log.d(TAG, mName + " provider enabled is now " + mEnabled);
-                }
-
-                mEnabled = enabled;
-
-                // it would be more correct to call this for all users, but we know this can only
-                // affect the current user since providers are disabled for non-current users
+        public void onStateChanged(State oldState, State newState) {
+            if (oldState.enabled != newState.enabled) {
+                // it would be more correct to call this for all users, but we know this can
+                // only affect the current user since providers are disabled for non-current
+                // users
                 onUseableChangedLocked(mCurrentUserId);
             }
         }
 
-        @Override
-        public void onSetProperties(ProviderProperties properties) {
+        @GuardedBy("mLock")
+        public boolean isUseable() {
+            return isUseable(mCurrentUserId);
+        }
+
+        @GuardedBy("mLock")
+        public boolean isUseable(int userId) {
             synchronized (mLock) {
-                mProperties = properties;
+                return mUseable.get(userId, Boolean.FALSE);
             }
         }
 
         @GuardedBy("mLock")
-        public boolean isUseableLocked() {
-            return isUseableLocked(mCurrentUserId);
-        }
-
-        @GuardedBy("mLock")
-        public boolean isUseableLocked(int userId) {
-            return mUseable.get(userId, Boolean.FALSE);
-        }
-
-        @GuardedBy("mLock")
         public void onUseableChangedLocked(int userId) {
+            if (userId == UserHandle.USER_NULL) {
+                // only used during initialization - we don't care about the null user
+                return;
+            }
+
             // if any property that contributes to "useability" here changes state, it MUST result
             // in a direct or indrect call to onUseableChangedLocked. this allows the provider to
             // guarantee that it will always eventually reach the correct state.
-            boolean useable = mProvider != null && mProviders.contains(this)
-                    && isCurrentProfileLocked(userId) && isLocationEnabledForUser(userId)
-                    && mEnabled;
+            boolean useable = isCurrentProfileLocked(userId)
+                    && mSettingsStore.isLocationEnabled(userId) && mProvider.getState().enabled;
 
-            if (useable == isUseableLocked(userId)) {
+            if (useable == isUseable(userId)) {
                 return;
             }
             mUseable.put(userId, useable);
@@ -1007,11 +968,7 @@
             // fused and passive provider never get public updates for legacy reasons
             if (!FUSED_PROVIDER.equals(mName) && !PASSIVE_PROVIDER.equals(mName)) {
                 // update LOCATION_PROVIDERS_ALLOWED for best effort backwards compatibility
-                Settings.Secure.putStringForUser(
-                        mContext.getContentResolver(),
-                        Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
-                        (useable ? "+" : "-") + mName,
-                        userId);
+                mSettingsStore.setLocationProviderAllowed(mName, useable, userId);
 
                 Intent intent = new Intent(LocationManager.PROVIDERS_CHANGED_ACTION);
                 intent.putExtra(LocationManager.EXTRA_PROVIDER_NAME, mName);
@@ -1031,53 +988,38 @@
         }
     }
 
-    private class MockLocationProvider extends LocationProviderManager {
+    class PassiveLocationProviderManager extends LocationProviderManager {
 
-        private ProviderRequest mCurrentRequest;
-
-        private MockLocationProvider(String name) {
-            super(name);
+        private PassiveLocationProviderManager() {
+            super(PASSIVE_PROVIDER);
         }
 
         @Override
-        public void attachLocked(AbstractLocationProvider provider) {
-            checkState(provider instanceof MockProvider);
-            super.attachLocked(provider);
+        public void setRealProvider(AbstractLocationProvider provider) {
+            Preconditions.checkArgument(provider instanceof PassiveProvider);
+            super.setRealProvider(provider);
         }
 
-        public boolean isMock() {
-            return true;
+        @Override
+        public void setMockProvider(@Nullable MockProvider provider) {
+            if (provider != null) {
+                throw new IllegalArgumentException("Cannot mock the passive provider");
+            }
         }
 
-        @GuardedBy("mLock")
-        public void setEnabledLocked(boolean enabled) {
-            if (mProvider != null) {
+        public void updateLocation(Location location) {
+            synchronized (mLock) {
+                PassiveProvider passiveProvider = (PassiveProvider) mProvider.getProvider();
+                Preconditions.checkState(passiveProvider != null);
+
                 long identity = Binder.clearCallingIdentity();
                 try {
-                    ((MockProvider) mProvider).setEnabled(enabled);
+                    passiveProvider.updateLocation(location);
                 } finally {
                     Binder.restoreCallingIdentity(identity);
                 }
             }
         }
-
-        @GuardedBy("mLock")
-        public void setLocationLocked(Location location) {
-            if (mProvider != null) {
-                long identity = Binder.clearCallingIdentity();
-                try {
-                    ((MockProvider) mProvider).setLocation(location);
-                } finally {
-                    Binder.restoreCallingIdentity(identity);
-                }
-            }
-        }
-
-        @Override
-        public void setRequest(ProviderRequest request, WorkSource workSource) {
-            super.setRequest(request, workSource);
-            mCurrentRequest = request;
-        }
     }
 
     /**
@@ -1181,17 +1123,17 @@
                 // See if receiver has any enabled update records.  Also note if any update records
                 // are high power (has a high power provider with an interval under a threshold).
                 for (UpdateRecord updateRecord : mUpdateRecords.values()) {
-                    LocationProviderManager provider = getLocationProviderLocked(
+                    LocationProviderManager manager = getLocationProviderManager(
                             updateRecord.mProvider);
-                    if (provider == null) {
+                    if (manager == null) {
                         continue;
                     }
-                    if (!provider.isUseableLocked() && !isSettingsExemptLocked(updateRecord)) {
+                    if (!manager.isUseable() && !isSettingsExemptLocked(updateRecord)) {
                         continue;
                     }
 
                     requestingLocation = true;
-                    ProviderProperties properties = provider.getPropertiesLocked();
+                    ProviderProperties properties = manager.getProperties();
                     if (properties != null
                             && properties.mPowerRequirement == Criteria.POWER_HIGH
                             && updateRecord.mRequest.getInterval() < HIGH_POWER_INTERVAL_MS) {
@@ -1432,7 +1374,7 @@
             String featureId, String listenerIdentifier) {
         Objects.requireNonNull(listenerIdentifier);
 
-        return mGnssManagerService == null ? false : mGnssManagerService.addGnssBatchingCallback(
+        return mGnssManagerService != null && mGnssManagerService.addGnssBatchingCallback(
                 callback, packageName, featureId, listenerIdentifier);
     }
 
@@ -1443,7 +1385,7 @@
 
     @Override
     public boolean startGnssBatch(long periodNanos, boolean wakeOnFifoFull, String packageName) {
-        return mGnssManagerService == null ? false : mGnssManagerService.startGnssBatch(periodNanos,
+        return mGnssManagerService != null && mGnssManagerService.startGnssBatch(periodNanos,
                 wakeOnFifoFull, packageName);
     }
 
@@ -1454,35 +1396,14 @@
 
     @Override
     public boolean stopGnssBatch() {
-        return mGnssManagerService == null ? false : mGnssManagerService.stopGnssBatch();
+        return mGnssManagerService != null && mGnssManagerService.stopGnssBatch();
     }
 
-    @GuardedBy("mLock")
-    private void addProviderLocked(LocationProviderManager provider) {
-        Preconditions.checkState(getLocationProviderLocked(provider.getName()) == null);
-
-        mProviders.add(provider);
-
-        // it would be more correct to call this for all users, but we know this can only
-        // affect the current user since providers are disabled for non-current users
-        provider.onUseableChangedLocked(mCurrentUserId);
-    }
-
-    @GuardedBy("mLock")
-    private void removeProviderLocked(LocationProviderManager provider) {
-        if (mProviders.remove(provider)) {
-            // it would be more correct to call this for all users, but we know this can only
-            // affect the current user since providers are disabled for non-current users
-            provider.onUseableChangedLocked(mCurrentUserId);
-        }
-    }
-
-    @GuardedBy("mLock")
     @Nullable
-    private LocationProviderManager getLocationProviderLocked(String providerName) {
-        for (LocationProviderManager provider : mProviders) {
-            if (providerName.equals(provider.getName())) {
-                return provider;
+    private LocationProviderManager getLocationProviderManager(String providerName) {
+        for (LocationProviderManager manager : mProviderManagers) {
+            if (providerName.equals(manager.getName())) {
+                return manager;
             }
         }
 
@@ -1531,12 +1452,12 @@
             // network and fused providers are ok with COARSE or FINE
             return RESOLUTION_LEVEL_COARSE;
         } else {
-            for (LocationProviderManager lp : mProviders) {
+            for (LocationProviderManager lp : mProviderManagers) {
                 if (!lp.getName().equals(provider)) {
                     continue;
                 }
 
-                ProviderProperties properties = lp.getPropertiesLocked();
+                ProviderProperties properties = lp.getProperties();
                 if (properties != null) {
                     if (properties.mRequiresSatellite) {
                         // provider requiring satellites require FINE permission
@@ -1587,11 +1508,9 @@
             case RESOLUTION_LEVEL_COARSE:
                 return AppOpsManager.OPSTR_COARSE_LOCATION;
             case RESOLUTION_LEVEL_FINE:
-                return AppOpsManager.OPSTR_FINE_LOCATION;
+                // fall through
             case RESOLUTION_LEVEL_NONE:
-                // The client is not allowed to get any location, so both FINE and COARSE ops will
-                // be denied. Pick the most restrictive one to be safe.
-                return AppOpsManager.OPSTR_FINE_LOCATION;
+                // fall through
             default:
                 // Use the most restrictive ops if not sure.
                 return AppOpsManager.OPSTR_FINE_LOCATION;
@@ -1629,17 +1548,14 @@
      */
     @Override
     public List<String> getAllProviders() {
-        synchronized (mLock) {
-            ArrayList<String> providers = new ArrayList<>(mProviders.size());
-            for (LocationProviderManager provider : mProviders) {
-                String name = provider.getName();
-                if (FUSED_PROVIDER.equals(name)) {
-                    continue;
-                }
-                providers.add(name);
+        ArrayList<String> providers = new ArrayList<>(mProviderManagers.size());
+        for (LocationProviderManager manager : mProviderManagers) {
+            if (FUSED_PROVIDER.equals(manager.getName())) {
+                continue;
             }
-            return providers;
+            providers.add(manager.getName());
         }
+        return providers;
     }
 
     /**
@@ -1651,21 +1567,21 @@
     public List<String> getProviders(Criteria criteria, boolean enabledOnly) {
         int allowedResolutionLevel = getCallerAllowedResolutionLevel();
         synchronized (mLock) {
-            ArrayList<String> providers = new ArrayList<>(mProviders.size());
-            for (LocationProviderManager provider : mProviders) {
-                String name = provider.getName();
+            ArrayList<String> providers = new ArrayList<>(mProviderManagers.size());
+            for (LocationProviderManager manager : mProviderManagers) {
+                String name = manager.getName();
                 if (FUSED_PROVIDER.equals(name)) {
                     continue;
                 }
                 if (allowedResolutionLevel < getMinimumResolutionLevelForProviderUseLocked(name)) {
                     continue;
                 }
-                if (enabledOnly && !provider.isUseableLocked()) {
+                if (enabledOnly && !manager.isUseable()) {
                     continue;
                 }
                 if (criteria != null
                         && !android.location.LocationProvider.propertiesMeetCriteria(
-                        name, provider.getPropertiesLocked(), criteria)) {
+                        name, manager.getProperties(), criteria)) {
                     continue;
                 }
                 providers.add(name);
@@ -1702,12 +1618,12 @@
     }
 
     @GuardedBy("mLock")
-    private void updateProviderUseableLocked(LocationProviderManager provider) {
-        boolean useable = provider.isUseableLocked();
+    private void updateProviderUseableLocked(LocationProviderManager manager) {
+        boolean useable = manager.isUseable();
 
         ArrayList<Receiver> deadReceivers = null;
 
-        ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider.getName());
+        ArrayList<UpdateRecord> records = mRecordsByProvider.get(manager.getName());
         if (records != null) {
             for (UpdateRecord record : records) {
                 if (!isCurrentProfileLocked(
@@ -1721,7 +1637,7 @@
                 }
 
                 // Sends a notification message to the receiver
-                if (!record.mReceiver.callProviderEnabledLocked(provider.getName(), useable)) {
+                if (!record.mReceiver.callProviderEnabledLocked(manager.getName(), useable)) {
                     if (deadReceivers == null) {
                         deadReceivers = new ArrayList<>();
                     }
@@ -1736,26 +1652,25 @@
             }
         }
 
-        applyRequirementsLocked(provider);
+        applyRequirementsLocked(manager);
     }
 
     @GuardedBy("mLock")
     private void applyRequirementsLocked(String providerName) {
-        LocationProviderManager provider = getLocationProviderLocked(providerName);
-        if (provider != null) {
-            applyRequirementsLocked(provider);
+        LocationProviderManager manager = getLocationProviderManager(providerName);
+        if (manager != null) {
+            applyRequirementsLocked(manager);
         }
     }
 
     @GuardedBy("mLock")
-    private void applyRequirementsLocked(LocationProviderManager provider) {
-        ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider.getName());
-        WorkSource worksource = new WorkSource();
-        ProviderRequest providerRequest = new ProviderRequest();
+    private void applyRequirementsLocked(LocationProviderManager manager) {
+        ArrayList<UpdateRecord> records = mRecordsByProvider.get(manager.getName());
+        ProviderRequest.Builder providerRequest = new ProviderRequest.Builder();
 
         // if provider is not active, it should not respond to requests
 
-        if (mProviders.contains(provider) && records != null && !records.isEmpty()) {
+        if (mProviderManagers.contains(manager) && records != null && !records.isEmpty()) {
             long backgroundThrottleInterval;
 
             long identity = Binder.clearCallingIdentity();
@@ -1765,6 +1680,8 @@
                 Binder.restoreCallingIdentity(identity);
             }
 
+            ArrayList<LocationRequest> requests = new ArrayList<>(records.size());
+
             final boolean isForegroundOnlyMode =
                     mBatterySaverMode == PowerManager.LOCATION_MODE_FOREGROUND_ONLY;
             final boolean shouldThrottleRequests =
@@ -1772,7 +1689,7 @@
                             == PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF
                             && !mPowerManager.isInteractive();
             // initialize the low power mode to true and set to false if any of the records requires
-            providerRequest.lowPowerMode = true;
+            providerRequest.setLowPowerMode(true);
             for (UpdateRecord record : records) {
                 if (!isCurrentProfileLocked(
                         UserHandle.getUserId(record.mReceiver.mCallerIdentity.mUid))) {
@@ -1787,10 +1704,10 @@
                 }
                 final boolean isBatterySaverDisablingLocation = shouldThrottleRequests
                         || (isForegroundOnlyMode && !record.mIsForegroundUid);
-                if (!provider.isUseableLocked() || isBatterySaverDisablingLocation) {
+                if (!manager.isUseable() || isBatterySaverDisablingLocation) {
                     if (isSettingsExemptLocked(record)) {
-                        providerRequest.locationSettingsIgnored = true;
-                        providerRequest.lowPowerMode = false;
+                        providerRequest.setLocationSettingsIgnored(true);
+                        providerRequest.setLowPowerMode(false);
                     } else {
                         continue;
                     }
@@ -1801,7 +1718,7 @@
 
 
                 // if we're forcing location, don't apply any throttling
-                if (!providerRequest.locationSettingsIgnored && !isThrottlingExemptLocked(
+                if (!providerRequest.isLocationSettingsIgnored() && !isThrottlingExemptLocked(
                         record.mReceiver.mCallerIdentity)) {
                     if (!record.mIsForegroundUid) {
                         interval = Math.max(interval, backgroundThrottleInterval);
@@ -1813,23 +1730,25 @@
                 }
 
                 record.mRequest = locationRequest;
-                providerRequest.locationRequests.add(locationRequest);
+                requests.add(locationRequest);
                 if (!locationRequest.isLowPowerMode()) {
-                    providerRequest.lowPowerMode = false;
+                    providerRequest.setLowPowerMode(false);
                 }
-                if (interval < providerRequest.interval) {
-                    providerRequest.reportLocation = true;
-                    providerRequest.interval = interval;
+                if (interval < providerRequest.getInterval()) {
+                    providerRequest.setInterval(interval);
                 }
             }
 
-            if (providerRequest.reportLocation) {
+            providerRequest.setLocationRequests(requests);
+
+            if (providerRequest.getInterval() < Long.MAX_VALUE) {
                 // calculate who to blame for power
                 // This is somewhat arbitrary. We pick a threshold interval
                 // that is slightly higher that the minimum interval, and
                 // spread the blame across all applications with a request
                 // under that threshold.
-                long thresholdInterval = (providerRequest.interval + 1000) * 3 / 2;
+                // TODO: overflow
+                long thresholdInterval = (providerRequest.getInterval() + 1000) * 3 / 2;
                 for (UpdateRecord record : records) {
                     if (isCurrentProfileLocked(
                             UserHandle.getUserId(record.mReceiver.mCallerIdentity.mUid))) {
@@ -1837,18 +1756,18 @@
 
                         // Don't assign battery blame for update records whose
                         // client has no permission to receive location data.
-                        if (!providerRequest.locationRequests.contains(locationRequest)) {
+                        if (!providerRequest.getLocationRequests().contains(locationRequest)) {
                             continue;
                         }
 
                         if (locationRequest.getInterval() <= thresholdInterval) {
                             if (record.mReceiver.mWorkSource != null
                                     && isValidWorkSource(record.mReceiver.mWorkSource)) {
-                                worksource.add(record.mReceiver.mWorkSource);
+                                providerRequest.getWorkSource().add(record.mReceiver.mWorkSource);
                             } else {
                                 // Assign blame to caller if there's no WorkSource associated with
                                 // the request or if it's invalid.
-                                worksource.add(
+                                providerRequest.getWorkSource().add(
                                         record.mReceiver.mCallerIdentity.mUid,
                                         record.mReceiver.mCallerIdentity.mPackageName);
                             }
@@ -1858,7 +1777,7 @@
             }
         }
 
-        provider.setRequest(providerRequest, worksource);
+        manager.setRequest(providerRequest.build());
     }
 
     /**
@@ -2198,8 +2117,8 @@
             throw new IllegalArgumentException("provider name must not be null");
         }
 
-        LocationProviderManager provider = getLocationProviderLocked(name);
-        if (provider == null) {
+        LocationProviderManager manager = getLocationProviderManager(name);
+        if (manager == null) {
             throw new IllegalArgumentException("provider doesn't exist: " + name);
         }
 
@@ -2217,7 +2136,7 @@
             oldRecord.disposeLocked(false);
         }
 
-        if (!provider.isUseableLocked() && !isSettingsExemptLocked(record)) {
+        if (!manager.isUseable() && !isSettingsExemptLocked(record)) {
             // Notify the listener that updates are currently disabled - but only if the request
             // does not ignore location settings
             receiver.callProviderEnabledLocked(name, false);
@@ -2320,8 +2239,8 @@
                 // or use the fused provider
                 String name = request.getProvider();
                 if (name == null) name = LocationManager.FUSED_PROVIDER;
-                LocationProviderManager provider = getLocationProviderLocked(name);
-                if (provider == null) return null;
+                LocationProviderManager manager = getLocationProviderManager(name);
+                if (manager == null) return null;
 
                 // only the current user or location providers may get location this way
                 if (!isCurrentProfileLocked(UserHandle.getUserId(uid)) && !isProviderPackage(
@@ -2329,7 +2248,7 @@
                     return null;
                 }
 
-                if (!provider.isUseableLocked()) {
+                if (!manager.isUseable()) {
                     return null;
                 }
 
@@ -2450,19 +2369,19 @@
                 "Access Fine Location permission not granted to inject Location");
 
         synchronized (mLock) {
-            LocationProviderManager provider = getLocationProviderLocked(location.getProvider());
-            if (provider == null || !provider.isUseableLocked()) {
+            LocationProviderManager manager = getLocationProviderManager(location.getProvider());
+            if (manager == null || !manager.isUseable()) {
                 return false;
             }
 
             // NOTE: If last location is already available, location is not injected.  If
             // provider's normal source (like a GPS chipset) have already provided an output
             // there is no need to inject this location.
-            if (mLastLocation.get(provider.getName()) != null) {
+            if (mLastLocation.get(manager.getName()) != null) {
                 return false;
             }
 
-            updateLastLocationLocked(location, provider.getName());
+            updateLastLocationLocked(location, manager.getName());
             return true;
         }
     }
@@ -2511,7 +2430,7 @@
                         packageName,
                         request,
                         /* hasListener= */ false,
-                        intent != null,
+                        true,
                         geofence,
                         mActivityManager.getPackageImportance(packageName));
             }
@@ -2542,7 +2461,7 @@
                         packageName,
                         /* LocationRequest= */ null,
                         /* hasListener= */ false,
-                        intent != null,
+                        true,
                         geofence,
                         mActivityManager.getPackageImportance(packageName));
             }
@@ -2555,7 +2474,7 @@
     @Override
     public boolean registerGnssStatusCallback(IGnssStatusListener listener, String packageName,
             String featureId) {
-        return mGnssManagerService == null ? false : mGnssManagerService.registerGnssStatusCallback(
+        return mGnssManagerService != null && mGnssManagerService.registerGnssStatusCallback(
                 listener, packageName, featureId);
     }
 
@@ -2569,9 +2488,8 @@
             String packageName, String featureId, String listenerIdentifier) {
         Objects.requireNonNull(listenerIdentifier);
 
-        return mGnssManagerService == null ? false
-                : mGnssManagerService.addGnssMeasurementsListener(listener, packageName, featureId,
-                       listenerIdentifier);
+        return mGnssManagerService != null && mGnssManagerService.addGnssMeasurementsListener(
+                listener, packageName, featureId, listenerIdentifier);
     }
 
     @Override
@@ -2586,8 +2504,8 @@
     public void injectGnssMeasurementCorrections(
             GnssMeasurementCorrections measurementCorrections, String packageName) {
         if (mGnssManagerService != null) {
-            mGnssManagerService.injectGnssMeasurementCorrections(
-                    measurementCorrections, packageName);
+            mGnssManagerService.injectGnssMeasurementCorrections(measurementCorrections,
+                    packageName);
         }
     }
 
@@ -2602,9 +2520,8 @@
             String packageName, String featureId, String listenerIdentifier) {
         Objects.requireNonNull(listenerIdentifier);
 
-        return mGnssManagerService == null ? false
-                : mGnssManagerService.addGnssNavigationMessageListener(listener, packageName,
-                        featureId, listenerIdentifier);
+        return mGnssManagerService != null && mGnssManagerService.addGnssNavigationMessageListener(
+                listener, packageName, featureId, listenerIdentifier);
     }
 
     @Override
@@ -2634,9 +2551,10 @@
                     LocationStatsEnums.API_SEND_EXTRA_COMMAND,
                     providerName);
 
-            LocationProviderManager provider = getLocationProviderLocked(providerName);
-            if (provider != null) {
-                provider.sendExtraCommand(command, extras);
+            LocationProviderManager manager = getLocationProviderManager(providerName);
+            if (manager != null) {
+                manager.sendExtraCommand(Binder.getCallingUid(), Binder.getCallingPid(), command,
+                        extras);
             }
 
             mLocationUsageLogger.logLocationApiUsage(
@@ -2650,43 +2568,37 @@
 
     @Override
     public boolean sendNiResponse(int notifId, int userResponse) {
-        return mGnssManagerService == null ? false : mGnssManagerService.sendNiResponse(notifId,
+        return mGnssManagerService != null && mGnssManagerService.sendNiResponse(notifId,
                 userResponse);
     }
 
     @Override
     public ProviderProperties getProviderProperties(String providerName) {
-        synchronized (mLock) {
-            LocationProviderManager provider = getLocationProviderLocked(providerName);
-            if (provider == null) {
-                return null;
-            }
-            return provider.getPropertiesLocked();
+        LocationProviderManager manager = getLocationProviderManager(providerName);
+        if (manager == null) {
+            return null;
         }
+        return manager.getProperties();
     }
 
     @Override
     public boolean isProviderPackage(String packageName) {
         mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_DEVICE_CONFIG,
                 Manifest.permission.READ_DEVICE_CONFIG + " permission required");
-        synchronized (mLock) {
-            for (LocationProviderManager provider : mProviders) {
-                if (provider.getPackagesLocked().contains(packageName)) {
-                    return true;
-                }
+        for (LocationProviderManager manager : mProviderManagers) {
+            if (manager.getPackages().contains(packageName)) {
+                return true;
             }
-            return false;
         }
+        return false;
     }
 
     @Override
     public List<String> getProviderPackages(String providerName) {
         mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_DEVICE_CONFIG,
                 Manifest.permission.READ_DEVICE_CONFIG + " permission required");
-        synchronized (mLock) {
-            LocationProviderManager provider = getLocationProviderLocked(providerName);
-            return provider == null ? Collections.emptyList() : provider.getPackagesLocked();
-        }
+        LocationProviderManager manager = getLocationProviderManager(providerName);
+        return manager == null ? Collections.emptyList() : new ArrayList<>(manager.getPackages());
     }
 
     @Override
@@ -2753,8 +2665,8 @@
         if (FUSED_PROVIDER.equals(providerName)) return false;
 
         synchronized (mLock) {
-            LocationProviderManager provider = getLocationProviderLocked(providerName);
-            return provider != null && provider.isUseableLocked(userId);
+            LocationProviderManager manager = getLocationProviderManager(providerName);
+            return manager != null && manager.isUseable(userId);
         }
     }
 
@@ -2792,37 +2704,39 @@
     }
 
     @GuardedBy("mLock")
-    private void handleLocationChangedLocked(Location location, LocationProviderManager provider) {
-        if (!mProviders.contains(provider)) {
+    private void handleLocationChangedLocked(Location location, LocationProviderManager manager) {
+        if (!mProviderManagers.contains(manager)) {
+            Log.w(TAG, "received location from unknown provider: " + manager.getName());
             return;
         }
         if (!location.isComplete()) {
-            Log.w(TAG, "Dropping incomplete location: " + location);
+            Log.w(TAG, "dropping incomplete location from " + manager.getName() + " provider: "
+                    + location);
             return;
         }
 
-        // only notify passive provider and update last location for locations that come from
-        // useable providers
-        if (provider.isUseableLocked()) {
-            if (!provider.isPassiveLocked()) {
-                mPassiveProvider.updateLocation(location);
-            }
+        // notify passive provider
+        if (manager != mPassiveManager) {
+            mPassiveManager.updateLocation(new Location(location));
         }
 
         if (D) Log.d(TAG, "incoming location: " + location);
         long now = SystemClock.elapsedRealtime();
-        if (provider.isUseableLocked()) {
-            updateLastLocationLocked(location, provider.getName());
+
+
+        // only update last location for locations that come from useable providers
+        if (manager.isUseable()) {
+            updateLastLocationLocked(location, manager.getName());
         }
 
         // Update last known coarse interval location if enough time has passed.
         Location lastLocationCoarseInterval = mLastLocationCoarseInterval.get(
-                provider.getName());
+                manager.getName());
         if (lastLocationCoarseInterval == null) {
             lastLocationCoarseInterval = new Location(location);
 
-            if (provider.isUseableLocked()) {
-                mLastLocationCoarseInterval.put(provider.getName(), lastLocationCoarseInterval);
+            if (manager.isUseable()) {
+                mLastLocationCoarseInterval.put(manager.getName(), lastLocationCoarseInterval);
             }
         }
         long timeDeltaMs = TimeUnit.NANOSECONDS.toMillis(location.getElapsedRealtimeNanos()
@@ -2837,7 +2751,7 @@
                 lastLocationCoarseInterval.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION);
 
         // Skip if there are no UpdateRecords for this provider.
-        ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider.getName());
+        ArrayList<UpdateRecord> records = mRecordsByProvider.get(manager.getName());
         if (records == null || records.size() == 0) return;
 
         // Fetch coarse location
@@ -2854,7 +2768,7 @@
             Receiver receiver = r.mReceiver;
             boolean receiverDead = false;
 
-            if (!provider.isUseableLocked() && !isSettingsExemptLocked(r)) {
+            if (!manager.isUseable() && !isSettingsExemptLocked(r)) {
                 continue;
             }
 
@@ -2949,7 +2863,7 @@
             for (UpdateRecord r : deadUpdateRecords) {
                 r.disposeLocked(true);
             }
-            applyRequirementsLocked(provider);
+            applyRequirementsLocked(manager);
         }
     }
 
@@ -3006,143 +2920,99 @@
 
     // Mock Providers
 
-    private boolean canCallerAccessMockLocation(String opPackageName) {
-        return mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(),
-                opPackageName) == AppOpsManager.MODE_ALLOWED;
-    }
-
     @Override
-    public void addTestProvider(String name, ProviderProperties properties, String opPackageName) {
-        if (!canCallerAccessMockLocation(opPackageName)) {
+    public void addTestProvider(String provider, ProviderProperties properties,
+            String packageName) {
+        if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName)
+                != AppOpsManager.MODE_ALLOWED) {
             return;
         }
 
-        if (PASSIVE_PROVIDER.equals(name)) {
-            throw new IllegalArgumentException("Cannot mock the passive location provider");
+        synchronized (mLock) {
+            LocationProviderManager manager = getLocationProviderManager(provider);
+            if (manager == null) {
+                manager = new LocationProviderManager(provider);
+                mProviderManagers.add(manager);
+            }
+
+            manager.setMockProvider(new MockProvider(mContext, properties));
+        }
+    }
+
+    @Override
+    public void removeTestProvider(String provider, String packageName) {
+        if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName)
+                != AppOpsManager.MODE_ALLOWED) {
+            return;
         }
 
         synchronized (mLock) {
-            long identity = Binder.clearCallingIdentity();
-            try {
-                LocationProviderManager oldProvider = getLocationProviderLocked(name);
-                if (oldProvider != null) {
-                    removeProviderLocked(oldProvider);
-                }
+            LocationProviderManager manager = getLocationProviderManager(provider);
+            if (manager == null) {
+                return;
+            }
 
-                MockLocationProvider mockProviderManager = new MockLocationProvider(name);
-                addProviderLocked(mockProviderManager);
-                mockProviderManager.attachLocked(
-                        new MockProvider(mContext, mockProviderManager, properties));
-            } finally {
-                Binder.restoreCallingIdentity(identity);
+            manager.setMockProvider(null);
+            if (!manager.hasProvider()) {
+                mProviderManagers.remove(manager);
+                mLastLocation.remove(manager.getName());
+                mLastLocationCoarseInterval.remove(manager.getName());
             }
         }
     }
 
     @Override
-    public void removeTestProvider(String name, String opPackageName) {
-        if (!canCallerAccessMockLocation(opPackageName)) {
+    public void setTestProviderLocation(String provider, Location location, String packageName) {
+        if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName)
+                != AppOpsManager.MODE_ALLOWED) {
             return;
         }
 
-        synchronized (mLock) {
-            long identity = Binder.clearCallingIdentity();
-            try {
-                LocationProviderManager testProvider = getLocationProviderLocked(name);
-                if (testProvider == null || !testProvider.isMock()) {
-                    return;
-                }
-
-                removeProviderLocked(testProvider);
-
-                // reinstate real provider if available
-                LocationProviderManager realProvider = null;
-                for (LocationProviderManager provider : mRealProviders) {
-                    if (name.equals(provider.getName())) {
-                        realProvider = provider;
-                        break;
-                    }
-                }
-
-                if (realProvider != null) {
-                    addProviderLocked(realProvider);
-                }
-            } finally {
-                Binder.restoreCallingIdentity(identity);
-            }
+        LocationProviderManager manager = getLocationProviderManager(provider);
+        if (manager == null) {
+            throw new IllegalArgumentException("provider doesn't exist: " + provider);
         }
+
+        manager.setMockProviderLocation(location);
     }
 
     @Override
-    public void setTestProviderLocation(String providerName, Location location,
-            String opPackageName) {
-        if (!canCallerAccessMockLocation(opPackageName)) {
+    public void setTestProviderEnabled(String provider, boolean enabled, String packageName) {
+        if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName)
+                != AppOpsManager.MODE_ALLOWED) {
             return;
         }
 
-        synchronized (mLock) {
-            LocationProviderManager testProvider = getLocationProviderLocked(providerName);
-            if (testProvider == null || !testProvider.isMock()) {
-                throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown");
-            }
-
-            String locationProvider = location.getProvider();
-            if (!TextUtils.isEmpty(locationProvider) && !providerName.equals(locationProvider)) {
-                // The location has an explicit provider that is different from the mock
-                // provider name. The caller may be trying to fool us via b/33091107.
-                EventLog.writeEvent(0x534e4554, "33091107", Binder.getCallingUid(),
-                        providerName + "!=" + location.getProvider());
-            }
-
-            ((MockLocationProvider) testProvider).setLocationLocked(location);
-        }
-    }
-
-    @Override
-    public void setTestProviderEnabled(String providerName, boolean enabled, String opPackageName) {
-        if (!canCallerAccessMockLocation(opPackageName)) {
-            return;
+        LocationProviderManager manager = getLocationProviderManager(provider);
+        if (manager == null) {
+            throw new IllegalArgumentException("provider doesn't exist: " + provider);
         }
 
-        synchronized (mLock) {
-            LocationProviderManager testProvider = getLocationProviderLocked(providerName);
-            if (testProvider == null || !testProvider.isMock()) {
-                throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown");
-            }
-
-            ((MockLocationProvider) testProvider).setEnabledLocked(enabled);
-        }
+        manager.setMockProviderEnabled(enabled);
     }
 
     @Override
     @NonNull
-    public List<LocationRequest> getTestProviderCurrentRequests(String providerName,
-            String opPackageName) {
-        if (!canCallerAccessMockLocation(opPackageName)) {
+    public List<LocationRequest> getTestProviderCurrentRequests(String provider,
+            String packageName) {
+        if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName)
+                != AppOpsManager.MODE_ALLOWED) {
             return Collections.emptyList();
         }
 
-        synchronized (mLock) {
-            LocationProviderManager testProvider = getLocationProviderLocked(providerName);
-            if (testProvider == null || !testProvider.isMock()) {
-                throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown");
-            }
-
-            MockLocationProvider provider = (MockLocationProvider) testProvider;
-            if (provider.mCurrentRequest == null) {
-                return Collections.emptyList();
-            }
-            List<LocationRequest> requests = new ArrayList<>();
-            for (LocationRequest request : provider.mCurrentRequest.locationRequests) {
-                requests.add(new LocationRequest(request));
-            }
-            return requests;
+        LocationProviderManager manager = getLocationProviderManager(provider);
+        if (manager == null) {
+            throw new IllegalArgumentException("provider doesn't exist: " + provider);
         }
+
+        return manager.getMockProviderRequests();
     }
 
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+        if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) {
+            return;
+        }
 
         IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
 
@@ -3226,25 +3096,27 @@
                 mLocationFudger.dump(fd, ipw, args);
                 ipw.decreaseIndent();
             }
-
-            ipw.println("Location Settings:");
-            ipw.increaseIndent();
-            mSettingsStore.dump(fd, ipw, args);
-            ipw.decreaseIndent();
-
-            ipw.println("Location Providers:");
-            ipw.increaseIndent();
-            for (LocationProviderManager provider : mProviders) {
-                provider.dumpLocked(fd, ipw, args);
-            }
-            ipw.decreaseIndent();
         }
 
-        if (mGnssManagerService != null) {
-            ipw.println("GNSS:");
-            ipw.increaseIndent();
-            mGnssManagerService.dump(fd, ipw, args);
-            ipw.decreaseIndent();
+        ipw.println("Location Settings:");
+        ipw.increaseIndent();
+        mSettingsStore.dump(fd, ipw, args);
+        ipw.decreaseIndent();
+
+        ipw.println("Location Providers:");
+        ipw.increaseIndent();
+        for (LocationProviderManager manager : mProviderManagers) {
+            manager.dump(fd, ipw, args);
+        }
+        ipw.decreaseIndent();
+
+        synchronized (mLock) {
+            if (mGnssManagerService != null) {
+                ipw.println("GNSS:");
+                ipw.increaseIndent();
+                mGnssManagerService.dump(fd, ipw, args);
+                ipw.decreaseIndent();
+            }
         }
     }
 }
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index deff440..7b4fd37 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -199,13 +199,14 @@
         mSystemClock = clock;
         mNumberOfNativeCrashPollsRemaining = NUMBER_OF_NATIVE_CRASH_POLLS;
         loadFromFile();
+        sPackageWatchdog = this;
     }
 
     /** Creates or gets singleton instance of PackageWatchdog. */
     public static PackageWatchdog getInstance(Context context) {
         synchronized (PackageWatchdog.class) {
             if (sPackageWatchdog == null) {
-                sPackageWatchdog = new PackageWatchdog(context);
+                new PackageWatchdog(context);
             }
             return sPackageWatchdog;
         }
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index fb1a962..3dafc64 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -18,8 +18,12 @@
 
 import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
 
+import android.annotation.Nullable;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.VersionedPackage;
 import android.os.Build;
 import android.os.Environment;
 import android.os.FileUtils;
@@ -36,8 +40,12 @@
 import android.util.SparseArray;
 import android.util.StatsLog;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
+import com.android.server.PackageWatchdog.FailureReasons;
+import com.android.server.PackageWatchdog.PackageHealthObserver;
+import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
 import com.android.server.am.SettingsToPropertiesMapper;
 import com.android.server.utils.FlagNamespaceUtils;
 
@@ -79,19 +87,30 @@
     @VisibleForTesting
     static final long BOOT_TRIGGER_WINDOW_MILLIS = 600 * DateUtils.SECOND_IN_MILLIS;
     @VisibleForTesting
-    static final long PERSISTENT_APP_CRASH_TRIGGER_WINDOW_MILLIS = 30 * DateUtils.SECOND_IN_MILLIS;
-    @VisibleForTesting
     static final String TAG = "RescueParty";
 
+    private static final String NAME = "rescue-party-observer";
+
+
     private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue";
     private static final String PROP_RESCUE_BOOT_START = "sys.rescue_boot_start";
     private static final String PROP_VIRTUAL_DEVICE = "ro.hardware.virtual_device";
 
+    private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT
+            | ApplicationInfo.FLAG_SYSTEM;
+
+
     /** Threshold for boot loops */
     private static final Threshold sBoot = new BootThreshold();
     /** Threshold for app crash loops */
     private static SparseArray<Threshold> sApps = new SparseArray<>();
 
+    /** Register the Rescue Party observer as a Package Watchdog health observer */
+    public static void registerHealthObserver(Context context) {
+        PackageWatchdog.getInstance(context).registerHealthObserver(
+                RescuePartyObserver.getInstance(context));
+    }
+
     private static boolean isDisabled() {
         // Check if we're explicitly enabled for testing
         if (SystemProperties.getBoolean(PROP_ENABLE_RESCUE, false)) {
@@ -135,24 +154,6 @@
     }
 
     /**
-     * Take note of a persistent app or apex module crash. If we notice too many of these
-     * events happening in rapid succession, we'll send out a rescue party.
-     */
-    public static void noteAppCrash(Context context, int uid) {
-        if (isDisabled()) return;
-        Threshold t = sApps.get(uid);
-        if (t == null) {
-            t = new AppThreshold(uid);
-            sApps.put(uid, t);
-        }
-        if (t.incrementAndTest()) {
-            t.reset();
-            incrementRescueLevel(t.uid);
-            executeRescueLevel(context);
-        }
-    }
-
-    /**
      * Check if we're currently attempting to reboot for a factory reset.
      */
     public static boolean isAttemptingFactoryReset() {
@@ -171,11 +172,6 @@
     @VisibleForTesting
     static void resetAllThresholds() {
         sBoot.reset();
-
-        for (int i = 0; i < sApps.size(); i++) {
-            Threshold appThreshold = sApps.get(sApps.keyAt(i));
-            appThreshold.reset();
-        }
     }
 
     @VisibleForTesting
@@ -243,6 +239,28 @@
                 FlagNamespaceUtils.NAMESPACE_NO_PACKAGE);
     }
 
+    private static int mapRescueLevelToUserImpact(int rescueLevel) {
+        switch(rescueLevel) {
+            case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
+            case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
+                return PackageHealthObserverImpact.USER_IMPACT_LOW;
+            case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
+            case LEVEL_FACTORY_RESET:
+                return PackageHealthObserverImpact.USER_IMPACT_HIGH;
+            default:
+                return PackageHealthObserverImpact.USER_IMPACT_NONE;
+        }
+    }
+
+    private static int getPackageUid(Context context, String packageName) {
+        try {
+            return context.getPackageManager().getPackageUid(packageName, 0);
+        } catch (PackageManager.NameNotFoundException e) {
+            // Since UIDs are always >= 0, this value means the UID could not be determined.
+            return -1;
+        }
+    }
+
     private static void resetAllSettings(Context context, int mode) throws Exception {
         // Try our best to reset all settings possible, and once finished
         // rethrow any exception that we encountered
@@ -271,6 +289,89 @@
     }
 
     /**
+     * Handle mitigation action for package failures. This observer will be register to Package
+     * Watchdog and will receive calls about package failures. This observer is persistent so it
+     * may choose to mitigate failures for packages it has not explicitly asked to observe.
+     */
+    public static class RescuePartyObserver implements PackageHealthObserver {
+
+        private Context mContext;
+
+        @GuardedBy("RescuePartyObserver.class")
+        static RescuePartyObserver sRescuePartyObserver;
+
+        private RescuePartyObserver(Context context) {
+            mContext = context;
+        }
+
+        /** Creates or gets singleton instance of RescueParty. */
+        public static RescuePartyObserver getInstance(Context context) {
+            synchronized (RescuePartyObserver.class) {
+                if (sRescuePartyObserver == null) {
+                    sRescuePartyObserver = new RescuePartyObserver(context);
+                }
+                return sRescuePartyObserver;
+            }
+        }
+
+        @Override
+        public int onHealthCheckFailed(@Nullable VersionedPackage failedPackage,
+                @FailureReasons int failureReason) {
+            if (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH
+                    || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING) {
+                int rescueLevel = MathUtils.constrain(
+                        SystemProperties.getInt(PROP_RESCUE_LEVEL, LEVEL_NONE) + 1,
+                        LEVEL_NONE, LEVEL_FACTORY_RESET);
+                return mapRescueLevelToUserImpact(rescueLevel);
+            } else {
+                return PackageHealthObserverImpact.USER_IMPACT_NONE;
+            }
+        }
+
+        @Override
+        public boolean execute(@Nullable VersionedPackage failedPackage,
+                @FailureReasons int failureReason) {
+            if (isDisabled()) {
+                return false;
+            }
+            if (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH
+                    || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING) {
+                int triggerUid = getPackageUid(mContext, failedPackage.getPackageName());
+                incrementRescueLevel(triggerUid);
+                executeRescueLevel(mContext);
+                return true;
+            } else {
+                return false;
+            }
+        }
+
+        @Override
+        public boolean isPersistent() {
+            return true;
+        }
+
+        @Override
+        public boolean mayObservePackage(String packageName) {
+            PackageManager pm = mContext.getPackageManager();
+            try {
+                // A package is a Mainline module if this is non-null
+                if (pm.getModuleInfo(packageName, 0) != null) {
+                    return true;
+                }
+                ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
+                return (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK;
+            } catch (PackageManager.NameNotFoundException e) {
+                return false;
+            }
+        }
+
+        @Override
+        public String getName() {
+            return NAME;
+        }
+    }
+
+    /**
      * Threshold that can be triggered if a number of events occur within a
      * window of time.
      */
@@ -349,27 +450,6 @@
         }
     }
 
-    /**
-     * Specialization of {@link Threshold} for monitoring app crashes. It stores
-     * counters in memory.
-     */
-    private static class AppThreshold extends Threshold {
-        private int count;
-        private long start;
-
-        public AppThreshold(int uid) {
-            // We're interested in TRIGGER_COUNT events in any
-            // PERSISTENT_APP_CRASH_TRIGGER_WINDOW_MILLIS second period; apps crash pretty quickly
-            // so we can keep a tight leash on them.
-            super(uid, TRIGGER_COUNT, PERSISTENT_APP_CRASH_TRIGGER_WINDOW_MILLIS);
-        }
-
-        @Override public int getCount() { return count; }
-        @Override public void setCount(int count) { this.count = count; }
-        @Override public long getStart() { return start; }
-        @Override public void setStart(long start) { this.start = start; }
-    }
-
     private static int[] getAllUserIds() {
         int[] userIds = { UserHandle.USER_SYSTEM };
         try {
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 3e6ccb5..0be21c5 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -2194,6 +2194,17 @@
     private static final String PHONE_CONSTANTS_STATE_KEY = "state";
     private static final String PHONE_CONSTANTS_SUBSCRIPTION_KEY = "subscription";
 
+    /**
+     * Broadcast Action: The phone's signal strength has changed. The intent will have the
+     * following extra values:
+     *   phoneName - A string version of the phone name.
+     *   asu - A numeric value for the signal strength.
+     *         An ASU is 0-31 or -1 if unknown (for GSM, dBm = -113 - 2 * asu).
+     *         The following special values are defined:
+     *         0 means "-113 dBm or less".31 means "-51 dBm or greater".
+     */
+    public static final String ACTION_SIGNAL_STRENGTH_CHANGED = "android.intent.action.SIG_STR";
+
     private void broadcastServiceStateChanged(ServiceState state, int phoneId, int subId) {
         long ident = Binder.clearCallingIdentity();
         try {
@@ -2228,7 +2239,7 @@
             Binder.restoreCallingIdentity(ident);
         }
 
-        Intent intent = new Intent(TelephonyIntents.ACTION_SIGNAL_STRENGTH_CHANGED);
+        Intent intent = new Intent(ACTION_SIGNAL_STRENGTH_CHANGED);
         Bundle data = new Bundle();
         fillInSignalStrengthNotifierBundle(signalStrength, data);
         intent.putExtras(data);
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index a58bd9b..e2a036a 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -2068,7 +2068,7 @@
                 + " type=" + resolvedType + " callingUid=" + callingUid);
 
         userId = mAm.mUserController.handleIncomingUser(callingPid, callingUid, userId, false,
-                ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE, "service",
+                ActivityManagerInternal.ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE, "service",
                 callingPackage);
 
         ServiceMap smap = getServiceMapLocked(userId);
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index 5e48dcf..8071f520 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -33,8 +33,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.ModuleInfo;
-import android.content.pm.PackageManager;
 import android.content.pm.VersionedPackage;
 import android.net.Uri;
 import android.os.Binder;
@@ -56,7 +54,6 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.server.PackageWatchdog;
-import com.android.server.RescueParty;
 import com.android.server.wm.WindowProcessController;
 
 import java.io.FileDescriptor;
@@ -423,28 +420,6 @@
         }
 
         if (r != null) {
-            boolean isApexModule = false;
-            try {
-                for (String androidPackage : r.getPackageList()) {
-                    ModuleInfo moduleInfo = mContext.getPackageManager().getModuleInfo(
-                            androidPackage, /*flags=*/ 0);
-                    if (moduleInfo != null) {
-                        isApexModule = true;
-                        break;
-                    }
-                }
-            } catch (IllegalStateException | PackageManager.NameNotFoundException e) {
-                // Call to PackageManager#getModuleInfo() can result in NameNotFoundException or
-                // IllegalStateException. In case they are thrown, there isn't much we can do
-                // other than proceed with app crash handling.
-            }
-
-            if (r.isPersistent() || isApexModule) {
-                // If a persistent app or apex module is stuck in a crash loop, the device isn't
-                // very usable, so we want to consider sending out a rescue party.
-                RescueParty.noteAppCrash(mContext, r.uid);
-            }
-
             mPackageWatchdog.onPackageFailure(r.getPackageListWithVersionCode(),
                     PackageWatchdog.FAILURE_REASON_APP_CRASH);
         }
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index eb1ab38..2b20782 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -16,12 +16,14 @@
 
 package com.android.server.am;
 
+import static android.Manifest.permission.INTERACT_ACROSS_PROFILES;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
 import static android.app.ActivityManager.USER_OP_ERROR_IS_SYSTEM;
 import static android.app.ActivityManager.USER_OP_ERROR_RELATED_USERS_CANNOT_STOP;
 import static android.app.ActivityManager.USER_OP_IS_CURRENT;
 import static android.app.ActivityManager.USER_OP_SUCCESS;
+import static android.app.ActivityManagerInternal.ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE;
 import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
 import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
 import static android.app.ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE;
@@ -1641,9 +1643,10 @@
 
         if (callingUid != 0 && callingUid != SYSTEM_UID) {
             final boolean allow;
+            final boolean isSameProfileGroup = isSameProfileGroup(callingUserId, targetUserId);
             if (mInjector.isCallerRecents(callingUid)
                     && callingUserId == getCurrentUserId()
-                    && isSameProfileGroup(callingUserId, targetUserId)) {
+                    && isSameProfileGroup) {
                 // If the caller is Recents and it is running in the current user, we then allow it
                 // to access its profiles.
                 allow = true;
@@ -1654,6 +1657,9 @@
             } else if (allowMode == ALLOW_FULL_ONLY) {
                 // We require full access, sucks to be you.
                 allow = false;
+            } else if (canInteractWithAcrossProfilesPermission(
+                    allowMode, isSameProfileGroup, callingPid, callingUid)) {
+                allow = true;
             } else if (mInjector.checkComponentPermission(INTERACT_ACROSS_USERS, callingPid,
                     callingUid, -1, true) != PackageManager.PERMISSION_GRANTED) {
                 // If the caller does not have either permission, they are always doomed.
@@ -1661,10 +1667,11 @@
             } else if (allowMode == ALLOW_NON_FULL) {
                 // We are blanket allowing non-full access, you lucky caller!
                 allow = true;
-            } else if (allowMode == ALLOW_NON_FULL_IN_PROFILE) {
+            } else if (allowMode == ALLOW_NON_FULL_IN_PROFILE
+                        || allowMode == ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) {
                 // We may or may not allow this depending on whether the two users are
                 // in the same profile.
-                allow = isSameProfileGroup(callingUserId, targetUserId);
+                allow = isSameProfileGroup;
             } else {
                 throw new IllegalArgumentException("Unknown mode: " + allowMode);
             }
@@ -1690,6 +1697,11 @@
                     if (allowMode != ALLOW_FULL_ONLY) {
                         builder.append(" or ");
                         builder.append(INTERACT_ACROSS_USERS);
+                        if (isSameProfileGroup
+                                && allowMode == ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) {
+                            builder.append(" or ");
+                            builder.append(INTERACT_ACROSS_PROFILES);
+                        }
                     }
                     String msg = builder.toString();
                     Slog.w(TAG, msg);
@@ -1710,6 +1722,19 @@
         return targetUserId;
     }
 
+    private boolean canInteractWithAcrossProfilesPermission(
+            int allowMode, boolean isSameProfileGroup, int callingPid, int callingUid) {
+        if (allowMode != ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) {
+            return false;
+        }
+        if (!isSameProfileGroup) {
+            return false;
+        }
+        return mInjector.checkComponentPermission(
+                INTERACT_ACROSS_PROFILES, callingPid, callingUid, /*owningUid= */-1,
+                /*exported= */true) == PackageManager.PERMISSION_GRANTED;
+    }
+
     int unsafeConvertIncomingUser(@UserIdInt int userId) {
         return (userId == UserHandle.USER_CURRENT || userId == UserHandle.USER_CURRENT_OR_SELF)
                 ? getCurrentUserId(): userId;
diff --git a/services/core/java/com/android/server/integrity/IntegrityFileManager.java b/services/core/java/com/android/server/integrity/IntegrityFileManager.java
index 31d4816..17a4b9c 100644
--- a/services/core/java/com/android/server/integrity/IntegrityFileManager.java
+++ b/services/core/java/com/android/server/integrity/IntegrityFileManager.java
@@ -39,6 +39,7 @@
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
 
@@ -153,14 +154,19 @@
             throws IOException, RuleParseException {
         synchronized (RULES_LOCK) {
             // Try to identify indexes from the index file.
-            List<RuleIndexRange> ruleReadingIndexes =
-                    mRuleIndexingController.identifyRulesToEvaluate(appInstallMetadata);
+            List<RuleIndexRange> ruleReadingIndexes;
+            try {
+                ruleReadingIndexes =
+                        mRuleIndexingController.identifyRulesToEvaluate(appInstallMetadata);
+            } catch (Exception e) {
+                Slog.w(TAG, "Error identifying the rule indexes. Trying unindexed.", e);
+                ruleReadingIndexes = Collections.emptyList();
+            }
 
-            // Read the rules based on the index information.
-            // TODO(b/145493956): Provide the identified indexes to the rule reader.
+            // Read the rules based on the index information when available.
             try (FileInputStream inputStream =
                          new FileInputStream(new File(mRulesDir, RULES_FILE))) {
-                List<Rule> rules = mRuleParser.parse(inputStream);
+                List<Rule> rules = mRuleParser.parse(inputStream, ruleReadingIndexes);
                 return rules;
             }
         }
diff --git a/services/core/java/com/android/server/integrity/model/BitTrackedInputStream.java b/services/core/java/com/android/server/integrity/model/BitTrackedInputStream.java
new file mode 100644
index 0000000..e555e3e
--- /dev/null
+++ b/services/core/java/com/android/server/integrity/model/BitTrackedInputStream.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2020 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.integrity.model;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * An input stream that tracks the total number read bytes since construction and allows moving
+ * fast forward to a certain byte any time during the execution.
+ *
+ * This class is used for efficient reading of rules based on the rule indexing.
+ */
+public class BitTrackedInputStream extends BitInputStream {
+
+    private static int sReadBitsCount;
+
+    /** Constructor with byte array. */
+    public BitTrackedInputStream(byte[] inputStream) {
+        super(inputStream);
+        sReadBitsCount = 0;
+    }
+
+    /** Constructor with input stream. */
+    public BitTrackedInputStream(InputStream inputStream) {
+        super(inputStream);
+        sReadBitsCount = 0;
+    }
+
+    /** Obtains an integer value of the next {@code numOfBits}. */
+    @Override
+    public int getNext(int numOfBits) throws IOException {
+        sReadBitsCount += numOfBits;
+        return super.getNext(numOfBits);
+    }
+
+    /** Returns the current cursor position showing the number of bits that are read. */
+    public int getReadBitsCount() {
+        return sReadBitsCount;
+    }
+
+    /**
+     * Sets the cursor to the specified byte location.
+     *
+     * Note that the integer parameter specifies the location in bytes -- not bits.
+     */
+    public void setCursorToByteLocation(int byteLocation) throws IOException {
+        int bitCountToRead = byteLocation * 8 - sReadBitsCount;
+        if (bitCountToRead < 0) {
+            throw new IllegalStateException("The byte position is already read.");
+        }
+        super.getNext(bitCountToRead);
+        sReadBitsCount = byteLocation * 8;
+    }
+}
diff --git a/services/core/java/com/android/server/integrity/serializer/ByteTrackedOutputStream.java b/services/core/java/com/android/server/integrity/model/ByteTrackedOutputStream.java
similarity index 96%
rename from services/core/java/com/android/server/integrity/serializer/ByteTrackedOutputStream.java
rename to services/core/java/com/android/server/integrity/model/ByteTrackedOutputStream.java
index 62815a9..f575599 100644
--- a/services/core/java/com/android/server/integrity/serializer/ByteTrackedOutputStream.java
+++ b/services/core/java/com/android/server/integrity/model/ByteTrackedOutputStream.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.integrity.serializer;
+package com.android.server.integrity.model;
 
 import java.io.IOException;
 import java.io.OutputStream;
diff --git a/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java b/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java
index cbb6e4e..e744326 100644
--- a/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java
+++ b/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java
@@ -37,71 +37,103 @@
 import android.content.integrity.Formula;
 import android.content.integrity.Rule;
 
-import com.android.server.integrity.model.BitInputStream;
+import com.android.server.integrity.model.BitTrackedInputStream;
 
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 /** A helper class to parse rules into the {@link Rule} model from Binary representation. */
 public class RuleBinaryParser implements RuleParser {
 
-    private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
-
     @Override
     public List<Rule> parse(byte[] ruleBytes) throws RuleParseException {
         try {
-            BitInputStream bitInputStream = new BitInputStream(ruleBytes);
-            return parseRules(bitInputStream);
+            BitTrackedInputStream bitTrackedInputStream = new BitTrackedInputStream(ruleBytes);
+            return parseRules(bitTrackedInputStream, /* indexRanges= */ Collections.emptyList());
         } catch (Exception e) {
             throw new RuleParseException(e.getMessage(), e);
         }
     }
 
     @Override
-    public List<Rule> parse(InputStream inputStream) throws RuleParseException {
+    public List<Rule> parse(InputStream inputStream, List<RuleIndexRange> indexRanges)
+            throws RuleParseException {
         try {
-            BitInputStream bitInputStream = new BitInputStream(inputStream);
-            return parseRules(bitInputStream);
+            BitTrackedInputStream bitTrackedInputStream = new BitTrackedInputStream(inputStream);
+            return parseRules(bitTrackedInputStream, indexRanges);
         } catch (Exception e) {
             throw new RuleParseException(e.getMessage(), e);
         }
     }
 
-    private List<Rule> parseRules(BitInputStream bitInputStream) throws IOException {
-        List<Rule> parsedRules = new ArrayList<>();
+    private List<Rule> parseRules(
+            BitTrackedInputStream bitTrackedInputStream,
+            List<RuleIndexRange> indexRanges)
+            throws IOException {
 
         // Read the rule binary file format version.
-        bitInputStream.getNext(FORMAT_VERSION_BITS);
+        bitTrackedInputStream.getNext(FORMAT_VERSION_BITS);
 
-        while (bitInputStream.hasNext()) {
-            if (bitInputStream.getNext(SIGNAL_BIT) == 1) {
-                parsedRules.add(parseRule(bitInputStream));
+        return indexRanges.isEmpty()
+                ? parseAllRules(bitTrackedInputStream)
+                : parseIndexedRules(bitTrackedInputStream, indexRanges);
+    }
+
+    private List<Rule> parseAllRules(BitTrackedInputStream bitTrackedInputStream)
+            throws IOException {
+        List<Rule> parsedRules = new ArrayList<>();
+
+        while (bitTrackedInputStream.hasNext()) {
+            if (bitTrackedInputStream.getNext(SIGNAL_BIT) == 1) {
+                parsedRules.add(parseRule(bitTrackedInputStream));
             }
         }
 
         return parsedRules;
     }
 
-    private Rule parseRule(BitInputStream bitInputStream) throws IOException {
-        Formula formula = parseFormula(bitInputStream);
-        int effect = bitInputStream.getNext(EFFECT_BITS);
+    private List<Rule> parseIndexedRules(
+            BitTrackedInputStream bitTrackedInputStream, List<RuleIndexRange> indexRanges)
+            throws IOException {
+        List<Rule> parsedRules = new ArrayList<>();
 
-        if (bitInputStream.getNext(SIGNAL_BIT) != 1) {
+        for (RuleIndexRange range : indexRanges) {
+            // Skip the rules that are not in the range.
+            bitTrackedInputStream.setCursorToByteLocation(range.getStartIndex());
+
+            // Read the rules until we reach the end index.
+            while (bitTrackedInputStream.hasNext()
+                    && bitTrackedInputStream.getReadBitsCount() < range.getEndIndex()) {
+                if (bitTrackedInputStream.getNext(SIGNAL_BIT) == 1) {
+                    parsedRules.add(parseRule(bitTrackedInputStream));
+                }
+            }
+        }
+
+        return parsedRules;
+    }
+
+    private Rule parseRule(BitTrackedInputStream bitTrackedInputStream) throws IOException {
+        Formula formula = parseFormula(bitTrackedInputStream);
+        int effect = bitTrackedInputStream.getNext(EFFECT_BITS);
+
+        if (bitTrackedInputStream.getNext(SIGNAL_BIT) != 1) {
             throw new IllegalArgumentException("A rule must end with a '1' bit.");
         }
 
         return new Rule(formula, effect);
     }
 
-    private Formula parseFormula(BitInputStream bitInputStream) throws IOException {
-        int separator = bitInputStream.getNext(SEPARATOR_BITS);
+    private Formula parseFormula(BitTrackedInputStream bitTrackedInputStream) throws IOException {
+        int separator = bitTrackedInputStream.getNext(SEPARATOR_BITS);
         switch (separator) {
             case ATOMIC_FORMULA_START:
-                return parseAtomicFormula(bitInputStream);
+                return parseAtomicFormula(bitTrackedInputStream);
             case COMPOUND_FORMULA_START:
-                return parseCompoundFormula(bitInputStream);
+                return parseCompoundFormula(bitTrackedInputStream);
             case COMPOUND_FORMULA_END:
                 return null;
             default:
@@ -110,37 +142,40 @@
         }
     }
 
-    private CompoundFormula parseCompoundFormula(BitInputStream bitInputStream) throws IOException {
-        int connector = bitInputStream.getNext(CONNECTOR_BITS);
+    private CompoundFormula parseCompoundFormula(BitTrackedInputStream bitTrackedInputStream)
+            throws IOException {
+        int connector = bitTrackedInputStream.getNext(CONNECTOR_BITS);
         List<Formula> formulas = new ArrayList<>();
 
-        Formula parsedFormula = parseFormula(bitInputStream);
+        Formula parsedFormula = parseFormula(bitTrackedInputStream);
         while (parsedFormula != null) {
             formulas.add(parsedFormula);
-            parsedFormula = parseFormula(bitInputStream);
+            parsedFormula = parseFormula(bitTrackedInputStream);
         }
 
         return new CompoundFormula(connector, formulas);
     }
 
-    private AtomicFormula parseAtomicFormula(BitInputStream bitInputStream) throws IOException {
-        int key = bitInputStream.getNext(KEY_BITS);
-        int operator = bitInputStream.getNext(OPERATOR_BITS);
+    private AtomicFormula parseAtomicFormula(BitTrackedInputStream bitTrackedInputStream)
+            throws IOException {
+        int key = bitTrackedInputStream.getNext(KEY_BITS);
+        int operator = bitTrackedInputStream.getNext(OPERATOR_BITS);
 
         switch (key) {
             case AtomicFormula.PACKAGE_NAME:
             case AtomicFormula.APP_CERTIFICATE:
             case AtomicFormula.INSTALLER_NAME:
             case AtomicFormula.INSTALLER_CERTIFICATE:
-                boolean isHashedValue = bitInputStream.getNext(IS_HASHED_BITS) == 1;
-                int valueSize = bitInputStream.getNext(VALUE_SIZE_BITS);
-                String stringValue = getStringValue(bitInputStream, valueSize, isHashedValue);
+                boolean isHashedValue = bitTrackedInputStream.getNext(IS_HASHED_BITS) == 1;
+                int valueSize = bitTrackedInputStream.getNext(VALUE_SIZE_BITS);
+                String stringValue = getStringValue(bitTrackedInputStream, valueSize,
+                        isHashedValue);
                 return new AtomicFormula.StringAtomicFormula(key, stringValue, isHashedValue);
             case AtomicFormula.VERSION_CODE:
-                int intValue = getIntValue(bitInputStream);
+                int intValue = getIntValue(bitTrackedInputStream);
                 return new AtomicFormula.IntAtomicFormula(key, operator, intValue);
             case AtomicFormula.PRE_INSTALLED:
-                boolean booleanValue = getBooleanValue(bitInputStream);
+                boolean booleanValue = getBooleanValue(bitTrackedInputStream);
                 return new AtomicFormula.BooleanAtomicFormula(key, booleanValue);
             default:
                 throw new IllegalArgumentException(String.format("Unknown key: %d", key));
diff --git a/services/core/java/com/android/server/integrity/parser/RuleParser.java b/services/core/java/com/android/server/integrity/parser/RuleParser.java
index 81783d5..a8e9f61 100644
--- a/services/core/java/com/android/server/integrity/parser/RuleParser.java
+++ b/services/core/java/com/android/server/integrity/parser/RuleParser.java
@@ -28,5 +28,6 @@
     List<Rule> parse(byte[] ruleBytes) throws RuleParseException;
 
     /** Parse rules from an input stream. */
-    List<Rule> parse(InputStream inputStream) throws RuleParseException;
+    List<Rule> parse(InputStream inputStream, List<RuleIndexRange> ruleIndexRanges)
+            throws RuleParseException;
 }
diff --git a/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java b/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java
index d405583..497be84 100644
--- a/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java
+++ b/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java
@@ -62,7 +62,8 @@
     }
 
     @Override
-    public List<Rule> parse(InputStream inputStream) throws RuleParseException {
+    public List<Rule> parse(InputStream inputStream, List<RuleIndexRange> indexRanges)
+            throws RuleParseException {
         try {
             XmlPullParser xmlPullParser = Xml.newPullParser();
             xmlPullParser.setInput(inputStream, StandardCharsets.UTF_8.name());
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
index b8791c3..f964d4c 100644
--- a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
+++ b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
@@ -42,6 +42,7 @@
 import com.android.internal.util.Preconditions;
 import com.android.server.integrity.IntegrityUtils;
 import com.android.server.integrity.model.BitOutputStream;
+import com.android.server.integrity.model.ByteTrackedOutputStream;
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -109,7 +110,8 @@
     }
 
     private void serializeRuleFileMetadata(Optional<Integer> formatVersion,
-            ByteTrackedOutputStream outputStream) throws IOException {
+            ByteTrackedOutputStream outputStream)
+            throws IOException {
         int formatVersionValue = formatVersion.orElse(DEFAULT_FORMAT_VERSION);
 
         BitOutputStream bitOutputStream = new BitOutputStream();
diff --git a/services/core/java/com/android/server/location/AbstractLocationProvider.java b/services/core/java/com/android/server/location/AbstractLocationProvider.java
index ccfc98e..ed6a759 100644
--- a/services/core/java/com/android/server/location/AbstractLocationProvider.java
+++ b/services/core/java/com/android/server/location/AbstractLocationProvider.java
@@ -16,11 +16,11 @@
 
 package com.android.server.location;
 
+import android.annotation.Nullable;
 import android.content.Context;
 import android.location.Location;
 import android.os.Binder;
 import android.os.Bundle;
-import android.os.WorkSource;
 
 import com.android.internal.location.ProviderProperties;
 import com.android.internal.location.ProviderRequest;
@@ -29,127 +29,336 @@
 import java.io.PrintWriter;
 import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.UnaryOperator;
 
 /**
- * Location Manager's interface for location providers. Always starts as disabled.
+ * Base class for all location providers.
  *
  * @hide
  */
 public abstract class AbstractLocationProvider {
 
     /**
-     * Interface for communicating from a location provider back to the location service.
+     * Interface for listening to location providers.
      */
-    public interface LocationProviderManager {
+    public interface Listener {
 
         /**
-         * May be called to inform the location service of a change in this location provider's
-         * enabled/disabled state.
+         * Called when a provider's state changes. May be invoked from any thread. Will be
+         * invoked with a cleared binder identity.
          */
-        void onSetEnabled(boolean enabled);
+        void onStateChanged(State oldState, State newState);
 
         /**
-         * May be called to inform the location service of a change in this location provider's
-         * properties.
-         */
-        void onSetProperties(ProviderProperties properties);
-
-        /**
-         * May be called to inform the location service that this provider has a new location
-         * available.
+         * Called when a provider has a new location available. May be invoked from any thread. Will
+         * be invoked with a cleared binder identity.
          */
         void onReportLocation(Location location);
 
         /**
-         * May be called to inform the location service that this provider has a new location
-         * available.
+         * Called when a provider has a new location available. May be invoked from any thread. Will
+         * be invoked with a cleared binder identity.
          */
         void onReportLocation(List<Location> locations);
     }
 
-    protected final Context mContext;
-    private final LocationProviderManager mLocationProviderManager;
+    /**
+     * Holds a representation of the public state of a provider.
+     */
+    public static final class State {
 
-    protected AbstractLocationProvider(
-            Context context, LocationProviderManager locationProviderManager) {
+        /**
+         * Default state value for a location provider that is disabled with no properties and an
+         * empty provider package list.
+         */
+        public static final State EMPTY_STATE = new State(false, null,
+                Collections.emptySet());
+
+        /**
+         * The provider's enabled state.
+         */
+        public final boolean enabled;
+
+        /**
+         * The provider's properties.
+         */
+        @Nullable public final ProviderProperties properties;
+
+        /**
+         * The provider's package name list - provider packages may be afforded special privileges.
+         */
+        public final Set<String> providerPackageNames;
+
+        private State(boolean enabled, ProviderProperties properties,
+                Set<String> providerPackageNames) {
+            this.enabled = enabled;
+            this.properties = properties;
+            this.providerPackageNames = Objects.requireNonNull(providerPackageNames);
+        }
+
+        private State withEnabled(boolean enabled) {
+            if (enabled == this.enabled) {
+                return this;
+            } else {
+                return new State(enabled, properties, providerPackageNames);
+            }
+        }
+
+        private State withProperties(ProviderProperties properties) {
+            if (properties.equals(this.properties)) {
+                return this;
+            } else {
+                return new State(enabled, properties, providerPackageNames);
+            }
+        }
+
+        private State withProviderPackageNames(Set<String> providerPackageNames) {
+            if (providerPackageNames.equals(this.providerPackageNames)) {
+                return this;
+            } else {
+                return new State(enabled, properties, providerPackageNames);
+            }
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (!(o instanceof State)) {
+                return false;
+            }
+            State state = (State) o;
+            return enabled == state.enabled && properties == state.properties
+                    && providerPackageNames.equals(state.providerPackageNames);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(enabled, properties, providerPackageNames);
+        }
+    }
+
+    // combines listener and state information so that they can be updated atomically with respect
+    // to each other and an ordering established.
+    private static class InternalState {
+        @Nullable public final Listener listener;
+        public final State state;
+
+        private InternalState(@Nullable Listener listener, State state) {
+            this.listener = listener;
+            this.state = state;
+        }
+
+        private InternalState withListener(Listener listener) {
+            if (listener == this.listener) {
+                return this;
+            } else {
+                return new InternalState(listener, state);
+            }
+        }
+
+        private InternalState withState(State state) {
+            if (state.equals(this.state)) {
+                return this;
+            } else {
+                return new InternalState(listener, state);
+            }
+        }
+
+        private InternalState withState(UnaryOperator<State> operator) {
+            return withState(operator.apply(state));
+        }
+    }
+
+    protected final Context mContext;
+    protected final Executor mExecutor;
+
+    // we use a lock-free implementation to update state to ensure atomicity between updating the
+    // provider state and setting the listener, so that the state updates a listener sees are
+    // consistent with when the listener was set (a listener should not see any updates that occur
+    // before it was set, and should not miss any updates that occur after it was set).
+    private final AtomicReference<InternalState> mInternalState;
+
+    protected AbstractLocationProvider(Context context, Executor executor) {
+        this(context, executor, Collections.singleton(context.getPackageName()));
+    }
+
+    protected AbstractLocationProvider(Context context, Executor executor,
+            Set<String> packageNames) {
         mContext = context;
-        mLocationProviderManager = locationProviderManager;
+        mExecutor = executor;
+        mInternalState = new AtomicReference<>(
+                new InternalState(null, State.EMPTY_STATE.withProviderPackageNames(packageNames)));
     }
 
     /**
-     * Call this method to report a change in provider enabled/disabled status. May be called from
-     * any thread.
+     * Sets the listener and returns the state at the moment the listener was set. The listener can
+     * expect to receive all state updates from after this point.
+     */
+    State setListener(@Nullable Listener listener) {
+        return mInternalState.updateAndGet(
+                internalState -> internalState.withListener(listener)).state;
+    }
+
+    /**
+     * Retrieves the state of the provider.
+     */
+    State getState() {
+        return mInternalState.get().state;
+    }
+
+    /**
+     * Sets the state of the provider to the new state.
+     */
+    void setState(State newState) {
+        InternalState oldInternalState = mInternalState.getAndUpdate(
+                internalState -> internalState.withState(newState));
+        if (newState.equals(oldInternalState.state)) {
+            return;
+        }
+
+        // we know that we only updated the state, so the listener for the old state is the same as
+        // the listener for the new state.
+        if (oldInternalState.listener != null) {
+            long identity = Binder.clearCallingIdentity();
+            try {
+                oldInternalState.listener.onStateChanged(oldInternalState.state, newState);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+    }
+
+    private void setState(UnaryOperator<State> operator) {
+        InternalState oldInternalState = mInternalState.getAndUpdate(
+                internalState -> internalState.withState(operator));
+
+        // recreate the new state from our knowledge of the old state - unfortunately may result in
+        // an extra allocation, but oh well...
+        State newState = operator.apply(oldInternalState.state);
+
+        if (newState.equals(oldInternalState.state)) {
+            return;
+        }
+
+        // we know that we only updated the state, so the listener for the old state is the same as
+        // the listener for the new state.
+        if (oldInternalState.listener != null) {
+            long identity = Binder.clearCallingIdentity();
+            try {
+                oldInternalState.listener.onStateChanged(oldInternalState.state, newState);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+    }
+
+    /**
+     * The current enabled state of this provider.
+     */
+    protected boolean isEnabled() {
+        return mInternalState.get().state.enabled;
+    }
+
+    /**
+     * The current provider properties of this provider.
+     */
+    @Nullable
+    protected ProviderProperties getProperties() {
+        return mInternalState.get().state.properties;
+    }
+
+    /**
+     * The current package set of this provider.
+     */
+    protected Set<String> getProviderPackages() {
+        return mInternalState.get().state.providerPackageNames;
+    }
+
+    /**
+     * Call this method to report a change in provider enabled/disabled status.
      */
     protected void setEnabled(boolean enabled) {
-        long identity = Binder.clearCallingIdentity();
-        try {
-            mLocationProviderManager.onSetEnabled(enabled);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
+        setState(state -> state.withEnabled(enabled));
     }
 
     /**
-     * Call this method to report a change in provider properties. May be called from
-     * any thread.
+     * Call this method to report a change in provider properties.
      */
     protected void setProperties(ProviderProperties properties) {
-        long identity = Binder.clearCallingIdentity();
-        try {
-            mLocationProviderManager.onSetProperties(properties);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
+        setState(state -> state.withProperties(properties));
     }
 
     /**
-     * Call this method to report a new location. May be called from any thread.
+     * Call this method to report a change in provider packages.
+     */
+    protected void setPackageNames(Set<String> packageNames) {
+        setState(state -> state.withProviderPackageNames(packageNames));
+    }
+
+    /**
+     * Call this method to report a new location.
      */
     protected void reportLocation(Location location) {
-        long identity = Binder.clearCallingIdentity();
-        try {
-            mLocationProviderManager.onReportLocation(location);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
+        Listener listener = mInternalState.get().listener;
+        if (listener != null) {
+            long identity = Binder.clearCallingIdentity();
+            try {
+                listener.onReportLocation(location);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
         }
     }
 
     /**
-     * Call this method to report a new location. May be called from any thread.
+     * Call this method to report a new location.
      */
     protected void reportLocation(List<Location> locations) {
-        long identity = Binder.clearCallingIdentity();
-        try {
-            mLocationProviderManager.onReportLocation(locations);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
+        Listener listener = mInternalState.get().listener;
+        if (listener != null) {
+            long identity = Binder.clearCallingIdentity();
+            try {
+                listener.onReportLocation(locations);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
         }
     }
 
     /**
-     * Invoked by the location service to return a list of packages currently associated with this
-     * provider. May be called from any thread.
+     * Sets a new request and worksource for the provider.
      */
-    public List<String> getProviderPackages() {
-        return Collections.singletonList(mContext.getPackageName());
+    public final void setRequest(ProviderRequest request) {
+        // all calls into the provider must be moved onto the provider thread to prevent deadlock
+        mExecutor.execute(() -> onSetRequest(request));
     }
 
     /**
-     * Invoked by the location service to deliver a new request for fulfillment to the provider.
-     * Replaces any previous requests completely. Will always be invoked from the location service
-     * thread with a cleared binder identity.
+     * Always invoked on the provider executor.
      */
-    public abstract void onSetRequest(ProviderRequest request, WorkSource source);
+    protected abstract void onSetRequest(ProviderRequest request);
 
     /**
-     * Invoked by the location service to deliver a custom command to this provider. Will always be
-     * invoked from the location service thread with a cleared binder identity.
+     * Sends an extra command to the provider for it to interpret as it likes.
      */
-    public void onSendExtraCommand(int uid, int pid, String command, Bundle extras) {}
+    public final void sendExtraCommand(int uid, int pid, String command, Bundle extras) {
+        // all calls into the provider must be moved onto the provider thread to prevent deadlock
+        mExecutor.execute(() -> onExtraCommand(uid, pid, command, extras));
+    }
 
     /**
-     * Invoked by the location service to dump debug or log information. May be invoked from any
-     * thread.
+     * Always invoked on the provider executor.
+     */
+    protected void onExtraCommand(int uid, int pid, String command, Bundle extras) {}
+
+    /**
+     * Dumps debug or log information. May be invoked from any thread.
      */
     public abstract void dump(FileDescriptor fd, PrintWriter pw, String[] args);
 }
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index d8561b6..15cf190 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -43,6 +43,7 @@
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.Looper;
 import android.os.Message;
 import android.os.PersistableBundle;
@@ -113,8 +114,15 @@
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
 
     private static final ProviderProperties PROPERTIES = new ProviderProperties(
-            true, true, false, false, true, true, true,
-            Criteria.POWER_HIGH, Criteria.ACCURACY_FINE);
+            /* requiresNetwork = */false,
+            /* requiresSatellite = */true,
+            /* requiresCell = */false,
+            /* hasMonetaryCost = */false,
+            /* supportAltitude = */true,
+            /* supportsSpeed = */true,
+            /* supportsBearing = */true,
+            Criteria.POWER_HIGH,
+            Criteria.ACCURACY_FINE);
 
     // these need to match GnssPositionMode enum in IGnss.hal
     private static final int GPS_POSITION_MODE_STANDALONE = 0;
@@ -616,13 +624,12 @@
         }
     }
 
-    public GnssLocationProvider(Context context, LocationProviderManager locationProviderManager,
-            Looper looper) {
-        super(context, locationProviderManager);
+    public GnssLocationProvider(Context context, Handler handler) {
+        super(context, new HandlerExecutor(handler));
 
         ensureInitialized();
 
-        mLooper = looper;
+        mLooper = handler.getLooper();
 
         // Create a wake lock
         mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
@@ -639,7 +646,7 @@
         mTimeoutIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(ALARM_TIMEOUT), 0);
 
         mNetworkConnectivityHandler = new GnssNetworkConnectivityHandler(context,
-                GnssLocationProvider.this::onNetworkAvailable, looper);
+                GnssLocationProvider.this::onNetworkAvailable, mLooper);
 
         // App ops service to keep track of who is accessing the GPS
         mAppOps = mContext.getSystemService(AppOpsManager.class);
@@ -649,7 +656,7 @@
                 BatteryStats.SERVICE_NAME));
 
         // Construct internal handler
-        mHandler = new ProviderHandler(looper);
+        mHandler = new ProviderHandler(mLooper);
 
         // Load GPS configuration and register listeners in the background:
         // some operations, such as opening files and registering broadcast receivers, can take a
@@ -693,10 +700,10 @@
         };
 
         mGnssMetrics = new GnssMetrics(mBatteryStats);
-        mNtpTimeHelper = new NtpTimeHelper(mContext, looper, this);
+        mNtpTimeHelper = new NtpTimeHelper(mContext, mLooper, this);
         GnssSatelliteBlacklistHelper gnssSatelliteBlacklistHelper =
                 new GnssSatelliteBlacklistHelper(mContext,
-                        looper, this);
+                        mLooper, this);
         mHandler.post(gnssSatelliteBlacklistHelper::updateSatelliteBlacklist);
         mGnssBatchingProvider = new GnssBatchingProvider();
         mGnssGeofenceProvider = new GnssGeofenceProvider();
@@ -1047,8 +1054,8 @@
     }
 
     @Override
-    public void onSetRequest(ProviderRequest request, WorkSource source) {
-        sendMessage(SET_REQUEST, 0, new GpsRequest(request, source));
+    public void onSetRequest(ProviderRequest request) {
+        sendMessage(SET_REQUEST, 0, new GpsRequest(request, request.workSource));
     }
 
     private void handleSetRequest(ProviderRequest request, WorkSource source) {
@@ -1185,7 +1192,7 @@
     }
 
     @Override
-    public void onSendExtraCommand(int uid, int pid, String command, Bundle extras) {
+    public void onExtraCommand(int uid, int pid, String command, Bundle extras) {
 
         long identity = Binder.clearCallingIdentity();
         try {
@@ -2064,10 +2071,8 @@
         }
 
         /**
-         * This method is bound to {@link #GnssLocationProvider(Context, LocationProviderManager,
-         * Looper)}.
-         * It is in charge of loading properties and registering for events that will be posted to
-         * this handler.
+         * This method is bound to the constructor. It is in charge of loading properties and
+         * registering for events that will be posted to this handler.
          */
         private void handleInitialize() {
             // class_init_native() already initializes the GNSS service handle during class loading.
diff --git a/services/core/java/com/android/server/location/LocationProviderProxy.java b/services/core/java/com/android/server/location/LocationProviderProxy.java
index 694f149..8a149af 100644
--- a/services/core/java/com/android/server/location/LocationProviderProxy.java
+++ b/services/core/java/com/android/server/location/LocationProviderProxy.java
@@ -23,12 +23,13 @@
 import android.content.pm.PackageManager;
 import android.location.Location;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.os.WorkSource;
+import android.util.ArraySet;
 import android.util.Log;
 
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.location.ILocationProvider;
 import com.android.internal.location.ILocationProviderManager;
 import com.android.internal.location.ProviderProperties;
@@ -39,10 +40,8 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
  * Proxy for ILocationProvider implementations.
@@ -52,59 +51,64 @@
     private static final String TAG = "LocationProviderProxy";
     private static final boolean D = LocationManagerService.D;
 
-    // used to ensure that updates to mProviderPackages are atomic
-    private final Object mProviderPackagesLock = new Object();
-
-    // used to ensure that updates to mRequest and mWorkSource are atomic
-    private final Object mRequestLock = new Object();
+    private static final int MAX_ADDITIONAL_PACKAGES = 2;
 
     private final ILocationProviderManager.Stub mManager = new ILocationProviderManager.Stub() {
         // executed on binder thread
         @Override
         public void onSetAdditionalProviderPackages(List<String> packageNames) {
-            LocationProviderProxy.this.onSetAdditionalProviderPackages(packageNames);
+            int maxCount = Math.min(MAX_ADDITIONAL_PACKAGES, packageNames.size()) + 1;
+            ArraySet<String> allPackages = new ArraySet<>(maxCount);
+            allPackages.add(mServiceWatcher.getCurrentPackageName());
+            for (String packageName : packageNames) {
+                if (packageNames.size() >= maxCount) {
+                    return;
+                }
+
+                try {
+                    mContext.getPackageManager().getPackageInfo(packageName, MATCH_SYSTEM_ONLY);
+                    allPackages.add(packageName);
+                } catch (PackageManager.NameNotFoundException e) {
+                    Log.w(TAG, mServiceWatcher + " specified unknown additional provider package: "
+                            + packageName);
+                }
+            }
+
+            setPackageNames(allPackages);
         }
 
         // executed on binder thread
         @Override
         public void onSetEnabled(boolean enabled) {
-            LocationProviderProxy.this.setEnabled(enabled);
+            setEnabled(enabled);
         }
 
         // executed on binder thread
         @Override
         public void onSetProperties(ProviderProperties properties) {
-            LocationProviderProxy.this.setProperties(properties);
+            setProperties(properties);
         }
 
         // executed on binder thread
         @Override
         public void onReportLocation(Location location) {
-            LocationProviderProxy.this.reportLocation(location);
+            reportLocation(location);
         }
     };
 
     private final ServiceWatcher mServiceWatcher;
 
-    @GuardedBy("mProviderPackagesLock")
-    private final CopyOnWriteArrayList<String> mProviderPackages = new CopyOnWriteArrayList<>();
-
-    @GuardedBy("mRequestLock")
-    @Nullable
-    private ProviderRequest mRequest;
-    @GuardedBy("mRequestLock")
-    private WorkSource mWorkSource;
+    @Nullable private ProviderRequest mRequest;
 
     /**
      * Creates a new LocationProviderProxy and immediately begins binding to the best applicable
      * service.
      */
     @Nullable
-    public static LocationProviderProxy createAndBind(
-            Context context, LocationProviderManager locationProviderManager, String action,
+    public static LocationProviderProxy createAndBind(Context context, String action,
             int overlaySwitchResId, int defaultServicePackageNameResId,
             int initialPackageNamesResId) {
-        LocationProviderProxy proxy = new LocationProviderProxy(context, locationProviderManager,
+        LocationProviderProxy proxy = new LocationProviderProxy(context, FgThread.getHandler(),
                 action, overlaySwitchResId, defaultServicePackageNameResId,
                 initialPackageNamesResId);
         if (proxy.bind()) {
@@ -114,14 +118,13 @@
         }
     }
 
-    private LocationProviderProxy(Context context, LocationProviderManager locationProviderManager,
-            String action, int overlaySwitchResId, int defaultServicePackageNameResId,
+    private LocationProviderProxy(Context context, Handler handler, String action,
+            int overlaySwitchResId, int defaultServicePackageNameResId,
             int initialPackageNamesResId) {
-        super(context, locationProviderManager);
+        super(context, new HandlerExecutor(handler), Collections.emptySet());
 
         mServiceWatcher = new ServiceWatcher(context, TAG, action, overlaySwitchResId,
-                defaultServicePackageNameResId, initialPackageNamesResId,
-                FgThread.getHandler()) {
+                defaultServicePackageNameResId, initialPackageNamesResId, handler) {
 
             @Override
             protected void onBind() {
@@ -130,14 +133,11 @@
 
             @Override
             protected void onUnbind() {
-                resetProviderPackages(Collections.emptyList());
-                setEnabled(false);
-                setProperties(null);
+                setState(State.EMPTY_STATE);
             }
         };
 
         mRequest = null;
-        mWorkSource = new WorkSource();
     }
 
     private boolean bind() {
@@ -148,77 +148,34 @@
         ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
         if (D) Log.d(TAG, "applying state to connected service " + mServiceWatcher);
 
-        resetProviderPackages(Collections.emptyList());
+        setPackageNames(Collections.singleton(mServiceWatcher.getCurrentPackageName()));
 
         service.setLocationProviderManager(mManager);
 
-        synchronized (mRequestLock) {
-            if (mRequest != null) {
-                service.setRequest(mRequest, mWorkSource);
-            }
+        if (mRequest != null) {
+            service.setRequest(mRequest, mRequest.workSource);
         }
     }
 
     @Override
-    public List<String> getProviderPackages() {
-        synchronized (mProviderPackagesLock) {
-            return mProviderPackages;
-        }
-    }
-
-    @Override
-    public void onSetRequest(ProviderRequest request, WorkSource source) {
-        synchronized (mRequestLock) {
-            mRequest = request;
-            mWorkSource = source;
-        }
+    public void onSetRequest(ProviderRequest request) {
         mServiceWatcher.runOnBinder(binder -> {
+            mRequest = request;
             ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
-            service.setRequest(request, source);
+            service.setRequest(request, request.workSource);
         });
     }
 
     @Override
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("service=" + mServiceWatcher);
-        synchronized (mProviderPackagesLock) {
-            if (mProviderPackages.size() > 1) {
-                pw.println("additional packages=" + mProviderPackages);
-            }
-        }
-    }
-
-    @Override
-    public void onSendExtraCommand(int uid, int pid, String command, Bundle extras) {
+    public void onExtraCommand(int uid, int pid, String command, Bundle extras) {
         mServiceWatcher.runOnBinder(binder -> {
             ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
             service.sendExtraCommand(command, extras);
         });
     }
 
-    private void onSetAdditionalProviderPackages(List<String> packageNames) {
-        resetProviderPackages(packageNames);
-    }
-
-    private void resetProviderPackages(List<String> additionalPackageNames) {
-        ArrayList<String> permittedPackages = new ArrayList<>(additionalPackageNames.size());
-        for (String packageName : additionalPackageNames) {
-            try {
-                mContext.getPackageManager().getPackageInfo(packageName, MATCH_SYSTEM_ONLY);
-                permittedPackages.add(packageName);
-            } catch (PackageManager.NameNotFoundException e) {
-                Log.w(TAG, mServiceWatcher + " specified unknown additional provider package: "
-                        + packageName);
-            }
-        }
-
-        synchronized (mProviderPackagesLock) {
-            mProviderPackages.clear();
-            String myPackage = mServiceWatcher.getCurrentPackageName();
-            if (myPackage != null) {
-                mProviderPackages.add(myPackage);
-                mProviderPackages.addAll(permittedPackages);
-            }
-        }
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("service=" + mServiceWatcher);
     }
 }
diff --git a/services/core/java/com/android/server/location/LocationSettingsStore.java b/services/core/java/com/android/server/location/LocationSettingsStore.java
index f625452..0e8720e 100644
--- a/services/core/java/com/android/server/location/LocationSettingsStore.java
+++ b/services/core/java/com/android/server/location/LocationSettingsStore.java
@@ -16,6 +16,8 @@
 
 package com.android.server.location;
 
+import static android.location.LocationManager.FUSED_PROVIDER;
+import static android.location.LocationManager.PASSIVE_PROVIDER;
 import static android.provider.Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS;
 import static android.provider.Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST;
 import static android.provider.Settings.Global.LOCATION_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS;
@@ -28,6 +30,7 @@
 import android.content.Context;
 import android.database.ContentObserver;
 import android.net.Uri;
+import android.os.Binder;
 import android.os.Handler;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -248,6 +251,9 @@
                 DEFAULT_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS);
     }
 
+    /**
+     * Retrieve maximum age of the last location.
+     */
     public long getMaxLastLocationAgeMs() {
         return Settings.Global.getLong(
                 mContext.getContentResolver(),
@@ -256,6 +262,29 @@
     }
 
     /**
+     * Set a value for the deprecated LOCATION_PROVIDERS_ALLOWED setting. This is used purely for
+     * backwards compatibility for old clients, and may be removed in the future.
+     */
+    public void setLocationProviderAllowed(String provider, boolean enabled, int userId) {
+        // fused and passive provider never get public updates for legacy reasons
+        if (FUSED_PROVIDER.equals(provider) || PASSIVE_PROVIDER.equals(provider)) {
+            return;
+        }
+
+        long identity = Binder.clearCallingIdentity();
+        try {
+            // update LOCATION_PROVIDERS_ALLOWED for best effort backwards compatibility
+            Settings.Secure.putStringForUser(
+                    mContext.getContentResolver(),
+                    Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
+                    (enabled ? "+" : "-") + provider,
+                    userId);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    /**
      * Dump info for debugging.
      */
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
diff --git a/services/core/java/com/android/server/location/MockProvider.java b/services/core/java/com/android/server/location/MockProvider.java
index 472876b..60c9fc1 100644
--- a/services/core/java/com/android/server/location/MockProvider.java
+++ b/services/core/java/com/android/server/location/MockProvider.java
@@ -19,7 +19,6 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.location.Location;
-import android.os.WorkSource;
 
 import com.android.internal.location.ProviderProperties;
 import com.android.internal.location.ProviderRequest;
@@ -34,41 +33,33 @@
  */
 public class MockProvider extends AbstractLocationProvider {
 
-    private boolean mEnabled;
     @Nullable private Location mLocation;
 
-    public MockProvider(Context context,
-            LocationProviderManager locationProviderManager, ProviderProperties properties) {
-        super(context, locationProviderManager);
-
-        mEnabled = true;
-        mLocation = null;
-
+    public MockProvider(Context context, ProviderProperties properties) {
+        // using a direct executor is only acceptable because this class is so simple it is trivial
+        // to verify that it does not acquire any locks or re-enter LMS from callbacks
+        super(context, Runnable::run);
         setProperties(properties);
     }
 
     /** Sets the enabled state of this mock provider. */
-    public void setEnabled(boolean enabled) {
-        mEnabled = enabled;
-        super.setEnabled(enabled);
+    public void setProviderEnabled(boolean enabled) {
+        setEnabled(enabled);
     }
 
     /** Sets the location to report for this mock provider. */
-    public void setLocation(Location l) {
-        mLocation = new Location(l);
-        if (!mLocation.isFromMockProvider()) {
-            mLocation.setIsFromMockProvider(true);
-        }
-        if (mEnabled) {
-            reportLocation(mLocation);
-        }
+    public void setProviderLocation(Location l) {
+        Location location = new Location(l);
+        location.setIsFromMockProvider(true);
+        mLocation = location;
+        reportLocation(location);
     }
 
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("last location=" + mLocation);
+        pw.println("last mock location=" + mLocation);
     }
 
     @Override
-    public void onSetRequest(ProviderRequest request, WorkSource source) {}
+    public void onSetRequest(ProviderRequest request) {}
 }
diff --git a/services/core/java/com/android/server/location/MockableLocationProvider.java b/services/core/java/com/android/server/location/MockableLocationProvider.java
new file mode 100644
index 0000000..f50dfe7
--- /dev/null
+++ b/services/core/java/com/android/server/location/MockableLocationProvider.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2019 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.location;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.location.Location;
+import android.os.Bundle;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.location.ProviderRequest;
+import com.android.internal.util.Preconditions;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Represents a location provider that may switch between a mock implementation and a real
+ * implementation. Requires owners to provide a lock object that will be used internally and held
+ * for the duration of all listener callbacks. Owners are reponsible for ensuring this cannot lead
+ * to deadlock.
+ *
+ * In order to ensure deadlock does not occur, the owner must validate that the ONLY lock which can
+ * be held BOTH when calling into this class AND when receiving a callback from this class is the
+ * lock given to this class via the constructor. Holding any other lock is ok as long as there is no
+ * possibility that it can be obtained within both codepaths.
+ *
+ * Holding the given lock guarantees atomicity of any operations on this class for the duration.
+ *
+ * @hide
+ */
+public class MockableLocationProvider extends AbstractLocationProvider {
+
+    private final Object mOwnerLock;
+
+    @GuardedBy("mOwnerLock")
+    @Nullable private AbstractLocationProvider mProvider;
+    @GuardedBy("mOwnerLock")
+    @Nullable private AbstractLocationProvider mRealProvider;
+    @GuardedBy("mOwnerLock")
+    @Nullable private MockProvider mMockProvider;
+
+    @GuardedBy("mOwnerLock")
+    private ProviderRequest mRequest;
+
+    /**
+     * The given lock object will be held any time the listener is invoked, and may also be acquired
+     * and released during the course of invoking any public methods. Holding the given lock ensures
+     * that provider state cannot change except as result of an explicit call by the owner of the
+     * lock into this class. The client is reponsible for ensuring this cannot cause deadlock.
+     *
+     * The client should expect that it may being to receive callbacks as soon as this constructor
+     * is invoked.
+     */
+    public MockableLocationProvider(Context context, Object ownerLock, Listener listener) {
+        // using a direct executor is acceptable because all inbound calls are delegated to the
+        // actual provider implementations which will use their own executors
+        super(context, Runnable::run, Collections.emptySet());
+        mOwnerLock = ownerLock;
+        mRequest = ProviderRequest.EMPTY_REQUEST;
+
+        setListener(listener);
+    }
+
+    /**
+     * Returns the current provider implementation. May be null if there is no current
+     * implementation.
+     */
+    @Nullable
+    public AbstractLocationProvider getProvider() {
+        synchronized (mOwnerLock) {
+            return mProvider;
+        }
+    }
+
+    /**
+     * Sets the real provider implementation, replacing any previous real provider implementation.
+     * May cause an inline invocation of {@link Listener#onStateChanged(State, State)} if this
+     * results in a state change.
+     */
+    public void setRealProvider(@Nullable AbstractLocationProvider provider) {
+        synchronized (mOwnerLock) {
+            if (mRealProvider == provider) {
+                return;
+            }
+
+            mRealProvider = provider;
+            if (!isMock()) {
+                setProviderLocked(mRealProvider);
+            }
+        }
+    }
+
+    /**
+     * Sets the mock provider implementation, replacing any previous mock provider implementation.
+     * Mock implementations are always used instead of real implementations if set. May cause an
+     * inline invocation of {@link Listener#onStateChanged(State, State)} if this results in a
+     * state change.
+     */
+    public void setMockProvider(@Nullable MockProvider provider) {
+        synchronized (mOwnerLock) {
+            if (mMockProvider == provider) {
+                return;
+            }
+
+            mMockProvider = provider;
+            if (mMockProvider != null) {
+                setProviderLocked(mMockProvider);
+            } else {
+                setProviderLocked(mRealProvider);
+            }
+        }
+    }
+
+    @GuardedBy("mOwnerLock")
+    private void setProviderLocked(@Nullable AbstractLocationProvider provider) {
+        if (mProvider == provider) {
+            return;
+        }
+
+        AbstractLocationProvider oldProvider = mProvider;
+        mProvider = provider;
+
+        if (oldProvider != null) {
+            // do this after switching the provider - so even if the old provider is using a direct
+            // executor, if it re-enters this class within setRequest(), it will be ignored
+            oldProvider.setListener(null);
+            oldProvider.setRequest(ProviderRequest.EMPTY_REQUEST);
+        }
+
+        State newState;
+        if (mProvider != null) {
+            newState = mProvider.setListener(new ListenerWrapper(mProvider));
+        } else {
+            newState = State.EMPTY_STATE;
+        }
+
+        ProviderRequest oldRequest = mRequest;
+        setState(newState);
+
+        if (mProvider != null && oldRequest == mRequest) {
+            mProvider.setRequest(mRequest);
+        }
+    }
+
+    /**
+     * Returns true if the current active provider implementation is the mock implementation, and
+     * false otherwise.
+     */
+    public boolean isMock() {
+        synchronized (mOwnerLock) {
+            return mMockProvider != null && mProvider == mMockProvider;
+        }
+    }
+
+    /**
+     * Sets the mock provider implementation's enabled state. Will throw an exception if the mock
+     * provider is not currently the active implementation.
+     */
+    public void setMockProviderEnabled(boolean enabled) {
+        synchronized (mOwnerLock) {
+            Preconditions.checkState(isMock());
+            mMockProvider.setProviderEnabled(enabled);
+        }
+    }
+    /**
+     * Sets the mock provider implementation's location. Will throw an exception if the mock
+     * provider is not currently the active implementation.
+     */
+    public void setMockProviderLocation(Location location) {
+        synchronized (mOwnerLock) {
+            Preconditions.checkState(isMock());
+            mMockProvider.setProviderLocation(location);
+        }
+    }
+
+    @Override
+    public State getState() {
+        return super.getState();
+    }
+
+    /**
+     * Returns the current location request.
+     */
+    public ProviderRequest getCurrentRequest() {
+        synchronized (mOwnerLock) {
+            return mRequest;
+        }
+    }
+
+    protected void onSetRequest(ProviderRequest request) {
+        synchronized (mOwnerLock) {
+            if (request == mRequest) {
+                return;
+            }
+
+            mRequest = request;
+
+            if (mProvider != null) {
+                mProvider.setRequest(request);
+            }
+        }
+    }
+
+    protected void onExtraCommand(int uid, int pid, String command, Bundle extras) {
+        synchronized (mOwnerLock) {
+            if (mProvider != null) {
+                mProvider.sendExtraCommand(uid, pid, command, extras);
+            }
+        }
+    }
+
+    /**
+     * Dumps the current provider implementation.
+     */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        AbstractLocationProvider provider;
+        synchronized (mOwnerLock) {
+            provider = mProvider;
+            pw.println("request=" + mRequest);
+        }
+
+        if (provider != null) {
+            // dump outside the lock in case the provider wants to acquire its own locks, and since
+            // the default provider dump behavior does not move things onto the provider thread...
+            provider.dump(fd, pw, args);
+        }
+    }
+
+    // ensures that callbacks from the incorrect provider are never visible to clients - this
+    // requires holding the owner's lock for the duration of the callback
+    private class ListenerWrapper implements Listener {
+
+        private final AbstractLocationProvider mListenerProvider;
+
+        private ListenerWrapper(AbstractLocationProvider listenerProvider) {
+            mListenerProvider = listenerProvider;
+        }
+
+        @Override
+        public final void onStateChanged(State oldState, State newState) {
+            synchronized (mOwnerLock) {
+                if (mListenerProvider != mProvider) {
+                    return;
+                }
+
+                setState(newState);
+            }
+        }
+
+        @Override
+        public final void onReportLocation(Location location) {
+            synchronized (mOwnerLock) {
+                if (mListenerProvider != mProvider) {
+                    return;
+                }
+
+                reportLocation(location);
+            }
+        }
+
+        @Override
+        public final void onReportLocation(List<Location> locations) {
+            synchronized (mOwnerLock) {
+                if (mListenerProvider != mProvider) {
+                    return;
+                }
+
+                reportLocation(locations);
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/location/PassiveProvider.java b/services/core/java/com/android/server/location/PassiveProvider.java
index 639b1eb..b338770 100644
--- a/services/core/java/com/android/server/location/PassiveProvider.java
+++ b/services/core/java/com/android/server/location/PassiveProvider.java
@@ -19,7 +19,6 @@
 import android.content.Context;
 import android.location.Criteria;
 import android.location.Location;
-import android.os.WorkSource;
 
 import com.android.internal.location.ProviderProperties;
 import com.android.internal.location.ProviderRequest;
@@ -37,13 +36,22 @@
 public class PassiveProvider extends AbstractLocationProvider {
 
     private static final ProviderProperties PROPERTIES = new ProviderProperties(
-            false, false, false, false, false, false, false,
-            Criteria.POWER_LOW, Criteria.ACCURACY_COARSE);
+            /* requiresNetwork = */false,
+            /* requiresSatellite = */false,
+            /* requiresCell = */false,
+            /* hasMonetaryCost = */false,
+            /* supportsAltitude = */false,
+            /* supportsSpeed = */false,
+            /* supportsBearing = */false,
+            Criteria.POWER_LOW,
+            Criteria.ACCURACY_COARSE);
 
-    private boolean mReportLocation;
+    private volatile boolean mReportLocation;
 
-    public PassiveProvider(Context context, LocationProviderManager locationProviderManager) {
-        super(context, locationProviderManager);
+    public PassiveProvider(Context context) {
+        // using a direct executor is only acceptable because this class is so simple it is trivial
+        // to verify that it does not acquire any locks or re-enter LMS from callbacks
+        super(context, Runnable::run);
 
         mReportLocation = false;
 
@@ -52,7 +60,7 @@
     }
 
     @Override
-    public void onSetRequest(ProviderRequest request, WorkSource source) {
+    public void onSetRequest(ProviderRequest request) {
         mReportLocation = request.reportLocation;
     }
 
@@ -63,7 +71,5 @@
     }
 
     @Override
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("report location=" + mReportLocation);
-    }
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {}
 }
diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java
index 9ca302e..9c9a412 100644
--- a/services/core/java/com/android/server/media/MediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java
@@ -47,11 +47,11 @@
 
     public abstract void requestCreateSession(String packageName, String routeId,
             String routeType, long requestId);
-    public abstract void releaseSession(int sessionId);
+    public abstract void releaseSession(String sessionId);
 
-    public abstract void selectRoute(int sessionId, String routeId);
-    public abstract void deselectRoute(int sessionId, String routeId);
-    public abstract void transferToRoute(int sessionId, String routeId);
+    public abstract void selectRoute(String sessionId, String routeId);
+    public abstract void deselectRoute(String sessionId, String routeId);
+    public abstract void transferToRoute(String sessionId, String routeId);
 
     public abstract void sendControlRequest(String routeId, Intent request);
     public abstract void requestSetVolume(String routeId, int volume);
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
index 5cc2b16..6359835 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
@@ -86,7 +86,7 @@
     }
 
     @Override
-    public void releaseSession(int sessionId) {
+    public void releaseSession(String sessionId) {
         if (mConnectionReady) {
             mActiveConnection.releaseSession(sessionId);
             updateBinding();
@@ -94,21 +94,21 @@
     }
 
     @Override
-    public void selectRoute(int sessionId, String routeId) {
+    public void selectRoute(String sessionId, String routeId) {
         if (mConnectionReady) {
             mActiveConnection.selectRoute(sessionId, routeId);
         }
     }
 
     @Override
-    public void deselectRoute(int sessionId, String routeId) {
+    public void deselectRoute(String sessionId, String routeId) {
         if (mConnectionReady) {
             mActiveConnection.deselectRoute(sessionId, routeId);
         }
     }
 
     @Override
-    public void transferToRoute(int sessionId, String routeId) {
+    public void transferToRoute(String sessionId, String routeId) {
         if (mConnectionReady) {
             mActiveConnection.transferToRoute(sessionId, routeId);
         }
@@ -302,6 +302,11 @@
                     + mComponentName);
             return;
         }
+
+        sessionInfo = new RouteSessionInfo.Builder(sessionInfo)
+                .setProviderId(getUniqueId())
+                .build();
+
         mCallback.onSessionInfoChanged(this, sessionInfo);
     }
 
@@ -355,7 +360,7 @@
             }
         }
 
-        public void releaseSession(int sessionId) {
+        public void releaseSession(String sessionId) {
             try {
                 mProvider.releaseSession(sessionId);
             } catch (RemoteException ex) {
@@ -363,7 +368,7 @@
             }
         }
 
-        public void selectRoute(int sessionId, String routeId) {
+        public void selectRoute(String sessionId, String routeId) {
             try {
                 mProvider.selectRoute(sessionId, routeId);
             } catch (RemoteException ex) {
@@ -371,7 +376,7 @@
             }
         }
 
-        public void deselectRoute(int sessionId, String routeId) {
+        public void deselectRoute(String sessionId, String routeId) {
             try {
                 mProvider.deselectRoute(sessionId, routeId);
             } catch (RemoteException ex) {
@@ -379,7 +384,7 @@
             }
         }
 
-        public void transferToRoute(int sessionId, String routeId) {
+        public void transferToRoute(String sessionId, String routeId) {
             try {
                 mProvider.transferToRoute(sessionId, routeId);
             } catch (RemoteException ex) {
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index b482432..487ab52 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -16,6 +16,9 @@
 
 package com.android.server.media;
 
+import static android.media.MediaRouter2Utils.getOriginalId;
+import static android.media.MediaRouter2Utils.getProviderId;
+
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
 import android.annotation.NonNull;
@@ -197,6 +200,9 @@
             MediaRoute2Info route) {
         Objects.requireNonNull(client, "client must not be null");
         Objects.requireNonNull(route, "route must not be null");
+        if (TextUtils.isEmpty(uniqueSessionId)) {
+            throw new IllegalArgumentException("uniqueSessionId must not be empty");
+        }
 
         final long token = Binder.clearCallingIdentity();
         try {
@@ -213,6 +219,9 @@
             MediaRoute2Info route) {
         Objects.requireNonNull(client, "client must not be null");
         Objects.requireNonNull(route, "route must not be null");
+        if (TextUtils.isEmpty(uniqueSessionId)) {
+            throw new IllegalArgumentException("uniqueSessionId must not be empty");
+        }
 
         final long token = Binder.clearCallingIdentity();
         try {
@@ -228,6 +237,9 @@
             MediaRoute2Info route) {
         Objects.requireNonNull(client, "client must not be null");
         Objects.requireNonNull(route, "route must not be null");
+        if (TextUtils.isEmpty(uniqueSessionId)) {
+            throw new IllegalArgumentException("uniqueSessionId must not be empty");
+        }
 
         final long token = Binder.clearCallingIdentity();
         try {
@@ -241,6 +253,9 @@
 
     public void releaseSession(IMediaRouter2Client client, String uniqueSessionId) {
         Objects.requireNonNull(client, "client must not be null");
+        if (TextUtils.isEmpty(uniqueSessionId)) {
+            throw new IllegalArgumentException("uniqueSessionId must not be empty");
+        }
 
         final long token = Binder.clearCallingIdentity();
         try {
@@ -607,7 +622,7 @@
             }
             long uniqueRequestId = toUniqueRequestId(managerRecord.mClientId, requestId);
             if (clientRecord != null && managerRecord.mTrusted) {
-                //TODO: select category properly
+                //TODO: select route type properly
                 requestCreateSessionLocked(clientRecord.mClient, route,
                         route.getRouteTypes().get(0), uniqueRequestId);
             }
@@ -1002,8 +1017,7 @@
             if (provider == null) {
                 return;
             }
-            provider.selectRoute(RouteSessionInfo.getSessionId(uniqueSessionId),
-                    route.getOriginalId());
+            provider.selectRoute(getOriginalId(uniqueSessionId), route.getOriginalId());
         }
 
         private void deselectRouteOnHandler(@NonNull Client2Record clientRecord,
@@ -1019,8 +1033,7 @@
             if (provider == null) {
                 return;
             }
-            provider.deselectRoute(RouteSessionInfo.getSessionId(uniqueSessionId),
-                    route.getOriginalId());
+            provider.deselectRoute(getOriginalId(uniqueSessionId), route.getOriginalId());
         }
 
         private void transferToRouteOnHandler(@NonNull Client2Record clientRecord,
@@ -1036,7 +1049,7 @@
             if (provider == null) {
                 return;
             }
-            provider.transferToRoute(RouteSessionInfo.getSessionId(uniqueSessionId),
+            provider.transferToRoute(getOriginalId(uniqueSessionId),
                     route.getOriginalId());
         }
 
@@ -1068,9 +1081,9 @@
                 return false;
             }
 
-            final Integer sessionId = RouteSessionInfo.getSessionId(uniqueSessionId);
+            final String sessionId = getOriginalId(uniqueSessionId);
             if (sessionId == null) {
-                Slog.w(TAG, "Failed to get int session id from unique session id. "
+                Slog.w(TAG, "Failed to get original session id from unique session id. "
                         + "uniqueSessionId=" + uniqueSessionId);
                 return false;
             }
@@ -1093,14 +1106,14 @@
                 return;
             }
 
-            final String providerId = RouteSessionInfo.getProviderId(uniqueSessionId);
+            final String providerId = getProviderId(uniqueSessionId);
             if (providerId == null) {
                 Slog.w(TAG, "Ignoring releasing session with invalid unique session ID. "
                         + "uniqueSessionId=" + uniqueSessionId);
                 return;
             }
 
-            final Integer sessionId = RouteSessionInfo.getSessionId(uniqueSessionId);
+            final String sessionId = getOriginalId(uniqueSessionId);
             if (sessionId == null) {
                 Slog.w(TAG, "Ignoring releasing session with invalid unique session ID. "
                         + "uniqueSessionId=" + uniqueSessionId + " providerId=" + providerId);
@@ -1146,15 +1159,15 @@
             }
 
             String originalRouteId = matchingRequest.mRoute.getId();
-            String originalCategory = matchingRequest.mRouteType;
+            String originalRouteType = matchingRequest.mRouteType;
             Client2Record client2Record = matchingRequest.mClientRecord;
 
             if (!sessionInfo.getSelectedRoutes().contains(originalRouteId)
-                    || !TextUtils.equals(originalCategory,
+                    || !TextUtils.equals(originalRouteType,
                         sessionInfo.getRouteType())) {
                 Slog.w(TAG, "Created session doesn't match the original request."
                         + " originalRouteId=" + originalRouteId
-                        + ", originalCategory=" + originalCategory + ", requestId=" + requestId
+                        + ", originalRouteType=" + originalRouteType + ", requestId=" + requestId
                         + ", sessionInfo=" + sessionInfo);
                 notifySessionCreationFailed(matchingRequest.mClientRecord,
                         toClientRequestId(requestId));
@@ -1164,41 +1177,34 @@
             // Succeeded
             notifySessionCreated(matchingRequest.mClientRecord,
                     sessionInfo, toClientRequestId(requestId));
-            mSessionToClientMap.put(sessionInfo.getUniqueSessionId(), client2Record);
+            mSessionToClientMap.put(sessionInfo.getId(), client2Record);
             // TODO: Tell managers for the session creation
         }
 
         private void onSessionInfoChangedOnHandler(@NonNull MediaRoute2Provider provider,
                 @NonNull RouteSessionInfo sessionInfo) {
-            RouteSessionInfo sessionInfoWithProviderId = new RouteSessionInfo.Builder(sessionInfo)
-                    .setProviderId(provider.getUniqueId())
-                    .build();
 
             Client2Record client2Record = mSessionToClientMap.get(
-                    sessionInfoWithProviderId.getUniqueSessionId());
+                    sessionInfo.getId());
             if (client2Record == null) {
-                Slog.w(TAG, "No matching client found for session=" + sessionInfoWithProviderId);
+                Slog.w(TAG, "No matching client found for session=" + sessionInfo);
                 // TODO: Tell managers for the session update
                 return;
             }
-            notifySessionInfoChanged(client2Record, sessionInfoWithProviderId);
+            notifySessionInfoChanged(client2Record, sessionInfo);
             // TODO: Tell managers for the session update
         }
 
         private void onSessionReleasedOnHandler(@NonNull MediaRoute2Provider provider,
                 @NonNull RouteSessionInfo sessionInfo) {
-            RouteSessionInfo sessionInfoWithProviderId = new RouteSessionInfo.Builder(sessionInfo)
-                    .setProviderId(provider.getUniqueId())
-                    .build();
 
-            Client2Record client2Record = mSessionToClientMap.get(
-                    sessionInfoWithProviderId.getUniqueSessionId());
+            Client2Record client2Record = mSessionToClientMap.get(sessionInfo.getId());
             if (client2Record == null) {
-                Slog.w(TAG, "No matching client found for session=" + sessionInfoWithProviderId);
+                Slog.w(TAG, "No matching client found for session=" + sessionInfo);
                 // TODO: Tell managers for the session release
                 return;
             }
-            notifySessionReleased(client2Record, sessionInfoWithProviderId);
+            notifySessionReleased(client2Record, sessionInfo);
             // TODO: Tell managers for the session release
         }
 
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index daf6030..0ea4e63 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -48,8 +48,8 @@
     static final String BLUETOOTH_ROUTE_ID = "BLUETOOTH_ROUTE";
 
     // TODO: Move these to a proper place
-    public static final String CATEGORY_LIVE_AUDIO = "android.media.intent.category.LIVE_AUDIO";
-    public static final String CATEGORY_LIVE_VIDEO = "android.media.intent.category.LIVE_VIDEO";
+    public static final String TYPE_LIVE_AUDIO = "android.media.intent.route.TYPE_LIVE_AUDIO";
+    public static final String TYPE_LIVE_VIDEO = "android.media.intent.route.TYPE_LIVE_VIDEO";
 
     private final AudioManager mAudioManager;
     private final IAudioService mAudioService;
@@ -97,22 +97,22 @@
     }
 
     @Override
-    public void releaseSession(int sessionId) {
+    public void releaseSession(String sessionId) {
         // Do nothing
     }
 
     @Override
-    public void selectRoute(int sessionId, String routeId) {
+    public void selectRoute(String sessionId, String routeId) {
         //TODO: implement method
     }
 
     @Override
-    public void deselectRoute(int sessionId, String routeId) {
+    public void deselectRoute(String sessionId, String routeId) {
         //TODO: implement method
     }
 
     @Override
-    public void transferToRoute(int sessionId, String routeId) {
+    public void transferToRoute(String sessionId, String routeId) {
         //TODO: implement method
     }
 
@@ -141,8 +141,8 @@
                         : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
                 .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
                 .setVolume(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC))
-                .addRouteType(CATEGORY_LIVE_AUDIO)
-                .addRouteType(CATEGORY_LIVE_VIDEO)
+                .addRouteType(TYPE_LIVE_AUDIO)
+                .addRouteType(TYPE_LIVE_VIDEO)
                 .build();
 
         AudioRoutesInfo newAudioRoutes = null;
@@ -181,8 +181,8 @@
                         : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
                 .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
                 .setVolume(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC))
-                .addRouteType(CATEGORY_LIVE_AUDIO)
-                .addRouteType(CATEGORY_LIVE_VIDEO)
+                .addRouteType(TYPE_LIVE_AUDIO)
+                .addRouteType(TYPE_LIVE_VIDEO)
                 .build();
 
         if (!TextUtils.equals(newRoutes.bluetoothName, mCurAudioRoutesInfo.bluetoothName)) {
@@ -193,7 +193,7 @@
                         mCurAudioRoutesInfo.bluetoothName)
                         .setDescription(mContext.getResources().getText(
                                 R.string.bluetooth_a2dp_audio_route_name).toString())
-                        .addRouteType(CATEGORY_LIVE_AUDIO)
+                        .addRouteType(TYPE_LIVE_AUDIO)
                         .build();
             } else {
                 mBluetoothA2dpRoute = null;
diff --git a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
index d66fd57..da2ca9b 100644
--- a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
+++ b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
@@ -43,7 +43,8 @@
         }
 
         record.updateNotificationChannel(mConfig.getNotificationChannel(record.sbn.getPackageName(),
-                record.sbn.getUid(), record.getChannel().getId(), false));
+                record.sbn.getUid(), record.getChannel().getId(),
+                record.getNotification().getShortcutId(), false));
 
         return null;
     }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index a4f4d07..5eafb41 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -25,6 +25,7 @@
 import static android.app.Notification.FLAG_NO_CLEAR;
 import static android.app.Notification.FLAG_ONGOING_EVENT;
 import static android.app.Notification.FLAG_ONLY_ALERT_ONCE;
+import static android.app.NotificationChannel.CONVERSATION_CHANNEL_ID_FORMAT;
 import static android.app.NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED;
 import static android.app.NotificationManager.ACTION_AUTOMATIC_ZEN_RULE_STATUS_CHANGED;
 import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED;
@@ -236,6 +237,7 @@
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
 import com.android.internal.util.function.TriPredicate;
 import com.android.server.DeviceIdleInternal;
@@ -2219,8 +2221,8 @@
         maybeNotifyChannelOwner(pkg, uid, preUpdate, channel);
 
         if (!fromListener) {
-            final NotificationChannel modifiedChannel =
-                    mPreferencesHelper.getNotificationChannel(pkg, uid, channel.getId(), false);
+            final NotificationChannel modifiedChannel = mPreferencesHelper.getNotificationChannel(
+                    pkg, uid, channel.getId(), false);
             mListeners.notifyNotificationChannelChanged(
                     pkg, UserHandle.getUserHandleForUid(uid),
                     modifiedChannel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED);
@@ -2481,7 +2483,7 @@
                     .setUid(r.sbn.getUid())
                     .setChannelId(r.getChannel().getId())
                     .setChannelName(r.getChannel().getName().toString())
-                    .setPostedTimeMs(r.sbn.getPostTime())
+                    .setPostedTimeMs(System.currentTimeMillis())
                     .setTitle(getHistoryTitle(r.getNotification()))
                     .setText(getHistoryText(
                             r.sbn.getPackageContext(getContext()), r.getNotification()))
@@ -3017,21 +3019,43 @@
 
         @Override
         public void createNotificationChannels(String pkg,
-                ParceledListSlice channelsList) throws RemoteException {
+                ParceledListSlice channelsList) {
             checkCallerIsSystemOrSameApp(pkg);
             createNotificationChannelsImpl(pkg, Binder.getCallingUid(), channelsList);
         }
 
         @Override
         public void createNotificationChannelsForPackage(String pkg, int uid,
-                ParceledListSlice channelsList) throws RemoteException {
-            checkCallerIsSystem();
+                ParceledListSlice channelsList) {
+            enforceSystemOrSystemUI("only system can call this");
             createNotificationChannelsImpl(pkg, uid, channelsList);
         }
 
         @Override
+        public void createConversationNotificationChannelForPackage(String pkg, int uid,
+                NotificationChannel parentChannel, String conversationId) {
+            enforceSystemOrSystemUI("only system can call this");
+            Preconditions.checkNotNull(parentChannel);
+            Preconditions.checkNotNull(conversationId);
+            String parentId = parentChannel.getId();
+            NotificationChannel conversationChannel = parentChannel;
+            conversationChannel.setId(String.format(
+                    CONVERSATION_CHANNEL_ID_FORMAT, parentId, conversationId));
+            conversationChannel.setConversationId(parentId, conversationId);
+            createNotificationChannelsImpl(
+                    pkg, uid, new ParceledListSlice(Arrays.asList(conversationChannel)));
+        }
+
+        @Override
         public NotificationChannel getNotificationChannel(String callingPkg, int userId,
                 String targetPkg, String channelId) {
+            return getConversationNotificationChannel(
+                    callingPkg, userId, targetPkg, channelId, null);
+        }
+
+        @Override
+        public NotificationChannel getConversationNotificationChannel(String callingPkg, int userId,
+                String targetPkg, String channelId, String conversationId) {
             if (canNotifyAsPackage(callingPkg, targetPkg, userId)
                     || isCallingUidSystem()) {
                 int targetUid = -1;
@@ -3041,7 +3065,8 @@
                     /* ignore */
                 }
                 return mPreferencesHelper.getNotificationChannel(
-                        targetPkg, targetUid, channelId, false /* includeDeleted */);
+                        targetPkg, targetUid, channelId, conversationId,
+                        false /* includeDeleted */);
             }
             throw new SecurityException("Pkg " + callingPkg
                     + " cannot read channels for " + targetPkg + " in " + userId);
@@ -3072,6 +3097,30 @@
         }
 
         @Override
+        public void deleteConversationNotificationChannels(String pkg, int uid,
+                String conversationId) {
+            checkCallerIsSystem();
+            final int callingUid = Binder.getCallingUid();
+            List<NotificationChannel> channels =
+                    mPreferencesHelper.getNotificationChannelsByConversationId(
+                            pkg, uid, conversationId);
+            if (!channels.isEmpty()) {
+                for (NotificationChannel nc : channels) {
+                    cancelAllNotificationsInt(MY_UID, MY_PID, pkg, nc.getId(), 0, 0, true,
+                            UserHandle.getUserId(callingUid), REASON_CHANNEL_BANNED, null);
+                    mPreferencesHelper.deleteNotificationChannel(pkg, callingUid, nc.getId());
+                    mListeners.notifyNotificationChannelChanged(pkg,
+                            UserHandle.getUserHandleForUid(callingUid),
+                            mPreferencesHelper.getNotificationChannel(
+                                    pkg, callingUid, nc.getId(), true),
+                            NOTIFICATION_CHANNEL_OR_GROUP_DELETED);
+                }
+                handleSavePolicyFile();
+            }
+        }
+
+
+        @Override
         public NotificationChannelGroup getNotificationChannelGroup(String pkg, String groupId) {
             checkCallerIsSystemOrSameApp(pkg);
             return mPreferencesHelper.getNotificationChannelGroupWithChannels(
@@ -5203,7 +5252,8 @@
             channelId = (new Notification.TvExtender(notification)).getChannelId();
         }
         final NotificationChannel channel = mPreferencesHelper.getNotificationChannel(pkg,
-                notificationUid, channelId, false /* includeDeleted */);
+                notificationUid, channelId, notification.getShortcutId(),
+                false /* includeDeleted */);
         if (channel == null) {
             final String noChannelStr = "No Channel found for "
                     + "pkg=" + pkg
diff --git a/services/core/java/com/android/server/notification/OWNERS b/services/core/java/com/android/server/notification/OWNERS
new file mode 100644
index 0000000..5a19656
--- /dev/null
+++ b/services/core/java/com/android/server/notification/OWNERS
@@ -0,0 +1,4 @@
+dsandler@android.com
+juliacr@google.com
+beverlyt@google.com
+pixel@google.com
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index cdb0a17..92fcb7f 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -770,6 +770,13 @@
                 channel.setShowBadge(false);
             }
             channel.setOriginalImportance(channel.getImportance());
+
+            // validate parent
+            if (channel.getParentChannelId() != null) {
+                Preconditions.checkArgument(r.channels.containsKey(channel.getParentChannelId()),
+                        "Tried to create a conversation channel without a preexisting parent");
+            }
+
             r.channels.put(channel.getId(), channel);
             if (channel.canBypassDnd() != mAreChannelsBypassingDnd) {
                 updateChannelsBypassingDnd(mContext.getUserId());
@@ -851,6 +858,13 @@
     public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
             boolean includeDeleted) {
         Objects.requireNonNull(pkg);
+        return getNotificationChannel(pkg, uid, channelId, null, includeDeleted);
+    }
+
+    @Override
+    public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
+            String conversationId, boolean includeDeleted) {
+        Preconditions.checkNotNull(pkg);
         synchronized (mPackagePreferences) {
             PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
             if (r == null) {
@@ -859,14 +873,51 @@
             if (channelId == null) {
                 channelId = NotificationChannel.DEFAULT_CHANNEL_ID;
             }
-            final NotificationChannel nc = r.channels.get(channelId);
-            if (nc != null && (includeDeleted || !nc.isDeleted())) {
-                return nc;
+            if (conversationId == null) {
+                final NotificationChannel nc = r.channels.get(channelId);
+                if (nc != null && (includeDeleted || !nc.isDeleted())) {
+                    return nc;
+                }
+            } else {
+                // look for an automatically created conversation specific channel
+                return findConversationChannel(r, channelId, conversationId, includeDeleted);
             }
             return null;
         }
     }
 
+    private NotificationChannel findConversationChannel(PackagePreferences p, String parentId,
+            String conversationId, boolean includeDeleted) {
+        for (NotificationChannel nc : p.channels.values()) {
+            if (conversationId.equals(nc.getConversationId())
+                    && parentId.equals(nc.getParentChannelId())
+                    && (includeDeleted || !nc.isDeleted())) {
+                return nc;
+            }
+        }
+        return null;
+    }
+
+    public List<NotificationChannel> getNotificationChannelsByConversationId(String pkg, int uid,
+            String conversationId) {
+        Preconditions.checkNotNull(pkg);
+        Preconditions.checkNotNull(conversationId);
+        List<NotificationChannel> channels = new ArrayList<>();
+        synchronized (mPackagePreferences) {
+            PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
+            if (r == null) {
+                return channels;
+            }
+            for (NotificationChannel nc : r.channels.values()) {
+                if (conversationId.equals(nc.getConversationId())
+                        && !nc.isDeleted()) {
+                    channels.add(nc);
+                }
+            }
+            return channels;
+        }
+    }
+
     @Override
     public void deleteNotificationChannel(String pkg, int uid, String channelId) {
         synchronized (mPackagePreferences) {
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index 7816f36..4b044c1 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -41,10 +41,15 @@
             int uid, boolean includeDeleted, boolean includeNonGrouped, boolean includeEmpty);
     boolean createNotificationChannel(String pkg, int uid, NotificationChannel channel,
             boolean fromTargetApp, boolean hasDndAccess);
-    void updateNotificationChannel(String pkg, int uid, NotificationChannel channel, boolean fromUser);
-    NotificationChannel getNotificationChannel(String pkg, int uid, String channelId, boolean includeDeleted);
+    void updateNotificationChannel(String pkg, int uid, NotificationChannel channel,
+            boolean fromUser);
+    NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
+            boolean includeDeleted);
+    NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
+            String conversationId, boolean includeDeleted);
     void deleteNotificationChannel(String pkg, int uid, String channelId);
     void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId);
     void permanentlyDeleteNotificationChannels(String pkg, int uid);
-    ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid, boolean includeDeleted);
+    ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid,
+            boolean includeDeleted);
 }
diff --git a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
index b25e1e2..ed71399 100644
--- a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
+++ b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
@@ -15,35 +15,45 @@
  */
 package com.android.server.pm;
 
+import static android.app.AppOpsManager.OP_INTERACT_ACROSS_PROFILES;
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
 
+import android.Manifest;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.ActivityOptions;
+import android.app.AppGlobals;
 import android.app.AppOpsManager;
 import android.app.IApplicationThread;
 import android.app.admin.DevicePolicyEventLogger;
+import android.app.admin.DevicePolicyManagerInternal;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ICrossProfileApps;
+import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ResolveInfo;
 import android.os.Binder;
+import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.stats.devicepolicy.DevicePolicyEnums;
 import android.text.TextUtils;
+import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
+import com.android.internal.app.IAppOpsService;
+import com.android.internal.util.ArrayUtils;
 import com.android.server.LocalServices;
+import com.android.server.appop.AppOpsService;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
 import java.util.ArrayList;
@@ -55,6 +65,9 @@
 
     private Context mContext;
     private Injector mInjector;
+    private AppOpsService mAppOpsService;
+    private final DevicePolicyManagerInternal mDpmi;
+    private final IPackageManager mIpm;
 
     public CrossProfileAppsServiceImpl(Context context) {
         this(context, new InjectorImpl(context));
@@ -64,6 +77,8 @@
     CrossProfileAppsServiceImpl(Context context, Injector injector) {
         mContext = context;
         mInjector = injector;
+        mIpm = AppGlobals.getPackageManager();
+        mDpmi = LocalServices.getService(DevicePolicyManagerInternal.class);
     }
 
     @Override
@@ -153,6 +168,63 @@
                 userId);
     }
 
+    @Override
+    public boolean canRequestInteractAcrossProfiles(String callingPackage) {
+        Objects.requireNonNull(callingPackage);
+        verifyCallingPackage(callingPackage);
+
+        final List<UserHandle> targetUserProfiles = getTargetUserProfilesUnchecked(
+                callingPackage, mInjector.getCallingUserId());
+        if (targetUserProfiles.isEmpty()) {
+            return false;
+        }
+
+        if (!hasRequestedAppOpPermission(
+                AppOpsManager.opToPermission(OP_INTERACT_ACROSS_PROFILES), callingPackage)) {
+            return false;
+        }
+        return isCrossProfilePackageWhitelisted(callingPackage);
+    }
+
+    private boolean hasRequestedAppOpPermission(String permission, String packageName) {
+        try {
+            String[] packages = mIpm.getAppOpPermissionPackages(permission);
+            return ArrayUtils.contains(packages, packageName);
+        } catch (RemoteException exc) {
+            Slog.e(TAG, "PackageManager dead. Cannot get permission info");
+            return false;
+        }
+    }
+
+    @Override
+    public boolean canInteractAcrossProfiles(String callingPackage) {
+        Objects.requireNonNull(callingPackage);
+        verifyCallingPackage(callingPackage);
+
+        final List<UserHandle> targetUserProfiles = getTargetUserProfilesUnchecked(
+                callingPackage, mInjector.getCallingUserId());
+        if (targetUserProfiles.isEmpty()) {
+            return false;
+        }
+
+        final int callingUid = mInjector.getCallingUid();
+        return isPermissionGranted(Manifest.permission.INTERACT_ACROSS_USERS_FULL, callingUid)
+                || isPermissionGranted(Manifest.permission.INTERACT_ACROSS_USERS, callingUid)
+                || isPermissionGranted(Manifest.permission.INTERACT_ACROSS_PROFILES, callingUid)
+                || AppOpsManager.MODE_ALLOWED == getAppOpsService().noteOperation(
+                OP_INTERACT_ACROSS_PROFILES, callingUid, callingPackage, /* featureId= */ null,
+                /*shouldCollectAsyncNotedOp= */false, /*message= */null);
+    }
+
+    private boolean isCrossProfilePackageWhitelisted(String packageName) {
+        final long ident = mInjector.clearCallingIdentity();
+        try {
+            return mDpmi.getAllCrossProfilePackages().contains(packageName);
+        } finally {
+            mInjector.restoreCallingIdentity(ident);
+        }
+    }
+
     private List<UserHandle> getTargetUserProfilesUnchecked(
             String callingPackage, @UserIdInt int callingUserId) {
         final long ident = mInjector.clearCallingIdentity();
@@ -239,6 +311,19 @@
         mInjector.getAppOpsManager().checkPackage(mInjector.getCallingUid(), callingPackage);
     }
 
+    private static boolean isPermissionGranted(String permission, int uid) {
+        return PackageManager.PERMISSION_GRANTED == ActivityManager.checkComponentPermission(
+                permission, uid, /* owningUid= */-1, /* exported= */ true);
+    }
+
+    private AppOpsService getAppOpsService() {
+        if (mAppOpsService == null) {
+            IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE);
+            mAppOpsService = (AppOpsService) IAppOpsService.Stub.asInterface(b);
+        }
+        return mAppOpsService;
+    }
+
     private static class InjectorImpl implements Injector {
         private Context mContext;
 
diff --git a/services/core/java/com/android/server/pm/InstallSource.java b/services/core/java/com/android/server/pm/InstallSource.java
index 0541797..6684e3f 100644
--- a/services/core/java/com/android/server/pm/InstallSource.java
+++ b/services/core/java/com/android/server/pm/InstallSource.java
@@ -18,6 +18,8 @@
 
 import android.annotation.Nullable;
 
+import com.android.internal.util.Preconditions;
+
 import java.util.Objects;
 
 /**
@@ -29,16 +31,27 @@
      * An instance of InstallSource representing an absence of knowledge of the source of
      * a package. Used in preference to null.
      */
-    static final InstallSource EMPTY = new InstallSource(null, null, null, false);
+    static final InstallSource EMPTY = new InstallSource(null, null, null, false, false, null);
 
     /** We also memoize this case because it is common - all un-updated system apps. */
-    private static final InstallSource EMPTY_ORPHANED = new InstallSource(null, null, null, true);
+    private static final InstallSource EMPTY_ORPHANED = new InstallSource(
+            null, null, null, true, false, null);
 
-    /** The package that requested the installation, if known. */
+    /**
+     * The package that requested the installation, if known. May not correspond to a currently
+     * installed package if {@link #isInitiatingPackageUninstalled} is true.
+     */
     @Nullable
     final String initiatingPackageName;
 
     /**
+     * The signing details of the initiating package, if known. Always null if
+     * {@link #initiatingPackageName} is null.
+     */
+    @Nullable
+    final PackageSignatures initiatingPackageSignatures;
+
+    /**
      * The package on behalf of which the initiating package requested the installation, if any.
      * For example if a downloaded APK is installed via the Package Installer this could be the
      * app that performed the download. This value is provided by the initiating package and not
@@ -57,76 +70,120 @@
     /** Indicates if the package that was the installerPackageName has been uninstalled. */
     final boolean isOrphaned;
 
+    /**
+     * Indicates if the package in initiatingPackageName has been uninstalled. Always false if
+     * {@link #initiatingPackageName} is null.
+     */
+    final boolean isInitiatingPackageUninstalled;
+
+    static InstallSource create(@Nullable String initiatingPackageName,
+            @Nullable String originatingPackageName, @Nullable String installerPackageName) {
+        return create(initiatingPackageName, originatingPackageName, installerPackageName,
+                false, false);
+    }
+
     static InstallSource create(@Nullable String initiatingPackageName,
             @Nullable String originatingPackageName, @Nullable String installerPackageName,
-            boolean isOrphaned) {
+            boolean isOrphaned, boolean isInitiatingPackageUninstalled) {
         return createInternal(
                 intern(initiatingPackageName),
                 intern(originatingPackageName),
                 intern(installerPackageName),
-                isOrphaned);
+                isOrphaned, isInitiatingPackageUninstalled, null);
     }
 
     private static InstallSource createInternal(@Nullable String initiatingPackageName,
             @Nullable String originatingPackageName, @Nullable String installerPackageName,
-            boolean isOrphaned) {
+            boolean isOrphaned, boolean isInitiatingPackageUninstalled,
+            @Nullable PackageSignatures initiatingPackageSignatures) {
         if (initiatingPackageName == null && originatingPackageName == null
-                && installerPackageName == null) {
+                && installerPackageName == null && initiatingPackageSignatures == null
+                && !isInitiatingPackageUninstalled) {
             return isOrphaned ? EMPTY_ORPHANED : EMPTY;
         }
         return new InstallSource(initiatingPackageName, originatingPackageName,
-                installerPackageName, isOrphaned);
+                installerPackageName, isOrphaned, isInitiatingPackageUninstalled,
+                initiatingPackageSignatures
+        );
     }
 
     private InstallSource(@Nullable String initiatingPackageName,
             @Nullable String originatingPackageName, @Nullable String installerPackageName,
-            boolean isOrphaned) {
+            boolean isOrphaned, boolean isInitiatingPackageUninstalled,
+            @Nullable PackageSignatures initiatingPackageSignatures) {
+        if (initiatingPackageName == null) {
+            Preconditions.checkArgument(initiatingPackageSignatures == null);
+            Preconditions.checkArgument(!isInitiatingPackageUninstalled);
+        }
         this.initiatingPackageName = initiatingPackageName;
         this.originatingPackageName = originatingPackageName;
         this.installerPackageName = installerPackageName;
         this.isOrphaned = isOrphaned;
+        this.isInitiatingPackageUninstalled = isInitiatingPackageUninstalled;
+        this.initiatingPackageSignatures = initiatingPackageSignatures;
     }
 
     /**
-     * Return an InstallSource the same as this one except with the specified installerPackageName.
+     * Return an InstallSource the same as this one except with the specified
+     * {@link #installerPackageName}.
      */
-    InstallSource setInstallerPackage(String installerPackageName) {
+    InstallSource setInstallerPackage(@Nullable String installerPackageName) {
         if (Objects.equals(installerPackageName, this.installerPackageName)) {
             return this;
         }
         return createInternal(initiatingPackageName, originatingPackageName,
-                intern(installerPackageName), isOrphaned);
+                intern(installerPackageName), isOrphaned, isInitiatingPackageUninstalled,
+                initiatingPackageSignatures
+        );
     }
 
     /**
-     * Return an InstallSource the same as this one except with the specified value for isOrphaned.
+     * Return an InstallSource the same as this one except with the specified value for
+     * {@link #isOrphaned}.
      */
     InstallSource setIsOrphaned(boolean isOrphaned) {
         if (isOrphaned == this.isOrphaned) {
             return this;
         }
         return createInternal(initiatingPackageName, originatingPackageName, installerPackageName,
-                isOrphaned);
+                isOrphaned, isInitiatingPackageUninstalled, initiatingPackageSignatures);
     }
 
     /**
-     * Return an InstallSource the same as this one except it does not refer to the specified
-     * installer package name (which is being uninstalled).
+     * Return an InstallSource the same as this one except with the specified
+     * {@link #initiatingPackageSignatures}.
      */
-    InstallSource removeInstallerPackage(String packageName) {
+    InstallSource setInitiatingPackageSignatures(@Nullable PackageSignatures signatures) {
+        if (signatures == initiatingPackageSignatures) {
+            return this;
+        }
+        return createInternal(initiatingPackageName, originatingPackageName, installerPackageName,
+                isOrphaned, isInitiatingPackageUninstalled, signatures);
+    }
+
+    /**
+     * Return an InstallSource the same as this one updated to reflect that the specified installer
+     * package name has been uninstalled.
+     */
+    InstallSource removeInstallerPackage(@Nullable String packageName) {
         if (packageName == null) {
             return this;
         }
 
         boolean modified = false;
-        String initiatingPackageName = this.initiatingPackageName;
+        boolean isInitiatingPackageUninstalled = this.isInitiatingPackageUninstalled;
         String originatingPackageName = this.originatingPackageName;
         String installerPackageName = this.installerPackageName;
         boolean isOrphaned = this.isOrphaned;
 
-        if (packageName.equals(initiatingPackageName)) {
-            initiatingPackageName = null;
-            modified = true;
+        if (packageName.equals(this.initiatingPackageName)) {
+            if (!isInitiatingPackageUninstalled) {
+                // In this case we deliberately do not clear the package name (and signatures).
+                // We allow an app to retrieve details of its own install initiator even after
+                // it has been uninstalled.
+                isInitiatingPackageUninstalled = true;
+                modified = true;
+            }
         }
         if (packageName.equals(originatingPackageName)) {
             originatingPackageName = null;
@@ -141,8 +198,9 @@
         if (!modified) {
             return this;
         }
+
         return createInternal(initiatingPackageName, originatingPackageName, installerPackageName,
-                isOrphaned);
+                isOrphaned, isInitiatingPackageUninstalled, initiatingPackageSignatures);
     }
 
     @Nullable
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index e2dfa12..c43f234 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -635,7 +635,7 @@
             }
         }
         InstallSource installSource = InstallSource.create(installerPackageName,
-                originatingPackageName, requestedInstallerPackageName, false);
+                originatingPackageName, requestedInstallerPackageName);
         session = new PackageInstallerSession(mInternalCallback, mContext, mPm, this,
                 mInstallThread.getLooper(), mStagingManager, sessionId, userId, callingUid,
                 installSource, params, createdMillis,
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 4dcdf7ea..97a2d43 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -1475,7 +1475,7 @@
                 }
 
                 mInstallerUid = uid;
-                mInstallSource = InstallSource.create(packageName, null, packageName, false);
+                mInstallSource = InstallSource.create(packageName, null, packageName);
             }
         } catch (PackageManager.NameNotFoundException e) {
             onSessionTransferStatus(statusReceiver, packageName,
@@ -3138,7 +3138,7 @@
         }
 
         InstallSource installSource = InstallSource.create(installInitiatingPackageName,
-                installOriginatingPackageName, installerPackageName, false);
+                installOriginatingPackageName, installerPackageName);
         return new PackageInstallerSession(callback, context, pm, sessionProvider,
                 installerThread, stagingManager, sessionId, userId, installerUid,
                 installSource, params, createdMillis, stageDir, stageCid, fileInfosArray,
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 6bd9c48..24180f2 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -5344,8 +5344,9 @@
         if (!mUserManager.exists(userId)) return null;
         final int callingUid = Binder.getCallingUid();
         flags = updateFlagsForComponent(flags, userId);
-        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
-                false /* requireFullPermission */, false /* checkShell */, "get service info");
+        mPermissionManager.enforceCrossUserOrProfilePermission(
+                callingUid, userId, false /* requireFullPermission */, false /* checkShell */,
+                "get service info");
         synchronized (mLock) {
             ParsedService s = mComponentResolver.getService(component);
             if (DEBUG_PACKAGE_INFO) Log.v(
@@ -7795,8 +7796,10 @@
             String resolvedType, int flags, int userId, int callingUid,
             boolean includeInstantApps) {
         if (!mUserManager.exists(userId)) return Collections.emptyList();
-        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
-                false /*requireFullPermission*/, false /*checkShell*/,
+        mPermissionManager.enforceCrossUserOrProfilePermission(callingUid,
+                userId,
+                false /*requireFullPermission*/,
+                false /*checkShell*/,
                 "query intent receivers");
         final String instantAppPkgName = getInstantAppPackageName(callingUid);
         flags = updateFlagsForResolve(flags, userId, callingUid, includeInstantApps);
@@ -15158,10 +15161,10 @@
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "updateSettings");
 
         final String pkgName = pkg.getPackageName();
-        final InstallSource installSource = installArgs.installSource;
-        final String installerPackageName = installSource.installerPackageName;
         final int[] installedForUsers = res.origUsers;
         final int installReason = installArgs.installReason;
+        InstallSource installSource = installArgs.installSource;
+        final String installerPackageName = installSource.installerPackageName;
 
         if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + pkg.getCodePath());
         synchronized (mLock) {
@@ -15207,6 +15210,14 @@
                     ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, userId, installerPackageName);
                 }
 
+                if (installSource.initiatingPackageName != null) {
+                    final PackageSetting ips = mSettings.mPackages.get(
+                            installSource.initiatingPackageName);
+                    if (ips != null) {
+                        installSource = installSource.setInitiatingPackageSignatures(
+                                ips.signatures);
+                    }
+                }
                 ps.setInstallSource(installSource);
                 mSettings.addInstallerPackageNames(installSource);
 
@@ -19372,7 +19383,7 @@
             // PermissionController manages default home directly.
             return false;
         }
-        mPermissionManager.setDefaultHome(currentPackageName, userId, (successful) -> {
+        mPermissionManager.setDefaultHome(packageName, userId, (successful) -> {
             if (successful) {
                 postPreferredActivityChangedBroadcast(userId);
             }
@@ -19976,19 +19987,25 @@
                 }
             }
 
-            // All installSource strings are interned, so == is ok here
-            if (installSource.initiatingPackageName == installSource.installerPackageName) {
-                // The installer and initiator will often be the same, and when they are
-                // we can skip doing the same check again.
-                initiatingPackageName = installerPackageName;
+            if (installSource.isInitiatingPackageUninstalled) {
+                // TODO(b/146555198) Allow the app itself to see the info
+                // (at least for non-instant apps)
+                initiatingPackageName = null;
             } else {
-                initiatingPackageName = installSource.initiatingPackageName;
-                final PackageSetting ps = mSettings.mPackages.get(initiatingPackageName);
-                if (ps == null || shouldFilterApplicationLocked(ps, callingUid, userId)) {
-                    initiatingPackageName = null;
+                // All installSource strings are interned, so == is ok here
+                if (installSource.initiatingPackageName == installSource.installerPackageName) {
+                    // The installer and initiator will often be the same, and when they are
+                    // we can skip doing the same check again.
+                    initiatingPackageName = installerPackageName;
+                } else {
+                    initiatingPackageName = installSource.initiatingPackageName;
+                    final PackageSetting ps = mSettings.mPackages.get(initiatingPackageName);
+                    if (ps == null || shouldFilterApplicationLocked(ps, callingUid, userId)) {
+                        initiatingPackageName = null;
+                    }
                 }
-
             }
+
             originatingPackageName = installSource.originatingPackageName;
             if (originatingPackageName != null) {
                 final PackageSetting ps = mSettings.mPackages.get(originatingPackageName);
@@ -20108,8 +20125,7 @@
         // Disable any carrier apps. We do this very early in boot to prevent the apps from being
         // disabled after already being started.
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(mContext.getOpPackageName(), this,
-                mPermissionManagerService, mContext.getContentResolver(),
-                UserHandle.USER_SYSTEM);
+                mPermissionManagerService, UserHandle.USER_SYSTEM, mContext);
 
         disableSkuSpecificApps();
 
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index f9a3361..ec84b51 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -2819,6 +2819,9 @@
         if (installSource.initiatingPackageName != null) {
             serializer.attribute(null, "installInitiator", installSource.initiatingPackageName);
         }
+        if (installSource.isInitiatingPackageUninstalled) {
+            serializer.attribute(null, "installInitiatorUninstalled", "true");
+        }
         if (installSource.originatingPackageName != null) {
             serializer.attribute(null, "installOriginator", installSource.originatingPackageName);
         }
@@ -2836,6 +2839,11 @@
 
         pkg.signatures.writeXml(serializer, "sigs", mPastSignatures);
 
+        if (installSource.initiatingPackageSignatures != null) {
+            installSource.initiatingPackageSignatures.writeXml(
+                    serializer, "install-initiator-sigs", mPastSignatures);
+        }
+
         writePermissionsLPr(serializer, pkg.getPermissionsState().getInstallPermissionStates());
 
         writeSigningKeySetLPr(serializer, pkg.keySetData);
@@ -3571,6 +3579,7 @@
         String isOrphaned = null;
         String installOriginatingPackageName = null;
         String installInitiatingPackageName = null;
+        String installInitiatorUninstalled = null;
         String volumeUuid = null;
         String categoryHintString = null;
         String updateAvailable = null;
@@ -3616,6 +3625,8 @@
             isOrphaned = parser.getAttributeValue(null, "isOrphaned");
             installInitiatingPackageName = parser.getAttributeValue(null, "installInitiator");
             installOriginatingPackageName = parser.getAttributeValue(null, "installOriginator");
+            installInitiatorUninstalled = parser.getAttributeValue(null,
+                    "installInitiatorUninstalled");
             volumeUuid = parser.getAttributeValue(null, "volumeUuid");
             categoryHintString = parser.getAttributeValue(null, "categoryHint");
             if (categoryHintString != null) {
@@ -3772,7 +3783,8 @@
             packageSetting.uidError = "true".equals(uidError);
             InstallSource installSource = InstallSource.create(
                     installInitiatingPackageName, installOriginatingPackageName,
-                    installerPackageName, "true".equals(isOrphaned));
+                    installerPackageName, "true".equals(isOrphaned),
+                    "true".equals(installInitiatorUninstalled));
             packageSetting.installSource = installSource;
             packageSetting.volumeUuid = volumeUuid;
             packageSetting.categoryHint = categoryHint;
@@ -3849,6 +3861,11 @@
                         mKeySetRefs.put(id, 1);
                     }
                     packageSetting.keySetData.addDefinedKeySet(id, alias);
+                } else if (tagName.equals("install-initiator-sigs")) {
+                    final PackageSignatures signatures = new PackageSignatures();
+                    signatures.readXml(parser, mPastSignatures);
+                    packageSetting.installSource =
+                            packageSetting.installSource.setInitiatingPackageSignatures(signatures);
                 } else if (tagName.equals(TAG_DOMAIN_VERIFICATION)) {
                     readDomainVerificationLPw(parser, packageSetting);
                 } else {
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index d921f31..d8c1966 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -4005,21 +4005,128 @@
             PackageManagerServiceUtils.enforceShellRestriction(mUserManagerInt,
                     UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId);
         }
-        if (!requirePermissionWhenSameUser && userId == UserHandle.getUserId(callingUid)) return;
-        if (callingUid != Process.SYSTEM_UID && callingUid != Process.ROOT_UID) {
-            if (requireFullPermission) {
-                mContext.enforceCallingOrSelfPermission(
-                        android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
-            } else {
-                try {
-                    mContext.enforceCallingOrSelfPermission(
-                            android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
-                } catch (SecurityException se) {
-                    mContext.enforceCallingOrSelfPermission(
-                            android.Manifest.permission.INTERACT_ACROSS_USERS, message);
-                }
-            }
+        final int callingUserId = UserHandle.getUserId(callingUid);
+        if (hasCrossUserPermission(
+                callingUid, callingUserId, userId, requireFullPermission,
+                requirePermissionWhenSameUser)) {
+            return;
         }
+        String errorMessage = buildInvalidCrossUserPermissionMessage(
+                message, requireFullPermission);
+        Slog.w(TAG, errorMessage);
+        throw new SecurityException(errorMessage);
+    }
+
+    /**
+     * Checks if the request is from the system or an app that has the appropriate cross-user
+     * permissions defined as follows:
+     * <ul>
+     * <li>INTERACT_ACROSS_USERS_FULL if {@code requireFullPermission} is true.</li>
+     * <li>INTERACT_ACROSS_USERS if the given {@userId} is in a different profile group
+     * to the caller.</li>
+     * <li>Otherwise, INTERACT_ACROSS_PROFILES if the given {@userId} is in the same profile group
+     * as the caller.</li>
+     * </ul>
+     *
+     * @param checkShell whether to prevent shell from access if there's a debugging restriction
+     * @param message the message to log on security exception
+     */
+    private void enforceCrossUserOrProfilePermission(int callingUid, int userId,
+            boolean requireFullPermission, boolean checkShell,
+            String message) {
+        if (userId < 0) {
+            throw new IllegalArgumentException("Invalid userId " + userId);
+        }
+        if (checkShell) {
+            PackageManagerServiceUtils.enforceShellRestriction(mUserManagerInt,
+                    UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId);
+        }
+        final int callingUserId = UserHandle.getUserId(callingUid);
+        if (hasCrossUserPermission(callingUid, callingUserId, userId, requireFullPermission,
+                /*requirePermissionWhenSameUser= */ false)) {
+            return;
+        }
+        final boolean isSameProfileGroup = isSameProfileGroup(callingUserId, userId);
+        if (isSameProfileGroup
+                && hasPermission(android.Manifest.permission.INTERACT_ACROSS_PROFILES)) {
+            return;
+        }
+        String errorMessage = buildInvalidCrossUserOrProfilePermissionMessage(
+                message, requireFullPermission, isSameProfileGroup);
+        Slog.w(TAG, errorMessage);
+        throw new SecurityException(errorMessage);
+    }
+
+    private boolean hasCrossUserPermission(
+            int callingUid, int callingUserId, int userId, boolean requireFullPermission,
+            boolean requirePermissionWhenSameUser) {
+        if (!requirePermissionWhenSameUser && userId == callingUserId) {
+            return true;
+        }
+        if (callingUid == Process.SYSTEM_UID || callingUid == Process.ROOT_UID) {
+            return true;
+        }
+        if (requireFullPermission) {
+            return hasPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+        }
+        return hasPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+                || hasPermission(Manifest.permission.INTERACT_ACROSS_USERS);
+    }
+
+    private boolean hasPermission(String permission) {
+        return mContext.checkCallingOrSelfPermission(permission)
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
+    private boolean isSameProfileGroup(@UserIdInt int callerUserId, @UserIdInt int userId) {
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            return UserManagerService.getInstance().isSameProfileGroup(callerUserId, userId);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    private static String buildInvalidCrossUserPermissionMessage(
+            String message, boolean requireFullPermission) {
+        StringBuilder builder = new StringBuilder();
+        if (message != null) {
+            builder.append(message);
+            builder.append(": ");
+        }
+        builder.append("Requires ");
+        builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+        if (requireFullPermission) {
+            builder.append(".");
+            return builder.toString();
+        }
+        builder.append(" or ");
+        builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS);
+        builder.append(".");
+        return builder.toString();
+    }
+
+    private static String buildInvalidCrossUserOrProfilePermissionMessage(
+            String message, boolean requireFullPermission, boolean isSameProfileGroup) {
+        StringBuilder builder = new StringBuilder();
+        if (message != null) {
+            builder.append(message);
+            builder.append(": ");
+        }
+        builder.append("Requires ");
+        builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+        if (requireFullPermission) {
+            builder.append(".");
+            return builder.toString();
+        }
+        builder.append(" or ");
+        builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS);
+        if (isSameProfileGroup) {
+            builder.append(" or ");
+            builder.append(android.Manifest.permission.INTERACT_ACROSS_PROFILES);
+        }
+        builder.append(".");
+        return builder.toString();
     }
 
     @GuardedBy({"mSettings.mLock", "mLock"})
@@ -4215,6 +4322,17 @@
             PermissionManagerService.this.enforceCrossUserPermission(callingUid, userId,
                     requireFullPermission, checkShell, requirePermissionWhenSameUser, message);
         }
+
+        @Override
+        public void enforceCrossUserOrProfilePermission(int callingUid, int userId,
+                boolean requireFullPermission, boolean checkShell, String message) {
+            PermissionManagerService.this.enforceCrossUserOrProfilePermission(callingUid,
+                    userId,
+                    requireFullPermission,
+                    checkShell,
+                    message);
+        }
+
         @Override
         public void enforceGrantRevokeRuntimePermissionPermissions(String message) {
             PermissionManagerService.this.enforceGrantRevokeRuntimePermissionPermissions(message);
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
index 0f22619..58a9f42 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
@@ -271,6 +271,15 @@
      */
     public abstract void enforceCrossUserPermission(int callingUid, int userId,
             boolean requireFullPermission, boolean checkShell, @NonNull String message);
+
+    /**
+     * Similar to {@link #enforceCrossUserPermission(int, int, boolean, boolean, String)}
+     * but also allows INTERACT_ACROSS_PROFILES permission if calling user and {@code userId} are
+     * in the same profile group.
+     */
+    public abstract void enforceCrossUserOrProfilePermission(int callingUid, int userId,
+            boolean requireFullPermission, boolean checkShell, @NonNull String message);
+
     /**
      * @see #enforceCrossUserPermission(int, int, boolean, boolean, String)
      * @param requirePermissionWhenSameUser When {@code true}, still require the cross user
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index e7269a6..a86c8d7 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -58,6 +58,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.IAppOpsCallback;
 import com.android.internal.app.IAppOpsService;
+import com.android.internal.infra.AndroidFuture;
 import com.android.internal.util.IntPair;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.FgThread;
@@ -68,7 +69,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
 
 /**
  * This is a permission policy that governs over all permission mechanism
@@ -280,7 +281,7 @@
             if (DEBUG) Slog.i(LOG_TAG, "defaultPermsWereGrantedSinceBoot(" + userId + ")");
 
             // Now call into the permission controller to apply policy around permissions
-            final CountDownLatch latch = new CountDownLatch(1);
+            final AndroidFuture<Boolean> future = new AndroidFuture<>();
 
             // We need to create a local manager that does not schedule work on the main
             // there as we are on the main thread and want to block until the work is
@@ -290,22 +291,22 @@
                             getUserContext(getContext(), UserHandle.of(userId)),
                             FgThread.getHandler());
             permissionControllerManager.grantOrUpgradeDefaultRuntimePermissions(
-                    FgThread.getExecutor(),
-                    (Boolean success) -> {
-                        if (!success) {
+                    FgThread.getExecutor(), successful -> {
+                        if (successful) {
+                            future.complete(null);
+                        } else {
                             // We are in an undefined state now, let us crash and have
                             // rescue party suggest a wipe to recover to a good one.
-                            final String message = "Error granting/upgrading runtime permissions";
+                            final String message = "Error granting/upgrading runtime permissions"
+                                    + " for user " + userId;
                             Slog.wtf(LOG_TAG, message);
-                            throw new IllegalStateException(message);
+                            future.completeExceptionally(new IllegalStateException(message));
                         }
-                        latch.countDown();
-                    }
-            );
+                    });
             try {
-                latch.await();
-            } catch (InterruptedException e) {
-                /* ignore */
+                future.get();
+            } catch (InterruptedException | ExecutionException e) {
+                throw new IllegalStateException(e);
             }
 
             permissionControllerManager.updateUserSensitive();
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
index 0b89646..8385f40 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
@@ -21,6 +21,7 @@
 import android.hardware.audio.common.V2_0.Uuid;
 import android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback;
 import android.hardware.soundtrigger.V2_3.ISoundTriggerHw;
+import android.hardware.soundtrigger.V2_3.Properties;
 import android.media.audio.common.AudioConfig;
 import android.media.audio.common.AudioOffloadInfo;
 import android.media.soundtrigger_middleware.ConfidenceLevel;
@@ -69,6 +70,13 @@
         return aidlProperties;
     }
 
+    static @NonNull SoundTriggerModuleProperties hidl2aidlProperties(
+            @NonNull Properties hidlProperties) {
+        SoundTriggerModuleProperties aidlProperties = hidl2aidlProperties(hidlProperties.base);
+        aidlProperties.supportedModelArch = hidlProperties.supportedModelArch;
+        return aidlProperties;
+    }
+
     static @NonNull
     String hidl2aidlUuid(@NonNull Uuid hidlUuid) {
         if (hidlUuid.node == null || hidlUuid.node.length != 6) {
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java
index f0a0d83..dbf91a9 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java
@@ -74,4 +74,12 @@
         config_2_0.data = HidlMemoryUtil.hidlMemoryToByteList(config.data);
         return config_2_0;
     }
+
+    static android.hardware.soundtrigger.V2_3.Properties convertProperties_2_0_to_2_3(
+            android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties properties) {
+        android.hardware.soundtrigger.V2_3.Properties properties_2_3 =
+                new android.hardware.soundtrigger.V2_3.Properties();
+        properties_2_3.base = properties;
+        return properties_2_3;
+    }
 }
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java b/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java
index 81252c9..2f024a5 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java
@@ -16,7 +16,6 @@
 
 package com.android.server.soundtrigger_middleware;
 
-import android.hardware.soundtrigger.V2_3.ISoundTriggerHw;
 import android.hardware.soundtrigger.V2_3.ModelParameterRange;
 import android.hidl.base.V1_0.IBase;
 import android.os.IHwBinder;
@@ -54,9 +53,10 @@
  */
 public interface ISoundTriggerHw2 {
     /**
-     * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#getProperties(android.hardware.soundtrigger.V2_0.ISoundTriggerHw.getPropertiesCallback
+     * @see android.hardware.soundtrigger.V2_3.ISoundTriggerHw#getPropertiesEx(
+     * android.hardware.soundtrigger.V2_3.ISoundTriggerHw.getPropertiesExCallback)
      */
-    android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Properties getProperties();
+    android.hardware.soundtrigger.V2_3.Properties getProperties();
 
     /**
      * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#loadSoundModel_2_1(android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel,
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
index 4a852c4..3354c56 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
@@ -112,18 +112,23 @@
     }
 
     @Override
-    public android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Properties getProperties() {
+    public android.hardware.soundtrigger.V2_3.Properties getProperties() {
         try {
             AtomicInteger retval = new AtomicInteger(-1);
-            AtomicReference<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Properties>
+            AtomicReference<android.hardware.soundtrigger.V2_3.Properties>
                     properties =
                     new AtomicReference<>();
-            as2_0().getProperties(
-                    (r, p) -> {
-                        retval.set(r);
-                        properties.set(p);
-                    });
-            handleHalStatus(retval.get(), "getProperties");
+            try {
+                as2_3().getProperties_2_3(
+                        (r, p) -> {
+                            retval.set(r);
+                            properties.set(p);
+                        });
+            } catch (NotSupported e) {
+                // Fall-back to the 2.0 version:
+                return getProperties_2_0();
+            }
+            handleHalStatus(retval.get(), "getProperties_2_3");
             return properties.get();
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
@@ -312,6 +317,21 @@
         return as2_0().interfaceDescriptor();
     }
 
+    private android.hardware.soundtrigger.V2_3.Properties getProperties_2_0()
+            throws RemoteException {
+        AtomicInteger retval = new AtomicInteger(-1);
+        AtomicReference<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties>
+                properties =
+                new AtomicReference<>();
+        as2_0().getProperties(
+                (r, p) -> {
+                    retval.set(r);
+                    properties.set(p);
+                });
+        handleHalStatus(retval.get(), "getProperties");
+        return Hw2CompatUtil.convertProperties_2_0_to_2_3(properties.get());
+    }
+
     private int loadSoundModel_2_0(
             android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel soundModel,
             Callback callback, int cookie)
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
index 987c05f..5a06a2c 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.PermissionChecker;
 import android.hardware.soundtrigger.V2_0.ISoundTriggerHw;
 import android.media.soundtrigger_middleware.ISoundTriggerCallback;
 import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
@@ -32,6 +33,7 @@
 import android.media.soundtrigger_middleware.RecognitionStatus;
 import android.media.soundtrigger_middleware.SoundModel;
 import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
+import android.media.soundtrigger_middleware.Status;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
 import android.util.Log;
@@ -223,23 +225,48 @@
     }
 
     /**
-     * Throws a {@link SecurityException} if caller doesn't have the right permissions to use this
-     * service.
+     * Throws a {@link SecurityException} if caller permanently doesn't have the given permission,
+     * or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if
+     * caller temporarily doesn't have the right permissions to use this service.
      */
     private void checkPermissions() {
-        mContext.enforceCallingOrSelfPermission(Manifest.permission.RECORD_AUDIO,
-                "Caller must have the android.permission.RECORD_AUDIO permission.");
-        mContext.enforceCallingOrSelfPermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD,
-                "Caller must have the android.permission.CAPTURE_AUDIO_HOTWORD permission.");
+        enforcePermission(Manifest.permission.RECORD_AUDIO);
+        enforcePermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD);
     }
 
     /**
-     * Throws a {@link SecurityException} if caller doesn't have the right permissions to preempt
-     * active sound trigger sessions.
+     * Throws a {@link SecurityException} if caller permanently doesn't have the given permission,
+     * or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if
+     * caller temporarily doesn't have the right permissions to preempt active sound trigger
+     * sessions.
      */
     private void checkPreemptPermissions() {
-        mContext.enforceCallingOrSelfPermission(Manifest.permission.PREEMPT_SOUND_TRIGGER,
-                "Caller must have the android.permission.PREEMPT_SOUND_TRIGGER permission.");
+        enforcePermission(Manifest.permission.PREEMPT_SOUND_TRIGGER);
+    }
+
+    /**
+     * Throws a {@link SecurityException} if caller permanently doesn't have the given permission,
+     * or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if
+     * caller temporarily doesn't have the given permission.
+     *
+     * @param permission The permission to check.
+     */
+    private void enforcePermission(String permission) {
+        final int status = PermissionChecker.checkCallingOrSelfPermissionForPreflight(mContext,
+                permission);
+        switch (status) {
+            case PermissionChecker.PERMISSION_GRANTED:
+                return;
+            case PermissionChecker.PERMISSION_DENIED:
+                throw new SecurityException(
+                        String.format("Caller must have the %s permission.", permission));
+            case PermissionChecker.PERMISSION_DENIED_APP_OP:
+                throw new ServiceSpecificException(Status.TEMPORARY_PERMISSION_DENIED,
+                        String.format("Caller must have the %s permission.", permission));
+            default:
+                throw new InternalServerError(
+                        new RuntimeException("Unexpected perimission check result."));
+        }
     }
 
     /** State of a sound model. */
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index fd871bd..d63165a 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -83,6 +83,7 @@
 import static com.android.server.wm.Task.REPARENT_KEEP_STACK_AT_FRONT;
 import static com.android.server.wm.Task.REPARENT_LEAVE_STACK_IN_PLACE;
 import static com.android.server.wm.Task.REPARENT_MOVE_STACK_TO_FRONT;
+import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 
@@ -2079,7 +2080,7 @@
         ArrayList<ActivityRecord> readyToStopActivities = null;
         for (int i = mStoppingActivities.size() - 1; i >= 0; --i) {
             final ActivityRecord s = mStoppingActivities.get(i);
-            final boolean animating = s.isAnimating(TRANSITION);
+            final boolean animating = s.isAnimating(TRANSITION | PARENTS);
             if (DEBUG_STATES) Slog.v(TAG, "Stopping " + s + ": nowVisible=" + s.nowVisible
                     + " animating=" + animating + " finishing=" + s.finishing);
 
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 09111d0..014cb76 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -436,10 +436,9 @@
         mNextAppTransition = TRANSIT_UNSET;
         mNextAppTransitionFlags = 0;
         setAppTransitionState(APP_STATE_RUNNING);
-        final AnimationAdapter topOpeningAnim =
-                (topOpeningApp != null && topOpeningApp.getAnimatingContainer() != null)
-                        ? topOpeningApp.getAnimatingContainer().getAnimation()
-                        : null;
+        final WindowContainer wc =
+                topOpeningApp != null ? topOpeningApp.getAnimatingContainer() : null;
+        final AnimationAdapter topOpeningAnim = wc != null ? wc.getAnimation() : null;
 
         int redoLayout = notifyAppTransitionStartingLocked(transit,
                 topOpeningAnim != null ? topOpeningAnim.getDurationHint() : 0,
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index f9ad03f..9c62e99 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -2061,7 +2061,8 @@
                         cf.set(displayFrames.mRestricted);
                     }
                     applyStableConstraints(sysUiFl, fl, cf, displayFrames);
-                    if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
+                    if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE
+                            && adjust != SOFT_INPUT_ADJUST_NOTHING) {
                         vf.set(displayFrames.mCurrent);
                     } else {
                         vf.set(cf);
@@ -2138,7 +2139,8 @@
 
                 applyStableConstraints(sysUiFl, fl, cf, displayFrames);
 
-                if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
+                if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE
+                        && adjust != SOFT_INPUT_ADJUST_NOTHING) {
                     vf.set(displayFrames.mCurrent);
                 } else {
                     vf.set(cf);
@@ -2179,7 +2181,8 @@
                         cf.set(displayFrames.mContent);
                         df.set(displayFrames.mContent);
                     }
-                    if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
+                    if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE
+                            && adjust != SOFT_INPUT_ADJUST_NOTHING) {
                         vf.set(displayFrames.mCurrent);
                     } else {
                         vf.set(cf);
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index a13383d..5a591ec 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -129,6 +129,7 @@
         if (win == null) {
             setServerVisible(false);
             mSource.setFrame(new Rect());
+            mSource.setVisibleFrame(null);
         } else if (mControllable) {
             mWin.setControllableInsetProvider(this);
             if (mControlTarget != null) {
@@ -160,6 +161,15 @@
             mTmpRect.inset(mWin.mGivenContentInsets);
         }
         mSource.setFrame(mTmpRect);
+
+        if (mWin.mGivenVisibleInsets.left != 0 || mWin.mGivenVisibleInsets.top != 0
+                || mWin.mGivenVisibleInsets.right != 0 || mWin.mGivenVisibleInsets.bottom != 0) {
+            mTmpRect.set(mWin.getFrameLw());
+            mTmpRect.inset(mWin.mGivenVisibleInsets);
+            mSource.setVisibleFrame(mTmpRect);
+        } else {
+            mSource.setVisibleFrame(null);
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index cefef37..8ac212f 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -777,7 +777,7 @@
      *         otherwise.
      */
     boolean isWaitingForTransitionStart() {
-        return getActivity(app -> app.isWaitingForTransitionStart()) != null;
+        return false;
     }
 
     /**
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 1ad6e86..03969b0 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -13,6 +13,7 @@
     ],
 
     srcs: [
+        ":graphicsstats_proto",
         "BroadcastRadio/JavaRef.cpp",
         "BroadcastRadio/NativeCallbackThread.cpp",
         "BroadcastRadio/BroadcastRadioService.cpp",
@@ -103,6 +104,11 @@
         "libinputflinger",
         "libinputflinger_base",
         "libinputservice",
+        "libprotobuf-cpp-lite",
+        "libprotoutil",
+        "libstatspull",
+        "libstatssocket",
+        "libstatslog",
         "libschedulerservicehidl",
         "libsensorservice",
         "libsensorservicehidl",
diff --git a/services/core/jni/com_android_server_GraphicsStatsService.cpp b/services/core/jni/com_android_server_GraphicsStatsService.cpp
index d1d253b..9353fbd 100644
--- a/services/core/jni/com_android_server_GraphicsStatsService.cpp
+++ b/services/core/jni/com_android_server_GraphicsStatsService.cpp
@@ -23,6 +23,16 @@
 #include <nativehelper/ScopedUtfChars.h>
 #include <JankTracker.h>
 #include <service/GraphicsStatsService.h>
+#include <stats_pull_atom_callback.h>
+#include <stats_event.h>
+#include <statslog.h>
+#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
+#include <android/util/ProtoOutputStream.h>
+#include "android/graphics/Utils.h"
+#include "core_jni_helpers.h"
+#include "protos/graphicsstats.pb.h"
+#include <cstring>
+#include <memory>
 
 namespace android {
 
@@ -77,6 +87,20 @@
     GraphicsStatsService::finishDump(dump);
 }
 
+static jlong finishDumpInMemory(JNIEnv* env, jobject, jlong dumpPtr) {
+    GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr);
+    std::vector<uint8_t>* result = new std::vector<uint8_t>();
+    GraphicsStatsService::finishDumpInMemory(dump,
+        [](void* buffer, int bufferOffset, int bufferSize, int totalSize, void* param1, void* param2) {
+            std::vector<uint8_t>* outBuffer = reinterpret_cast<std::vector<uint8_t>*>(param2);
+            if (outBuffer->size() < totalSize) {
+                outBuffer->resize(totalSize);
+            }
+            std::memcpy(outBuffer->data() + bufferOffset, buffer, bufferSize);
+        }, env, result);
+    return reinterpret_cast<jlong>(result);
+}
+
 static void saveBuffer(JNIEnv* env, jobject clazz, jstring jpath, jstring jpackage,
         jlong versionCode, jlong startTime, jlong endTime, jbyteArray jdata) {
     ScopedByteArrayRO buffer(env, jdata);
@@ -93,19 +117,173 @@
     GraphicsStatsService::saveBuffer(path, package, versionCode, startTime, endTime, data);
 }
 
+static jobject gGraphicsStatsServiceObject = nullptr;
+static jmethodID gGraphicsStatsService_pullGraphicsStatsMethodID;
+
+static JNIEnv* getJNIEnv() {
+    JavaVM* vm = AndroidRuntime::getJavaVM();
+    JNIEnv* env = nullptr;
+    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+        if (vm->AttachCurrentThreadAsDaemon(&env, nullptr) != JNI_OK) {
+            LOG_ALWAYS_FATAL("Failed to AttachCurrentThread!");
+        }
+    }
+    return env;
+}
+
+using namespace google::protobuf;
+
+// Field ids taken from FrameTimingHistogram message in atoms.proto
+#define TIME_MILLIS_BUCKETS_FIELD_NUMBER 1
+#define FRAME_COUNTS_FIELD_NUMBER 2
+
+static void writeCpuHistogram(stats_event* event,
+                              const uirenderer::protos::GraphicsStatsProto& stat) {
+    util::ProtoOutputStream proto;
+    for (int bucketIndex = 0; bucketIndex < stat.histogram_size(); bucketIndex++) {
+        auto& bucket = stat.histogram(bucketIndex);
+        proto.write(android::util::FIELD_TYPE_INT32 | android::util::FIELD_COUNT_REPEATED |
+                            TIME_MILLIS_BUCKETS_FIELD_NUMBER /* field id */,
+                    (int)bucket.render_millis());
+    }
+    for (int bucketIndex = 0; bucketIndex < stat.histogram_size(); bucketIndex++) {
+        auto& bucket = stat.histogram(bucketIndex);
+        proto.write(android::util::FIELD_TYPE_INT64 | android::util::FIELD_COUNT_REPEATED |
+                            FRAME_COUNTS_FIELD_NUMBER /* field id */,
+                    (long long)bucket.frame_count());
+    }
+    std::vector<uint8_t> outVector;
+    proto.serializeToVector(&outVector);
+    stats_event_write_byte_array(event, outVector.data(), outVector.size());
+}
+
+static void writeGpuHistogram(stats_event* event,
+                              const uirenderer::protos::GraphicsStatsProto& stat) {
+    util::ProtoOutputStream proto;
+    for (int bucketIndex = 0; bucketIndex < stat.gpu_histogram_size(); bucketIndex++) {
+        auto& bucket = stat.gpu_histogram(bucketIndex);
+        proto.write(android::util::FIELD_TYPE_INT32 | android::util::FIELD_COUNT_REPEATED |
+                            TIME_MILLIS_BUCKETS_FIELD_NUMBER /* field id */,
+                    (int)bucket.render_millis());
+    }
+    for (int bucketIndex = 0; bucketIndex < stat.gpu_histogram_size(); bucketIndex++) {
+        auto& bucket = stat.gpu_histogram(bucketIndex);
+        proto.write(android::util::FIELD_TYPE_INT64 | android::util::FIELD_COUNT_REPEATED |
+                            FRAME_COUNTS_FIELD_NUMBER /* field id */,
+                    (long long)bucket.frame_count());
+    }
+    std::vector<uint8_t> outVector;
+    proto.serializeToVector(&outVector);
+    stats_event_write_byte_array(event, outVector.data(), outVector.size());
+}
+
+// graphicsStatsPullCallback is invoked by statsd service to pull GRAPHICS_STATS atom.
+static bool graphicsStatsPullCallback(int32_t atom_tag, pulled_stats_event_list* data,
+                                      const void* cookie) {
+    JNIEnv* env = getJNIEnv();
+    if (!env) {
+        return false;
+    }
+    if (gGraphicsStatsServiceObject == nullptr) {
+        ALOGE("Failed to get graphicsstats service");
+        return false;
+    }
+
+    for (bool lastFullDay : {true, false}) {
+        jlong jdata = (jlong) env->CallLongMethod(
+                    gGraphicsStatsServiceObject,
+                    gGraphicsStatsService_pullGraphicsStatsMethodID,
+                    (jboolean)(lastFullDay ? JNI_TRUE : JNI_FALSE));
+        if (env->ExceptionCheck()) {
+            env->ExceptionDescribe();
+            env->ExceptionClear();
+            ALOGE("Failed to invoke graphicsstats service");
+            return false;
+        }
+        if (!jdata) {
+            // null means data is not available for that day.
+            continue;
+        }
+        android::uirenderer::protos::GraphicsStatsServiceDumpProto serviceDump;
+        std::vector<uint8_t>* buffer = reinterpret_cast<std::vector<uint8_t>*>(jdata);
+        std::unique_ptr<std::vector<uint8_t>> bufferRelease(buffer);
+        int dataSize = buffer->size();
+        if (!dataSize) {
+            // Data is not available for that day.
+            continue;
+        }
+        io::ArrayInputStream input{buffer->data(), dataSize};
+        bool success = serviceDump.ParseFromZeroCopyStream(&input);
+        if (!success) {
+            ALOGW("Parse failed on GraphicsStatsPuller error='%s' dataSize='%d'",
+                  serviceDump.InitializationErrorString().c_str(), dataSize);
+            return false;
+        }
+
+        for (int stat_index = 0; stat_index < serviceDump.stats_size(); stat_index++) {
+            auto& stat = serviceDump.stats(stat_index);
+            stats_event* event = add_stats_event_to_pull_data(data);
+            stats_event_set_atom_id(event, android::util::GRAPHICS_STATS);
+            stats_event_write_string8(event, stat.package_name().c_str());
+            stats_event_write_int64(event, (int64_t)stat.version_code());
+            stats_event_write_int64(event, (int64_t)stat.stats_start());
+            stats_event_write_int64(event, (int64_t)stat.stats_end());
+            stats_event_write_int32(event, (int32_t)stat.pipeline());
+            stats_event_write_int32(event, (int32_t)stat.summary().total_frames());
+            stats_event_write_int32(event, (int32_t)stat.summary().missed_vsync_count());
+            stats_event_write_int32(event, (int32_t)stat.summary().high_input_latency_count());
+            stats_event_write_int32(event, (int32_t)stat.summary().slow_ui_thread_count());
+            stats_event_write_int32(event, (int32_t)stat.summary().slow_bitmap_upload_count());
+            stats_event_write_int32(event, (int32_t)stat.summary().slow_draw_count());
+            stats_event_write_int32(event, (int32_t)stat.summary().missed_deadline_count());
+            writeCpuHistogram(event, stat);
+            writeGpuHistogram(event, stat);
+            // TODO: fill in UI mainline module version, when the feature is available.
+            stats_event_write_int64(event, (int64_t)0);
+            stats_event_write_bool(event, !lastFullDay);
+            stats_event_build(event);
+        }
+    }
+    return true;
+}
+
+// Register a puller for GRAPHICS_STATS atom with the statsd service.
+static void nativeInit(JNIEnv* env, jobject javaObject) {
+    gGraphicsStatsServiceObject = env->NewGlobalRef(javaObject);
+    pull_atom_metadata metadata = {.cool_down_ns = 10 * 1000000, // 10 milliseconds
+                                   .timeout_ns = 2 * NS_PER_SEC, // 2 seconds
+                                   .additive_fields = nullptr,
+                                   .additive_fields_size = 0};
+    register_stats_pull_atom_callback(android::util::GRAPHICS_STATS, &graphicsStatsPullCallback,
+            &metadata, nullptr);
+}
+
+static void nativeDestructor(JNIEnv* env, jobject javaObject) {
+    //TODO: Unregister the puller callback when a new API is available.
+    env->DeleteGlobalRef(gGraphicsStatsServiceObject);
+    gGraphicsStatsServiceObject = nullptr;
+}
+
 static const JNINativeMethod sMethods[] = {
     { "nGetAshmemSize", "()I", (void*) getAshmemSize },
     { "nCreateDump", "(IZ)J", (void*) createDump },
     { "nAddToDump", "(JLjava/lang/String;Ljava/lang/String;JJJ[B)V", (void*) addToDump },
     { "nAddToDump", "(JLjava/lang/String;)V", (void*) addFileToDump },
     { "nFinishDump", "(J)V", (void*) finishDump },
+    { "nFinishDumpInMemory", "(J)J", (void*) finishDumpInMemory },
     { "nSaveBuffer", "(Ljava/lang/String;Ljava/lang/String;JJJ[B)V", (void*) saveBuffer },
+    { "nativeInit", "()V", (void*) nativeInit },
+    { "nativeDestructor",   "()V",     (void*)nativeDestructor }
 };
 
 int register_android_server_GraphicsStatsService(JNIEnv* env)
 {
+    jclass graphicsStatsService_class = FindClassOrDie(env,
+            "com/android/server/GraphicsStatsService");
+    gGraphicsStatsService_pullGraphicsStatsMethodID = GetMethodIDOrDie(env,
+            graphicsStatsService_class, "pullGraphicsStats", "(Z)J");
     return jniRegisterNativeMethods(env, "com/android/server/GraphicsStatsService",
                                     sMethods, NELEM(sMethods));
 }
 
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/services/devicepolicy/Android.bp b/services/devicepolicy/Android.bp
index 380ee94..cdbe77a 100644
--- a/services/devicepolicy/Android.bp
+++ b/services/devicepolicy/Android.bp
@@ -18,8 +18,3 @@
         "compat-changeid-annotation-processor",
     ],
 }
-
-platform_compat_config {
-    name: "services-devicepolicy-platform-compat-config",
-    src: ":services.devicepolicy",
-}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 2a08f5c..d5ff280 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -11723,6 +11723,11 @@
             return mStateCache;
         }
 
+        @Override
+        public List<String> getAllCrossProfilePackages() {
+            return DevicePolicyManagerService.this.getAllCrossProfilePackages();
+        }
+
     }
 
     private Intent createShowAdminSupportIntent(ComponentName admin, int userId) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index bfec51c..b6a8ca4 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -751,6 +751,7 @@
         // Now that we have the bare essentials of the OS up and running, take
         // note that we just booted, which might send out a rescue party if
         // we're stuck in a runtime restart loop.
+        RescueParty.registerHealthObserver(mSystemContext);
         RescueParty.noteBoot(mSystemContext);
 
         // Manages LEDs and display backlight so we need it to bring up the display.
diff --git a/services/net/java/android/net/ip/IpClientCallbacks.java b/services/net/java/android/net/ip/IpClientCallbacks.java
index 61cd88a..c93e5c5 100644
--- a/services/net/java/android/net/ip/IpClientCallbacks.java
+++ b/services/net/java/android/net/ip/IpClientCallbacks.java
@@ -17,6 +17,7 @@
 package android.net.ip;
 
 import android.net.DhcpResults;
+import android.net.DhcpResultsParcelable;
 import android.net.Layer2PacketParcelable;
 import android.net.LinkProperties;
 
@@ -69,6 +70,18 @@
     public void onNewDhcpResults(DhcpResults dhcpResults) {}
 
     /**
+     * Callback called when new DHCP results are available.
+     *
+     * <p>This is purely advisory and not an indication of provisioning success or failure.  This is
+     * only here for callers that want to expose DHCPv4 results to other APIs
+     * (e.g., WifiInfo#setInetAddress).
+     *
+     * <p>DHCPv4 or static IPv4 configuration failure or success can be determined by whether or not
+     * the passed-in DhcpResults object is null.
+     */
+    public void onNewDhcpResults(DhcpResultsParcelable dhcpResults) {}
+
+    /**
      * Indicates that provisioning was successful.
      */
     public void onProvisioningSuccess(LinkProperties newLp) {}
diff --git a/services/net/java/android/net/ip/IpClientUtil.java b/services/net/java/android/net/ip/IpClientUtil.java
index 4d60e62..7f723b1 100644
--- a/services/net/java/android/net/ip/IpClientUtil.java
+++ b/services/net/java/android/net/ip/IpClientUtil.java
@@ -119,6 +119,7 @@
         @Override
         public void onNewDhcpResults(DhcpResultsParcelable dhcpResults) {
             mCb.onNewDhcpResults(fromStableParcelable(dhcpResults));
+            mCb.onNewDhcpResults(dhcpResults);
         }
 
         @Override
diff --git a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
index 8632ca4..8b2f15c 100644
--- a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
@@ -81,7 +81,10 @@
 import org.robolectric.shadows.ShadowPackageManager;
 
 import java.io.File;
+import java.io.FileDescriptor;
 import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.util.List;
 
 /**
@@ -1238,13 +1241,49 @@
         assertThat(service.getAncestralSerialNumber()).isEqualTo(testSerialNumber2);
     }
 
+    /**
+     * Test that {@link UserBackupManagerService#dump()} for system user does not prefix dump with
+     * "User 0:".
+     */
+    @Test
+    public void testDump_forSystemUser_DoesNotHaveUserPrefix() throws Exception {
+        mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
+        UserBackupManagerService service =
+                BackupManagerServiceTestUtils.createUserBackupManagerServiceAndRunTasks(
+                        UserHandle.USER_SYSTEM,
+                        mContext,
+                        mBackupThread,
+                        mBaseStateDir,
+                        mDataDir,
+                        mTransportManager);
+
+        StringWriter dump = new StringWriter();
+        service.dump(new FileDescriptor(), new PrintWriter(dump), new String[0]);
+
+        assertThat(dump.toString()).startsWith("Backup Manager is ");
+    }
+
+    /**
+     * Test that {@link UserBackupManagerService#dump()} for non-system user prefixes dump with
+     * "User <userid>:".
+     */
+    @Test
+    public void testDump_forNonSystemUser_HasUserPrefix() throws Exception {
+        mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
+        UserBackupManagerService service = createUserBackupManagerServiceAndRunTasks();
+
+        StringWriter dump = new StringWriter();
+        service.dump(new FileDescriptor(), new PrintWriter(dump), new String[0]);
+
+        assertThat(dump.toString()).startsWith("User " + USER_ID + ":" + "Backup Manager is ");
+    }
+
     private File createTestFile() throws IOException {
         File testFile = new File(mContext.getFilesDir(), "test");
         testFile.createNewFile();
         return testFile;
     }
 
-
     /**
      * We can't mock the void method {@link #schedule(Context, long, BackupManagerConstants)} so we
      * extend {@link ShadowKeyValueBackupJob} and throw an exception at the end of the method.
diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
index 0d6020c..30d89d3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
@@ -32,6 +32,7 @@
 
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.pm.VersionedPackage;
 import android.os.RecoverySystem;
 import android.os.SystemProperties;
 import android.os.UserHandle;
@@ -39,6 +40,8 @@
 import android.provider.Settings;
 
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
+import com.android.server.RescueParty.RescuePartyObserver;
 import com.android.server.am.SettingsToPropertiesMapper;
 import com.android.server.utils.FlagNamespaceUtils;
 
@@ -57,13 +60,15 @@
  * Test RescueParty.
  */
 public class RescuePartyTest {
-    private static final int PERSISTENT_APP_UID = 12;
     private static final long CURRENT_NETWORK_TIME_MILLIS = 0L;
     private static final String FAKE_NATIVE_NAMESPACE1 = "native1";
     private static final String FAKE_NATIVE_NAMESPACE2 = "native2";
     private static final String[] FAKE_RESET_NATIVE_NAMESPACES =
             {FAKE_NATIVE_NAMESPACE1, FAKE_NATIVE_NAMESPACE2};
 
+    private static VersionedPackage sFailingPackage = new VersionedPackage("com.package.name", 1);
+    private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue";
+
     private MockitoSession mSession;
 
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
@@ -182,25 +187,25 @@
 
     @Test
     public void testPersistentAppCrashDetectionWithExecutionForAllRescueLevels() {
-        notePersistentAppCrash(RescueParty.TRIGGER_COUNT);
+        notePersistentAppCrash();
 
         verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS);
         assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS,
                 SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
 
-        notePersistentAppCrash(RescueParty.TRIGGER_COUNT);
+        notePersistentAppCrash();
 
         verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES);
         assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES,
                 SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
 
-        notePersistentAppCrash(RescueParty.TRIGGER_COUNT);
+        notePersistentAppCrash();
 
         verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS);
         assertEquals(RescueParty.LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS,
                 SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
 
-        notePersistentAppCrash(RescueParty.TRIGGER_COUNT);
+        notePersistentAppCrash();
 
         verify(() -> RecoverySystem.rebootPromptAndWipeUserData(mMockContext, RescueParty.TAG));
         assertEquals(RescueParty.LEVEL_FACTORY_RESET,
@@ -221,20 +226,6 @@
     }
 
     @Test
-    public void testPersistentAppCrashDetectionWithWrongInterval() {
-        notePersistentAppCrash(RescueParty.TRIGGER_COUNT - 1);
-
-        // last persistent app crash is just outside of the boot loop detection window
-        doReturn(CURRENT_NETWORK_TIME_MILLIS
-                + RescueParty.PERSISTENT_APP_CRASH_TRIGGER_WINDOW_MILLIS + 1)
-                .when(() -> RescueParty.getElapsedRealtime());
-        notePersistentAppCrash(/*numTimes=*/1);
-
-        assertEquals(RescueParty.LEVEL_NONE,
-                SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
-    }
-
-    @Test
     public void testBootLoopDetectionWithProperInterval() {
         noteBoot(RescueParty.TRIGGER_COUNT - 1);
 
@@ -249,21 +240,6 @@
     }
 
     @Test
-    public void testPersistentAppCrashDetectionWithProperInterval() {
-        notePersistentAppCrash(RescueParty.TRIGGER_COUNT - 1);
-
-        // last persistent app crash is just inside of the boot loop detection window
-        doReturn(CURRENT_NETWORK_TIME_MILLIS
-                + RescueParty.PERSISTENT_APP_CRASH_TRIGGER_WINDOW_MILLIS)
-                .when(() -> RescueParty.getElapsedRealtime());
-        notePersistentAppCrash(/*numTimes=*/1);
-
-        verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS);
-        assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS,
-                SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
-    }
-
-    @Test
     public void testBootLoopDetectionWithWrongTriggerCount() {
         noteBoot(RescueParty.TRIGGER_COUNT - 1);
         assertEquals(RescueParty.LEVEL_NONE,
@@ -271,13 +247,6 @@
     }
 
     @Test
-    public void testPersistentAppCrashDetectionWithWrongTriggerCount() {
-        notePersistentAppCrash(RescueParty.TRIGGER_COUNT - 1);
-        assertEquals(RescueParty.LEVEL_NONE,
-                SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
-    }
-
-    @Test
     public void testIsAttemptingFactoryReset() {
         noteBoot(RescueParty.TRIGGER_COUNT * 4);
 
@@ -319,6 +288,77 @@
                         FAKE_NATIVE_NAMESPACE2, /*makeDefault=*/true));
     }
 
+    @Test
+    public void testExplicitlyEnablingAndDisablingRescue() {
+        SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(false));
+        SystemProperties.set(PROP_DISABLE_RESCUE, Boolean.toString(true));
+        assertEquals(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage,
+                PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING), false);
+
+        SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true));
+        assertTrue(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage,
+                PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING));
+    }
+
+    @Test
+    public void testHealthCheckLevels() {
+        RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
+
+        // Ensure that no action is taken for cases where the failure reason is unknown
+        SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString(
+                RescueParty.LEVEL_FACTORY_RESET));
+        assertEquals(observer.onHealthCheckFailed(null, PackageWatchdog.FAILURE_REASON_UNKNOWN),
+                PackageHealthObserverImpact.USER_IMPACT_NONE);
+
+        /*
+        For the following cases, ensure that the returned user impact corresponds with the user
+        impact of the next available rescue level, not the current one.
+         */
+        SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString(
+                RescueParty.LEVEL_NONE));
+        assertEquals(observer.onHealthCheckFailed(null,
+                PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING),
+                PackageHealthObserverImpact.USER_IMPACT_LOW);
+
+        SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString(
+                RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS));
+        assertEquals(observer.onHealthCheckFailed(null,
+                PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING),
+                PackageHealthObserverImpact.USER_IMPACT_LOW);
+
+
+        SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString(
+                RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES));
+        assertEquals(observer.onHealthCheckFailed(null,
+                PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING),
+                PackageHealthObserverImpact.USER_IMPACT_HIGH);
+
+
+        SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString(
+                RescueParty.LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS));
+        assertEquals(observer.onHealthCheckFailed(null,
+                PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING),
+                PackageHealthObserverImpact.USER_IMPACT_HIGH);
+
+
+        SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString(
+                RescueParty.LEVEL_FACTORY_RESET));
+        assertEquals(observer.onHealthCheckFailed(null,
+                PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING),
+                PackageHealthObserverImpact.USER_IMPACT_HIGH);
+    }
+
+    @Test
+    public void testRescueLevelIncrementsWhenExecuted() {
+        RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
+        SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString(
+                RescueParty.LEVEL_NONE));
+        observer.execute(sFailingPackage,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH);
+        assertEquals(SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, -1),
+                RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS);
+    }
+
     private void verifySettingsResets(int resetMode) {
         verify(() -> Settings.Global.resetToDefaultsAsUser(mMockContentResolver, null,
                 resetMode, UserHandle.USER_SYSTEM));
@@ -332,9 +372,8 @@
         }
     }
 
-    private void notePersistentAppCrash(int numTimes) {
-        for (int i = 0; i < numTimes; i++) {
-            RescueParty.noteAppCrash(mMockContext, PERSISTENT_APP_UID);
-        }
+    private void notePersistentAppCrash() {
+        RescuePartyObserver.getInstance(mMockContext).execute(new VersionedPackage(
+                "com.package.name", 1), PackageWatchdog.FAILURE_REASON_UNKNOWN);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
index 1829fb7..2fb2021 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
@@ -29,6 +29,7 @@
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
@@ -56,7 +57,6 @@
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
-import android.net.wifi.WifiSsid;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -182,8 +182,8 @@
     }
 
     private ScanResult createScanResult(String ssid, String bssid) {
-        ScanResult result = new ScanResult();
-        result.wifiSsid = WifiSsid.createFromAsciiEncoded(ssid);
+        ScanResult result = mock(ScanResult.class);
+        result.SSID = ssid;
         result.BSSID = bssid;
         return result;
     }
@@ -794,7 +794,7 @@
     @Test
     public void testScanResultsScoreCacheFilter_invalidScanResults() throws Exception {
         List<ScanResult> invalidScanResults = Lists.newArrayList(
-                new ScanResult(),
+                mock(ScanResult.class),
                 createScanResult("", SCORED_NETWORK.networkKey.wifiKey.bssid),
                 createScanResult(WifiManager.UNKNOWN_SSID, SCORED_NETWORK.networkKey.wifiKey.bssid),
                 createScanResult(SSID, null),
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
index d11d987..591c3a3 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
@@ -85,6 +85,7 @@
 import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
 
 import com.android.server.accessibility.AccessibilityWindowManager.RemoteAccessibilityConnection;
+import com.android.server.accessibility.test.MessageCapturingHandler;
 import com.android.server.wm.WindowManagerInternal;
 
 import org.junit.Before;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
new file mode 100644
index 0000000..75239db
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2019 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.accessibility;
+
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.Manifest;
+import android.app.PendingIntent;
+import android.app.RemoteAction;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Icon;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.server.LocalServices;
+import com.android.server.accessibility.AccessibilityManagerService.AccessibilityDisplayListener;
+import com.android.server.wm.ActivityTaskManagerInternal;
+import com.android.server.wm.WindowManagerInternal;
+
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * APCT tests for {@link AccessibilityManagerService}.
+ */
+public class AccessibilityManagerServiceTest extends AndroidTestCase {
+    private static final String TAG = "A11Y_MANAGER_SERVICE_TEST";
+    private static final int ACTION_ID = 20;
+    private static final String LABEL = "label";
+    private static final String INTENT_ACTION = "TESTACTION";
+    private static final String DESCRIPTION = "description";
+    private static final PendingIntent TEST_PENDING_INTENT = PendingIntent.getBroadcast(
+            InstrumentationRegistry.getTargetContext(), 0, new Intent(INTENT_ACTION), 0);
+    private static final RemoteAction TEST_ACTION = new RemoteAction(
+            Icon.createWithContentUri("content://test"),
+            LABEL,
+            DESCRIPTION,
+            TEST_PENDING_INTENT);
+    private static final AccessibilityAction NEW_ACCESSIBILITY_ACTION =
+            new AccessibilityAction(ACTION_ID, LABEL);
+
+    @Mock private PackageManager mMockPackageManager;
+    @Mock private WindowManagerInternal mMockWindowManagerService;
+    @Mock private AccessibilitySecurityPolicy mMockSecurityPolicy;
+    @Mock private SystemActionPerformer mMockSystemActionPerformer;
+    @Mock private AccessibilityWindowManager mMockA11yWindowManager;
+    @Mock private AccessibilityDisplayListener mMockA11yDisplayListener;
+    @Mock private ActivityTaskManagerInternal mMockActivityTaskManagerInternal;
+
+    private AccessibilityManagerService mA11yms;
+
+    @Override
+    protected void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        LocalServices.removeServiceForTest(WindowManagerInternal.class);
+        LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
+        LocalServices.addService(WindowManagerInternal.class, mMockWindowManagerService);
+        LocalServices.addService(
+                ActivityTaskManagerInternal.class, mMockActivityTaskManagerInternal);
+        mA11yms = new AccessibilityManagerService(
+            InstrumentationRegistry.getContext(),
+            mMockPackageManager,
+            mMockSecurityPolicy,
+            mMockSystemActionPerformer,
+            mMockA11yWindowManager,
+            mMockA11yDisplayListener);
+    }
+
+    @SmallTest
+    public void testRegisterSystemActionWithoutPermission() throws Exception {
+        doThrow(SecurityException.class).when(mMockSecurityPolicy).enforceCallingPermission(
+                Manifest.permission.MANAGE_ACCESSIBILITY,
+                AccessibilityManagerService.FUNCTION_REGISTER_SYSTEM_ACTION);
+
+        try {
+            mA11yms.registerSystemAction(TEST_ACTION, ACTION_ID);
+            fail();
+        } catch (SecurityException expected) {
+        }
+        verify(mMockSystemActionPerformer, never()).registerSystemAction(ACTION_ID, TEST_ACTION);
+    }
+
+    @SmallTest
+    public void testRegisterSystemAction() throws Exception {
+        mA11yms.registerSystemAction(TEST_ACTION, ACTION_ID);
+        verify(mMockSystemActionPerformer).registerSystemAction(ACTION_ID, TEST_ACTION);
+    }
+
+    @SmallTest
+    public void testUnregisterSystemActionWithoutPermission() throws Exception {
+        doThrow(SecurityException.class).when(mMockSecurityPolicy).enforceCallingPermission(
+                Manifest.permission.MANAGE_ACCESSIBILITY,
+                AccessibilityManagerService.FUNCTION_UNREGISTER_SYSTEM_ACTION);
+
+        try {
+            mA11yms.unregisterSystemAction(ACTION_ID);
+            fail();
+        } catch (SecurityException expected) {
+        }
+        verify(mMockSystemActionPerformer, never()).unregisterSystemAction(ACTION_ID);
+    }
+
+    @SmallTest
+    public void testUnregisterSystemAction() throws Exception {
+        mA11yms.unregisterSystemAction(ACTION_ID);
+        verify(mMockSystemActionPerformer).unregisterSystemAction(ACTION_ID);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
index 99dd9a1..2ce70b6f 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
@@ -44,6 +44,7 @@
 import android.testing.DexmakerShareClassLoaderRule;
 import android.view.Display;
 
+import com.android.server.accessibility.test.MessageCapturingHandler;
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
index 67075ed..9db5a08 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
@@ -53,6 +53,7 @@
 import android.view.accessibility.IAccessibilityInteractionConnection;
 
 import com.android.server.accessibility.AccessibilityWindowManager.RemoteAccessibilityConnection;
+import com.android.server.accessibility.test.MessageCapturingHandler;
 import com.android.server.wm.WindowManagerInternal;
 import com.android.server.wm.WindowManagerInternal.WindowsForAccessibilityCallback;
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureControllerTest.java
index 44a514f..96ae102e5 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureControllerTest.java
@@ -31,6 +31,8 @@
 import android.accessibilityservice.FingerprintGestureController.FingerprintGestureCallback;
 import android.accessibilityservice.IAccessibilityServiceConnection;
 
+import com.android.server.accessibility.test.MessageCapturingHandler;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mock;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureDispatcherTest.java b/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureDispatcherTest.java
index de7bc44..30d00ad 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureDispatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureDispatcherTest.java
@@ -31,6 +31,7 @@
 import android.view.KeyEvent;
 
 import com.android.server.accessibility.FingerprintGestureDispatcher.FingerprintGestureClient;
+import com.android.server.accessibility.test.MessageCapturingHandler;
 
 import org.junit.After;
 import org.junit.Before;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/KeyEventDispatcherTest.java b/services/tests/servicestests/src/com/android/server/accessibility/KeyEventDispatcherTest.java
index 23ce483..4123556 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/KeyEventDispatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/KeyEventDispatcherTest.java
@@ -44,6 +44,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.accessibility.KeyEventDispatcher.KeyEventFilter;
+import com.android.server.accessibility.test.MessageCapturingHandler;
 import com.android.server.policy.WindowManagerPolicy;
 
 import org.hamcrest.Description;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java
index 322653b..78e651b 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java
@@ -33,6 +33,7 @@
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.accessibility.test.MessageCapturingHandler;
 import com.android.server.policy.WindowManagerPolicy;
 
 import org.hamcrest.Description;
@@ -212,4 +213,4 @@
             description.appendText("Matches key event");
         }
     }
-}
\ No newline at end of file
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java
index 773b877..82c6498 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java
@@ -47,6 +47,7 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.accessibility.test.MessageCapturingHandler;
 import com.android.server.wm.WindowManagerInternal;
 import com.android.server.wm.WindowManagerInternal.MagnificationCallbacks;
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java
index 36e854c..1ac4a8e 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java
@@ -54,6 +54,7 @@
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.accessibility.test.MessageCapturingHandler;
 import com.android.server.accessibility.utils.MotionEventMatcher;
 
 import org.hamcrest.Description;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
index 8da927d..c8baca6 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
@@ -37,6 +37,7 @@
 import android.os.IBinder;
 import android.view.accessibility.AccessibilityEvent;
 
+import com.android.server.accessibility.test.MessageCapturingHandler;
 import com.android.server.wm.WindowManagerInternal;
 
 import org.junit.After;
diff --git a/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java
index 2326dfd..d44476e 100644
--- a/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
@@ -59,6 +60,7 @@
 import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -83,6 +85,8 @@
     @Mock
     private UserBackupManagerService mUserBackupManagerService;
     @Mock
+    private UserBackupManagerService mNonSystemUserBackupManagerService;
+    @Mock
     private Context mContextMock;
     @Mock
     private PrintWriter mPrintWriterMock;
@@ -105,7 +109,7 @@
 
         mUserServices = new SparseArray<>();
         mUserServices.append(UserHandle.USER_SYSTEM, mUserBackupManagerService);
-        mUserServices.append(NON_USER_SYSTEM, mUserBackupManagerService);
+        mUserServices.append(NON_USER_SYSTEM, mNonSystemUserBackupManagerService);
 
         when(mUserManagerMock.getUserInfo(UserHandle.USER_SYSTEM)).thenReturn(mUserInfoMock);
         when(mUserManagerMock.getUserInfo(NON_USER_SYSTEM)).thenReturn(mUserInfoMock);
@@ -512,6 +516,26 @@
         mService.dump(mFileDescriptorStub, mPrintWriterMock, new String[0]);
 
         verifyNoMoreInteractions(mUserBackupManagerService);
+        verifyNoMoreInteractions(mNonSystemUserBackupManagerService);
+    }
+
+    /**
+     * Test that {@link BackupManagerService#dump()} dumps system user information before non-system
+     * user information.
+     */
+
+    @Test
+    public void testDump_systemUserFirst() {
+        String[] args = new String[0];
+        mService.dumpWithoutCheckingPermission(mFileDescriptorStub, mPrintWriterMock, args);
+
+        InOrder inOrder =
+                inOrder(mUserBackupManagerService, mNonSystemUserBackupManagerService);
+        inOrder.verify(mUserBackupManagerService)
+                .dump(mFileDescriptorStub, mPrintWriterMock, args);
+        inOrder.verify(mNonSystemUserBackupManagerService)
+                .dump(mFileDescriptorStub, mPrintWriterMock, args);
+        inOrder.verifyNoMoreInteractions();
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/integrity/model/BitTrackedInputStreamTest.java b/services/tests/servicestests/src/com/android/server/integrity/model/BitTrackedInputStreamTest.java
new file mode 100644
index 0000000..1eb5eb5
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/integrity/model/BitTrackedInputStreamTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2020 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.integrity.model;
+
+import static com.android.server.integrity.model.ComponentBitSize.ATOMIC_FORMULA_START;
+import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_END;
+import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_START;
+import static com.android.server.integrity.model.ComponentBitSize.CONNECTOR_BITS;
+import static com.android.server.integrity.model.ComponentBitSize.EFFECT_BITS;
+import static com.android.server.integrity.model.ComponentBitSize.KEY_BITS;
+import static com.android.server.integrity.model.ComponentBitSize.OPERATOR_BITS;
+import static com.android.server.integrity.model.ComponentBitSize.SEPARATOR_BITS;
+import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS;
+import static com.android.server.integrity.utils.TestUtils.getBits;
+import static com.android.server.integrity.utils.TestUtils.getBytes;
+import static com.android.server.integrity.utils.TestUtils.getValueBits;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.content.integrity.AtomicFormula;
+import android.content.integrity.CompoundFormula;
+import android.content.integrity.Rule;
+
+import com.android.server.integrity.parser.BinaryFileOperations;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.IOException;
+
+@RunWith(JUnit4.class)
+public class BitTrackedInputStreamTest {
+    private static final String COMPOUND_FORMULA_START_BITS =
+            getBits(COMPOUND_FORMULA_START, SEPARATOR_BITS);
+    private static final String COMPOUND_FORMULA_END_BITS =
+            getBits(COMPOUND_FORMULA_END, SEPARATOR_BITS);
+    private static final String ATOMIC_FORMULA_START_BITS =
+            getBits(ATOMIC_FORMULA_START, SEPARATOR_BITS);
+    private static final String NOT = getBits(CompoundFormula.NOT, CONNECTOR_BITS);
+    private static final String PACKAGE_NAME = getBits(AtomicFormula.PACKAGE_NAME, KEY_BITS);
+    private static final String EQ = getBits(AtomicFormula.EQ, OPERATOR_BITS);
+    private static final String DENY = getBits(Rule.DENY, EFFECT_BITS);
+
+    private static final String IS_NOT_HASHED = "0";
+    private static final String START_BIT = "1";
+    private static final String END_BIT = "1";
+
+    @Test
+    public void testBitOperationsCountBitsCorrectly() throws IOException {
+        String packageName = "com.test.app";
+        byte[] testInput =
+                getBytes(
+                        START_BIT
+                                + COMPOUND_FORMULA_START_BITS
+                                + NOT
+                                + ATOMIC_FORMULA_START_BITS
+                                + PACKAGE_NAME
+                                + EQ
+                                + IS_NOT_HASHED
+                                + getBits(packageName.length(), VALUE_SIZE_BITS)
+                                + getValueBits(packageName)
+                                + COMPOUND_FORMULA_END_BITS
+                                + DENY
+                                + END_BIT);
+
+        BitTrackedInputStream bitTrackedInputStream = new BitTrackedInputStream(testInput);
+
+        // Right after construction, the read bits count should be 0.
+        assertThat(bitTrackedInputStream.getReadBitsCount()).isEqualTo(0);
+
+        // Get next 10 bits should result with 10 bits read.
+        bitTrackedInputStream.getNext(10);
+        assertThat(bitTrackedInputStream.getReadBitsCount()).isEqualTo(10);
+
+        // When we move the cursor 8 bytes, we should point to 64 bits.
+        bitTrackedInputStream.setCursorToByteLocation(8);
+        assertThat(bitTrackedInputStream.getReadBitsCount()).isEqualTo(64);
+
+        // Read until the end and the total size of the input stream should be available.
+        while (bitTrackedInputStream.hasNext()) {
+            bitTrackedInputStream.getNext(1);
+        }
+        assertThat(bitTrackedInputStream.getReadBitsCount()).isEqualTo(128);
+    }
+
+    @Test
+    public void testBitInputStreamOperationsStillWork() throws IOException {
+        String packageName = "com.test.app";
+        byte[] testInput =
+                getBytes(
+                        IS_NOT_HASHED
+                                + getBits(packageName.length(), VALUE_SIZE_BITS)
+                                + getValueBits(packageName));
+
+        BitTrackedInputStream bitTrackedInputStream = new BitTrackedInputStream(testInput);
+        assertThat(bitTrackedInputStream.getReadBitsCount()).isEqualTo(0);
+
+        // Read until the string parameter.
+        String stringValue = BinaryFileOperations.getStringValue(bitTrackedInputStream);
+
+        // Verify that the read bytes are counted.
+        assertThat(stringValue).isEqualTo(packageName);
+        assertThat(bitTrackedInputStream.getReadBitsCount()).isGreaterThan(0);
+    }
+
+    @Test
+    public void testBitTrackedInputStream_moveCursorForwardFailsIfAlreadyRead() throws IOException {
+        String packageName = "com.test.app";
+        byte[] testInput =
+                getBytes(
+                        IS_NOT_HASHED
+                                + getBits(packageName.length(), VALUE_SIZE_BITS)
+                                + getValueBits(packageName));
+
+        BitTrackedInputStream bitTrackedInputStream = new BitTrackedInputStream(testInput);
+
+        // Read more than two bytes.
+        bitTrackedInputStream.getNext(20);
+
+        // Ask to move the cursor to the second byte.
+        assertThrows(
+                IllegalStateException.class,
+                () -> bitTrackedInputStream.setCursorToByteLocation(2));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/ByteTrackedOutputStreamTest.java b/services/tests/servicestests/src/com/android/server/integrity/model/ByteTrackedOutputStreamTest.java
similarity index 95%
rename from services/tests/servicestests/src/com/android/server/integrity/serializer/ByteTrackedOutputStreamTest.java
rename to services/tests/servicestests/src/com/android/server/integrity/model/ByteTrackedOutputStreamTest.java
index 5ecb8b5c..c7cc343 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/serializer/ByteTrackedOutputStreamTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/model/ByteTrackedOutputStreamTest.java
@@ -14,12 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.server.integrity.serializer;
+package com.android.server.integrity.model;
 
 import static com.google.common.truth.Truth.assertThat;
 
-import com.android.server.integrity.model.BitOutputStream;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java
index 9cc0ed8..51f5c75 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java
@@ -48,6 +48,7 @@
 import java.io.InputStream;
 import java.nio.ByteBuffer;
 import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
@@ -97,8 +98,10 @@
     private static final byte[] DEFAULT_FORMAT_VERSION_BYTES =
             getBytes(getBits(DEFAULT_FORMAT_VERSION, FORMAT_VERSION_BITS));
 
+    private static final List<RuleIndexRange> NO_INDEXING = Collections.emptyList();
+
     @Test
-    public void testBinaryStream_validCompoundFormula() throws Exception {
+    public void testBinaryStream_validCompoundFormula_noIndexing() throws Exception {
         String packageName = "com.test.app";
         String ruleBits =
                 START_BIT
@@ -131,13 +134,13 @@
                                                 /* isHashedValue= */ false))),
                         Rule.DENY);
 
-        List<Rule> rules = binaryParser.parse(inputStream);
+        List<Rule> rules = binaryParser.parse(inputStream, NO_INDEXING);
 
         assertThat(rules).isEqualTo(Collections.singletonList(expectedRule));
     }
 
     @Test
-    public void testBinaryString_validCompoundFormula_notConnector() throws Exception {
+    public void testBinaryString_validCompoundFormula_notConnector_noIndexing() throws Exception {
         String packageName = "com.test.app";
         String ruleBits =
                 START_BIT
@@ -175,7 +178,7 @@
     }
 
     @Test
-    public void testBinaryString_validCompoundFormula_andConnector() throws Exception {
+    public void testBinaryString_validCompoundFormula_andConnector_noIndexing() throws Exception {
         String packageName = "com.test.app";
         String appCertificate = "test_cert";
         String ruleBits =
@@ -223,7 +226,7 @@
     }
 
     @Test
-    public void testBinaryString_validCompoundFormula_orConnector() throws Exception {
+    public void testBinaryString_validCompoundFormula_orConnector_noIndexing() throws Exception {
         String packageName = "com.test.app";
         String appCertificate = "test_cert";
         String ruleBits =
@@ -272,7 +275,7 @@
     }
 
     @Test
-    public void testBinaryString_validAtomicFormula_stringValue() throws Exception {
+    public void testBinaryString_validAtomicFormula_stringValue_noIndexing() throws Exception {
         String packageName = "com.test.app";
         String ruleBits =
                 START_BIT
@@ -304,7 +307,7 @@
     }
 
     @Test
-    public void testBinaryString_validAtomicFormula_hashedValue() throws Exception {
+    public void testBinaryString_validAtomicFormula_hashedValue_noIndexing() throws Exception {
         String appCertificate = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
         String ruleBits =
                 START_BIT
@@ -337,7 +340,7 @@
     }
 
     @Test
-    public void testBinaryString_validAtomicFormula_integerValue() throws Exception {
+    public void testBinaryString_validAtomicFormula_integerValue_noIndexing() throws Exception {
         int versionCode = 1;
         String ruleBits =
                 START_BIT
@@ -365,7 +368,7 @@
     }
 
     @Test
-    public void testBinaryString_validAtomicFormula_booleanValue() throws Exception {
+    public void testBinaryString_validAtomicFormula_booleanValue_noIndexing() throws Exception {
         String isPreInstalled = "1";
         String ruleBits =
                 START_BIT
@@ -392,7 +395,7 @@
     }
 
     @Test
-    public void testBinaryString_invalidAtomicFormula() throws Exception {
+    public void testBinaryString_invalidAtomicFormula_noIndexing() {
         int versionCode = 1;
         String ruleBits =
                 START_BIT
@@ -415,7 +418,7 @@
     }
 
     @Test
-    public void testBinaryString_withNoRuleList() throws RuleParseException {
+    public void testBinaryString_withNoRuleList_noIndexing() throws RuleParseException {
         ByteBuffer rule = ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length);
         rule.put(DEFAULT_FORMAT_VERSION_BYTES);
         RuleParser binaryParser = new RuleBinaryParser();
@@ -426,7 +429,7 @@
     }
 
     @Test
-    public void testBinaryString_withEmptyRule() throws RuleParseException {
+    public void testBinaryString_withEmptyRule_noIndexing() {
         String ruleBits = START_BIT;
         byte[] ruleBytes = getBytes(ruleBits);
         ByteBuffer rule =
@@ -442,7 +445,7 @@
     }
 
     @Test
-    public void testBinaryString_invalidCompoundFormula_invalidNumberOfFormulas() throws Exception {
+    public void testBinaryString_invalidCompoundFormula_invalidNumberOfFormulas_noIndexing() {
         String packageName = "com.test.app";
         String appCertificate = "test_cert";
         String ruleBits =
@@ -478,7 +481,7 @@
     }
 
     @Test
-    public void testBinaryString_invalidRule_invalidOperator() throws Exception {
+    public void testBinaryString_invalidRule_invalidOperator_noIndexing() {
         int versionCode = 1;
         String ruleBits =
                 START_BIT
@@ -506,7 +509,7 @@
     }
 
     @Test
-    public void testBinaryString_invalidRule_invalidEffect() throws Exception {
+    public void testBinaryString_invalidRule_invalidEffect_noIndexing() {
         String packageName = "com.test.app";
         String ruleBits =
                 START_BIT
@@ -536,7 +539,7 @@
     }
 
     @Test
-    public void testBinaryString_invalidRule_invalidConnector() throws Exception {
+    public void testBinaryString_invalidRule_invalidConnector_noIndexing() {
         String packageName = "com.test.app";
         String ruleBits =
                 START_BIT
@@ -566,7 +569,7 @@
     }
 
     @Test
-    public void testBinaryString_invalidRule_invalidKey() throws Exception {
+    public void testBinaryString_invalidRule_invalidKey_noIndexing() {
         String packageName = "com.test.app";
         String ruleBits =
                 START_BIT
@@ -596,7 +599,7 @@
     }
 
     @Test
-    public void testBinaryString_invalidRule_invalidSeparator() throws Exception {
+    public void testBinaryString_invalidRule_invalidSeparator_noIndexing() {
         String packageName = "com.test.app";
         String ruleBits =
                 START_BIT
@@ -626,7 +629,7 @@
     }
 
     @Test
-    public void testBinaryString_invalidRule_invalidEndMarker() throws Exception {
+    public void testBinaryString_invalidRule_invalidEndMarker_noIndexing() {
         String packageName = "com.test.app";
         String ruleBits =
                 START_BIT
@@ -653,4 +656,65 @@
                 /* expectedExceptionMessageRegex */ "A rule must end with a '1' bit",
                 () -> binaryParser.parse(rule.array()));
     }
+
+    @Test
+    public void testBinaryStream_multipleRules_indexingIdentifiesParsesIndexRangeCorrectly()
+            throws Exception {
+        String packageName2 = "com.test.2";
+
+        byte[] ruleBytes1 = getBytes(getRulesWithPackageName("com.test.1"));
+        byte[] ruleBytes2 = getBytes(getRulesWithPackageName(packageName2));
+        byte[] ruleBytes3 = getBytes(getRulesWithPackageName("com.test.3"));
+
+        ByteBuffer rule =
+                ByteBuffer.allocate(
+                        DEFAULT_FORMAT_VERSION_BYTES.length
+                                + ruleBytes1.length
+                                + ruleBytes2.length
+                                + ruleBytes3.length);
+        rule.put(DEFAULT_FORMAT_VERSION_BYTES);
+        rule.put(ruleBytes1);
+        rule.put(ruleBytes2);
+        rule.put(ruleBytes3);
+        InputStream inputStream = new ByteArrayInputStream(rule.array());
+
+        RuleParser binaryParser = new RuleBinaryParser();
+
+        List<RuleIndexRange> indexRanges = new ArrayList<>();
+        indexRanges.add(
+                new RuleIndexRange(
+                        DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes1.length,
+                        DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes1.length
+                                + ruleBytes2.length));
+        List<Rule> rules = binaryParser.parse(inputStream, indexRanges);
+
+        Rule expectedRule =
+                new Rule(
+                        new CompoundFormula(
+                                CompoundFormula.NOT,
+                                Collections.singletonList(
+                                        new AtomicFormula.StringAtomicFormula(
+                                                AtomicFormula.PACKAGE_NAME,
+                                                packageName2,
+                                                /* isHashedValue= */ false))),
+                        Rule.DENY);
+
+        assertThat(rules).isEqualTo(Collections.singletonList(expectedRule));
+    }
+
+    private static String getRulesWithPackageName(String packageName) {
+        return START_BIT
+                + COMPOUND_FORMULA_START_BITS
+                + NOT
+                + ATOMIC_FORMULA_START_BITS
+                + PACKAGE_NAME
+                + EQ
+                + IS_NOT_HASHED
+                + getBits(packageName.length(), VALUE_SIZE_BITS)
+                + getValueBits(packageName)
+                + COMPOUND_FORMULA_END_BITS
+                + DENY
+                + END_BIT;
+
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleXmlParserTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleXmlParserTest.java
index a14197b..6944aee 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleXmlParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleXmlParserTest.java
@@ -73,7 +73,7 @@
                                                 /* isHashedValue= */ false))),
                         Rule.DENY);
 
-        List<Rule> rules = xmlParser.parse(inputStream);
+        List<Rule> rules = xmlParser.parse(inputStream, Collections.emptyList());
 
         assertThat(rules).isEqualTo(Collections.singletonList(expectedRule));
     }
@@ -623,7 +623,7 @@
         assertExpectException(
                 RuleParseException.class,
                 /* expectedExceptionMessageRegex */ "Rules must start with RuleList <RL> tag",
-                () -> xmlParser.parse(inputStream));
+                () -> xmlParser.parse(inputStream, Collections.emptyList()));
     }
 
     private String generateTagWithAttribute(
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
index c478ec4..15327b6 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
@@ -156,8 +156,7 @@
         if (isMultiPackage) {
             params.isMultiPackage = true;
         }
-        InstallSource installSource = InstallSource.create("testInstaller", null, "testInstaller",
-                false);
+        InstallSource installSource = InstallSource.create("testInstaller", null, "testInstaller");
         return new PackageInstallerSession(
                 /* callback */ null,
                 /* context */null,
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
index f8915c0..0f75816 100644
--- a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
@@ -155,7 +155,16 @@
         return properties;
     }
 
-    private static void validateDefaultProperties(SoundTriggerModuleProperties properties,
+    private static android.hardware.soundtrigger.V2_3.Properties createDefaultProperties_2_3(
+            boolean supportConcurrentCapture) {
+        android.hardware.soundtrigger.V2_3.Properties properties =
+                new android.hardware.soundtrigger.V2_3.Properties();
+        properties.base = createDefaultProperties(supportConcurrentCapture);
+        properties.supportedModelArch = "supportedModelArch";
+        return properties;
+    }
+
+    private void validateDefaultProperties(SoundTriggerModuleProperties properties,
             boolean supportConcurrentCapture) {
         assertEquals("implementor", properties.implementor);
         assertEquals("description", properties.description);
@@ -173,8 +182,20 @@
         assertEquals(supportConcurrentCapture, properties.concurrentCapture);
         assertTrue(properties.triggerInEvent);
         assertEquals(432, properties.powerConsumptionMw);
+
+        if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
+            assertEquals("supportedModelArch", properties.supportedModelArch);
+        } else {
+            assertEquals("", properties.supportedModelArch);
+        }
     }
 
+    private void verifyNotGetProperties() throws RemoteException {
+        if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
+            verify((android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver,
+                    never()).getProperties(any());
+        }
+    }
 
     private static android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent createRecognitionEvent_2_0(
             int hwHandle,
@@ -290,6 +311,22 @@
                     properties);
             return null;
         }).when(mHalDriver).getProperties(any());
+
+        if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
+            android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver =
+                    (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver;
+            doAnswer(invocation -> {
+                android.hardware.soundtrigger.V2_3.Properties properties =
+                        createDefaultProperties_2_3(
+                                supportConcurrentCapture);
+                ((android.hardware.soundtrigger.V2_3.ISoundTriggerHw.getProperties_2_3Callback)
+                        invocation.getArgument(
+                        0)).onValues(0,
+                        properties);
+                return null;
+            }).when(driver).getProperties_2_3(any());
+        }
+
         mService = new SoundTriggerMiddlewareImpl(mHalDriver, mAudioSessionProvider);
     }
 
@@ -716,6 +753,7 @@
         SoundTriggerModuleProperties properties = allDescriptors[0].properties;
 
         validateDefaultProperties(properties, true);
+        verifyNotGetProperties();
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
index 12ba219..1dd7e64 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -365,8 +365,9 @@
     public void testSetAppStandbyBucket() throws Exception {
         // For a known package, standby bucket should be set properly
         reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
+        mInjector.mElapsedRealtime = HOUR_MS;
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
-                REASON_MAIN_TIMEOUT, HOUR_MS);
+                REASON_MAIN_TIMEOUT);
         assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
 
         // For an unknown package, standby bucket should not be set, hence NEVER is returned
@@ -374,7 +375,7 @@
         mController.clearAppIdleForPackage(PACKAGE_UNKNOWN, USER_ID);
         isPackageInstalled = false; // Mock package is not installed
         mController.setAppStandbyBucket(PACKAGE_UNKNOWN, USER_ID, STANDBY_BUCKET_ACTIVE,
-                REASON_MAIN_TIMEOUT, HOUR_MS);
+                REASON_MAIN_TIMEOUT);
         isPackageInstalled = true; // Reset mocked variable for other tests
         assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController, PACKAGE_UNKNOWN));
     }
@@ -468,12 +469,13 @@
     @Test
     public void testPredictionTimedout() throws Exception {
         // Set it to timeout or usage, so that prediction can override it
+        mInjector.mElapsedRealtime = HOUR_MS;
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
-                REASON_MAIN_TIMEOUT, HOUR_MS);
+                REASON_MAIN_TIMEOUT);
         assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
 
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
-                REASON_MAIN_PREDICTED, HOUR_MS);
+                REASON_MAIN_PREDICTED);
         assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
 
         // Fast forward 12 hours
@@ -497,29 +499,31 @@
     @Test
     public void testOverrides() throws Exception {
         // Can force to NEVER
+        mInjector.mElapsedRealtime = HOUR_MS;
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER,
-                REASON_MAIN_FORCED, 1 * HOUR_MS);
+                REASON_MAIN_FORCED);
         assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController, PACKAGE_1));
 
         // Prediction can't override FORCED reason
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
-                REASON_MAIN_FORCED, 1 * HOUR_MS);
+                REASON_MAIN_FORCED);
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
-                REASON_MAIN_PREDICTED, 1 * HOUR_MS);
+                REASON_MAIN_PREDICTED);
         assertEquals(STANDBY_BUCKET_FREQUENT, getStandbyBucket(mController, PACKAGE_1));
 
         // Prediction can't override NEVER
+        mInjector.mElapsedRealtime = 2 * HOUR_MS;
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER,
-                REASON_MAIN_DEFAULT, 2 * HOUR_MS);
+                REASON_MAIN_DEFAULT);
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
-                REASON_MAIN_PREDICTED, 2 * HOUR_MS);
+                REASON_MAIN_PREDICTED);
         assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController, PACKAGE_1));
 
         // Prediction can't set to NEVER
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
-                REASON_MAIN_USAGE, 2 * HOUR_MS);
+                REASON_MAIN_USAGE);
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER,
-                REASON_MAIN_PREDICTED, 2 * HOUR_MS);
+                REASON_MAIN_PREDICTED);
         assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
     }
 
@@ -530,7 +534,7 @@
 
         mInjector.mElapsedRealtime = 2000;
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
-                REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime);
+                REASON_MAIN_PREDICTED);
         assertBucket(STANDBY_BUCKET_ACTIVE);
 
         // bucketing works after timeout
@@ -554,15 +558,17 @@
         assertBucket(STANDBY_BUCKET_ACTIVE);
 
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
-                REASON_MAIN_PREDICTED, 1000);
+                REASON_MAIN_PREDICTED);
         assertBucket(STANDBY_BUCKET_ACTIVE);
 
+        mInjector.mElapsedRealtime = 2000 + mController.mStrongUsageTimeoutMillis;
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
-                REASON_MAIN_PREDICTED, 2000 + mController.mStrongUsageTimeoutMillis);
+                REASON_MAIN_PREDICTED);
         assertBucket(STANDBY_BUCKET_WORKING_SET);
 
+        mInjector.mElapsedRealtime = 2000 + mController.mNotificationSeenTimeoutMillis;
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
-                REASON_MAIN_PREDICTED, 2000 + mController.mNotificationSeenTimeoutMillis);
+                REASON_MAIN_PREDICTED);
         assertBucket(STANDBY_BUCKET_FREQUENT);
     }
 
@@ -582,18 +588,18 @@
         // Still in ACTIVE after first USER_INTERACTION times out
         mInjector.mElapsedRealtime = mController.mStrongUsageTimeoutMillis + 1000;
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
-                REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime);
+                REASON_MAIN_PREDICTED);
         assertBucket(STANDBY_BUCKET_ACTIVE);
 
         // Both timed out, so NOTIFICATION_SEEN timeout should be effective
         mInjector.mElapsedRealtime = mController.mStrongUsageTimeoutMillis * 2 + 2000;
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
-                REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime);
+                REASON_MAIN_PREDICTED);
         assertBucket(STANDBY_BUCKET_WORKING_SET);
 
         mInjector.mElapsedRealtime = mController.mNotificationSeenTimeoutMillis + 2000;
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
-                REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime);
+                REASON_MAIN_PREDICTED);
         assertBucket(STANDBY_BUCKET_RARE);
     }
 
@@ -625,7 +631,7 @@
         mInjector.mElapsedRealtime = 1 * RARE_THRESHOLD + 100;
         // Make sure app is in NEVER bucket
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER,
-                REASON_MAIN_FORCED, mInjector.mElapsedRealtime);
+                REASON_MAIN_FORCED);
         mController.checkIdleStates(USER_ID);
         assertBucket(STANDBY_BUCKET_NEVER);
 
@@ -670,7 +676,7 @@
         // Predict to ACTIVE
         mInjector.mElapsedRealtime += 1000;
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
-                REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime);
+                REASON_MAIN_PREDICTED);
         assertBucket(STANDBY_BUCKET_ACTIVE);
 
         // CheckIdleStates should not change the prediction
@@ -687,7 +693,7 @@
         // Predict to FREQUENT
         mInjector.mElapsedRealtime = RARE_THRESHOLD;
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
-                REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime);
+                REASON_MAIN_PREDICTED);
         assertBucket(STANDBY_BUCKET_FREQUENT);
 
         // Add a short timeout event
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
index eb45960..bd7d9ec 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
@@ -20,7 +20,6 @@
 import static android.app.NotificationManager.IMPORTANCE_LOW;
 
 import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertNull;
 
 import static org.mockito.Matchers.any;
@@ -65,11 +64,10 @@
 
         NotificationChannel updatedChannel =
                 new NotificationChannel("a", "", IMPORTANCE_HIGH);
-        when(mConfig.getNotificationChannel(any(), anyInt(), eq("a"), eq(false)))
+        when(mConfig.getNotificationChannel(any(), anyInt(), eq("a"), eq(null), eq(false)))
                 .thenReturn(updatedChannel);
 
         assertNull(extractor.process(r));
         assertEquals(updatedChannel, r.getChannel());
     }
-
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 172df99..ae597e3 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -112,6 +112,7 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.Parcel;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -133,6 +134,7 @@
 import android.testing.TestablePermissions;
 import android.testing.TestableResources;
 import android.text.Html;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
@@ -475,7 +477,7 @@
         when(mPreferencesHelper.bubblesEnabled()).thenReturn(globalEnabled);
         when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(pkgEnabled);
         when(mPreferencesHelper.getNotificationChannel(
-                anyString(), anyInt(), anyString(), anyBoolean())).thenReturn(
+                anyString(), anyInt(), anyString(), eq(null), anyBoolean())).thenReturn(
                 mTestNotificationChannel);
         when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn(
                 mTestNotificationChannel.getImportance());
@@ -1762,7 +1764,7 @@
         mBinderService.enqueueNotificationWithTag(PKG, PKG, "testTvExtenderChannelOverride_onTv", 0,
                 generateNotificationRecord(null, tv).getNotification(), 0);
         verify(mPreferencesHelper, times(1)).getNotificationChannel(
-                anyString(), anyInt(), eq("foo"), anyBoolean());
+                anyString(), anyInt(), eq("foo"), eq(null), anyBoolean());
     }
 
     @Test
@@ -1777,7 +1779,8 @@
         mBinderService.enqueueNotificationWithTag(PKG, PKG, "testTvExtenderChannelOverride_notOnTv",
                 0, generateNotificationRecord(null, tv).getNotification(), 0);
         verify(mPreferencesHelper, times(1)).getNotificationChannel(
-                anyString(), anyInt(), eq(mTestNotificationChannel.getId()), anyBoolean());
+                anyString(), anyInt(), eq(mTestNotificationChannel.getId()), eq(null),
+                anyBoolean());
     }
 
     @Test
@@ -5044,10 +5047,9 @@
                 nb.build(), new UserHandle(mUid), null, 0);
         // Make sure it has foreground service
         sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
-        NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
         mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
-                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+                sbn.getId(), sbn.getNotification(), sbn.getUserId());
         waitForIdle();
 
         // yes phone call, yes person, yes foreground service, but not allowed, no bubble
@@ -5802,4 +5804,79 @@
 
         verify(mHistoryManager, times(1)).addNotification(any());
     }
+
+    @Test
+    public void createConversationNotificationChannel() throws Exception {
+        NotificationChannel original = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
+        original.setAllowBubbles(!original.canBubble());
+        original.setShowBadge(!original.canShowBadge());
+
+        Parcel parcel = Parcel.obtain();
+        original.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        NotificationChannel orig = NotificationChannel.CREATOR.createFromParcel(parcel);
+        assertEquals(original, orig);
+        assertFalse(TextUtils.isEmpty(orig.getName()));
+
+        mBinderService.createNotificationChannels(PKG, new ParceledListSlice(Arrays.asList(
+                orig)));
+
+        mBinderService.createConversationNotificationChannelForPackage(PKG, mUid, orig, "friend");
+
+        NotificationChannel friendChannel = mBinderService.getConversationNotificationChannel(
+                PKG, 0, PKG, original.getId(), "friend");
+
+        assertEquals(original.getName(), friendChannel.getName());
+        assertEquals(original.getId(), friendChannel.getParentChannelId());
+        assertEquals("friend", friendChannel.getConversationId());
+        assertEquals(null, original.getConversationId());
+        assertEquals(original.canShowBadge(), friendChannel.canShowBadge());
+        assertEquals(original.canBubble(), friendChannel.canBubble());
+        assertFalse(original.getId().equals(friendChannel.getId()));
+        assertNotNull(friendChannel.getId());
+    }
+
+    @Test
+    public void deleteConversationNotificationChannels() throws Exception {
+        NotificationChannel messagesParent =
+                new NotificationChannel("messages", "messages", IMPORTANCE_HIGH);
+        Parcel msgParcel = Parcel.obtain();
+        messagesParent.writeToParcel(msgParcel, 0);
+        msgParcel.setDataPosition(0);
+
+        NotificationChannel callsParent =
+                new NotificationChannel("calls", "calls", IMPORTANCE_HIGH);
+        Parcel callParcel = Parcel.obtain();
+        callsParent.writeToParcel(callParcel, 0);
+        callParcel.setDataPosition(0);
+
+        mBinderService.createNotificationChannels(PKG, new ParceledListSlice(Arrays.asList(
+                messagesParent, callsParent)));
+
+        String conversationId = "friend";
+
+        mBinderService.createConversationNotificationChannelForPackage(
+                PKG, mUid, NotificationChannel.CREATOR.createFromParcel(msgParcel), conversationId);
+        mBinderService.createConversationNotificationChannelForPackage(
+                PKG, mUid, NotificationChannel.CREATOR.createFromParcel(callParcel),
+                conversationId);
+
+        NotificationChannel messagesChild = mBinderService.getConversationNotificationChannel(
+                PKG, 0, PKG, messagesParent.getId(), conversationId);
+        NotificationChannel callsChild = mBinderService.getConversationNotificationChannel(
+                PKG, 0, PKG, callsParent.getId(), conversationId);
+
+        assertEquals(messagesParent.getId(), messagesChild.getParentChannelId());
+        assertEquals(conversationId, messagesChild.getConversationId());
+
+        assertEquals(callsParent.getId(), callsChild.getParentChannelId());
+        assertEquals(conversationId, callsChild.getConversationId());
+
+        mBinderService.deleteConversationNotificationChannels(PKG, mUid, conversationId);
+
+        assertNull(mBinderService.getConversationNotificationChannel(
+                PKG, 0, PKG, messagesParent.getId(), conversationId));
+        assertNull(mBinderService.getConversationNotificationChannel(
+                PKG, 0, PKG, callsParent.getId(), conversationId));
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 8961796..7173e0c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.notification;
 
+import static android.app.NotificationChannel.CONVERSATION_CHANNEL_ID_FORMAT;
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 import static android.app.NotificationManager.IMPORTANCE_HIGH;
 import static android.app.NotificationManager.IMPORTANCE_LOW;
@@ -224,6 +225,26 @@
         assertEquals(expected.getGroup(), actual.getGroup());
         assertEquals(expected.getAudioAttributes(), actual.getAudioAttributes());
         assertEquals(expected.getLightColor(), actual.getLightColor());
+        assertEquals(expected.getParentChannelId(), actual.getParentChannelId());
+        assertEquals(expected.getConversationId(), actual.getConversationId());
+    }
+
+    private void compareChannelsParentChild(NotificationChannel parent,
+            NotificationChannel actual, String conversationId) {
+        assertEquals(parent.getName(), actual.getName());
+        assertEquals(parent.getDescription(), actual.getDescription());
+        assertEquals(parent.shouldVibrate(), actual.shouldVibrate());
+        assertEquals(parent.shouldShowLights(), actual.shouldShowLights());
+        assertEquals(parent.getImportance(), actual.getImportance());
+        assertEquals(parent.getLockscreenVisibility(), actual.getLockscreenVisibility());
+        assertEquals(parent.getSound(), actual.getSound());
+        assertEquals(parent.canBypassDnd(), actual.canBypassDnd());
+        assertTrue(Arrays.equals(parent.getVibrationPattern(), actual.getVibrationPattern()));
+        assertEquals(parent.getGroup(), actual.getGroup());
+        assertEquals(parent.getAudioAttributes(), actual.getAudioAttributes());
+        assertEquals(parent.getLightColor(), actual.getLightColor());
+        assertEquals(parent.getId(), actual.getParentChannelId());
+        assertEquals(conversationId, actual.getConversationId());
     }
 
     private void compareGroups(NotificationChannelGroup expected, NotificationChannelGroup actual) {
@@ -2786,4 +2807,40 @@
         assertEquals(user10Importance, mHelper.getNotificationChannel(
                 pkg, uidList10[0], channelId, false).getImportance());
     }
+
+    @Test
+    public void testGetConversationNotificationChannel() {
+        String conversationId = "friend";
+
+        NotificationChannel parent =
+                new NotificationChannel("parent", "messages", IMPORTANCE_DEFAULT);
+        mHelper.createNotificationChannel(PKG_O, UID_O, parent, true, false);
+
+        NotificationChannel friend = new NotificationChannel(String.format(
+                CONVERSATION_CHANNEL_ID_FORMAT, parent.getId(), conversationId),
+                "messages", IMPORTANCE_DEFAULT);
+        friend.setConversationId(parent.getId(), conversationId);
+        mHelper.createNotificationChannel(PKG_O, UID_O, friend, true, false);
+
+        compareChannelsParentChild(parent, mHelper.getNotificationChannel(
+                PKG_O, UID_O, parent.getId(), conversationId, false), conversationId);
+    }
+
+    @Test
+    public void testConversationNotificationChannelsRequireParents() {
+        String parentId = "does not exist";
+        String conversationId = "friend";
+
+        NotificationChannel friend = new NotificationChannel(String.format(
+                CONVERSATION_CHANNEL_ID_FORMAT, parentId, conversationId),
+                "messages", IMPORTANCE_DEFAULT);
+        friend.setConversationId(parentId, conversationId);
+
+        try {
+            mHelper.createNotificationChannel(PKG_O, UID_O, friend, true, false);
+            fail("allowed creation of conversation channel without a parent");
+        } catch (IllegalArgumentException e) {
+            // good
+        }
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
index 09ac9ce..d819b1a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
@@ -63,6 +63,26 @@
         assertEquals(Insets.of(0, 100, 0, 0),
                 mProvider.getSource().calculateInsets(new Rect(0, 0, 500, 500),
                         false /* ignoreVisibility */));
+        assertEquals(Insets.of(0, 100, 0, 0),
+                mProvider.getSource().calculateVisibleInsets(new Rect(0, 0, 500, 500)));
+    }
+
+    @Test
+    public void testPostLayout_givenInsets() {
+        final WindowState ime = createWindow(null, TYPE_APPLICATION, "ime");
+        ime.getFrameLw().set(0, 0, 500, 100);
+        ime.getGivenContentInsetsLw().set(0, 0, 0, 60);
+        ime.getGivenVisibleInsetsLw().set(0, 0, 0, 75);
+        ime.mHasSurface = true;
+        mProvider.setWindow(ime, null);
+        mProvider.onPostLayout();
+        assertEquals(new Rect(0, 0, 500, 40), mProvider.getSource().getFrame());
+        assertEquals(new Rect(0, 0, 500, 25), mProvider.getSource().getVisibleFrame());
+        assertEquals(Insets.of(0, 40, 0, 0),
+                mProvider.getSource().calculateInsets(new Rect(0, 0, 500, 500),
+                        false /* ignoreVisibility */));
+        assertEquals(Insets.of(0, 25, 0, 0),
+                mProvider.getSource().calculateVisibleInsets(new Rect(0, 0, 500, 500)));
     }
 
     @Test
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index c900f38..8397aa4 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -1568,44 +1568,16 @@
         }
 
         @Override
-        public void setAppStandbyBucket(String packageName,
-                int bucket, int userId) {
+        public void setAppStandbyBucket(String packageName, int bucket, int userId) {
             getContext().enforceCallingPermission(Manifest.permission.CHANGE_APP_IDLE_STATE,
                     "No permission to change app standby state");
 
-            if (bucket < UsageStatsManager.STANDBY_BUCKET_ACTIVE
-                    || bucket > UsageStatsManager.STANDBY_BUCKET_NEVER) {
-                throw new IllegalArgumentException("Cannot set the standby bucket to " + bucket);
-            }
             final int callingUid = Binder.getCallingUid();
-            try {
-                userId = ActivityManager.getService().handleIncomingUser(
-                        Binder.getCallingPid(), callingUid, userId, false, true,
-                        "setAppStandbyBucket", null);
-            } catch (RemoteException re) {
-                throw re.rethrowFromSystemServer();
-            }
-            final boolean shellCaller = callingUid == 0 || callingUid == Process.SHELL_UID;
-            final boolean systemCaller = UserHandle.isCore(callingUid);
-            final int reason = systemCaller
-                    ? UsageStatsManager.REASON_MAIN_FORCED
-                    : UsageStatsManager.REASON_MAIN_PREDICTED;
+            final int callingPid = Binder.getCallingPid();
             final long token = Binder.clearCallingIdentity();
             try {
-                final int packageUid = mPackageManagerInternal.getPackageUid(packageName,
-                        PackageManager.MATCH_ANY_USER | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
-                        | PackageManager.MATCH_DIRECT_BOOT_AWARE, userId);
-                // Caller cannot set their own standby state
-                if (packageUid == callingUid) {
-                    throw new IllegalArgumentException("Cannot set your own standby bucket");
-                }
-                if (packageUid < 0) {
-                    throw new IllegalArgumentException(
-                            "Cannot set standby bucket for non existent package (" + packageName
-                                    + ")");
-                }
-                mAppStandby.setAppStandbyBucket(packageName, userId, bucket, reason,
-                        SystemClock.elapsedRealtime(), shellCaller);
+                mAppStandby.setAppStandbyBucket(packageName, bucket, userId,
+                        callingUid, callingPid);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -1643,37 +1615,11 @@
                     "No permission to change app standby state");
 
             final int callingUid = Binder.getCallingUid();
-            try {
-                userId = ActivityManager.getService().handleIncomingUser(
-                        Binder.getCallingPid(), callingUid, userId, false, true,
-                        "setAppStandbyBucket", null);
-            } catch (RemoteException re) {
-                throw re.rethrowFromSystemServer();
-            }
-            final boolean shellCaller = callingUid == 0 || callingUid == Process.SHELL_UID;
-            final int reason = shellCaller
-                    ? UsageStatsManager.REASON_MAIN_FORCED
-                    : UsageStatsManager.REASON_MAIN_PREDICTED;
+            final int callingPid = Binder.getCallingPid();
             final long token = Binder.clearCallingIdentity();
             try {
-                final long elapsedRealtime = SystemClock.elapsedRealtime();
-                List<AppStandbyInfo> bucketList = appBuckets.getList();
-                for (AppStandbyInfo bucketInfo : bucketList) {
-                    final String packageName = bucketInfo.mPackageName;
-                    final int bucket = bucketInfo.mStandbyBucket;
-                    if (bucket < UsageStatsManager.STANDBY_BUCKET_ACTIVE
-                            || bucket > UsageStatsManager.STANDBY_BUCKET_NEVER) {
-                        throw new IllegalArgumentException(
-                                "Cannot set the standby bucket to " + bucket);
-                    }
-                    // Caller cannot set their own standby state
-                    if (mPackageManagerInternal.getPackageUid(packageName,
-                            PackageManager.MATCH_ANY_USER, userId) == callingUid) {
-                        throw new IllegalArgumentException("Cannot set your own standby bucket");
-                    }
-                    mAppStandby.setAppStandbyBucket(packageName, userId, bucket, reason,
-                            elapsedRealtime, shellCaller);
-                }
+                mAppStandby.setAppStandbyBuckets(appBuckets.getList(), userId,
+                        callingUid, callingPid);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index 198b4c3..bde2cfd 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -611,65 +611,91 @@
 
     int setParameter(UUID modelId, @ModelParams int modelParam, int value) {
         synchronized (mLock) {
-            MetricsLogger.count(mContext, "sth_set_parameter", 1);
-            if (modelId == null || mModule == null) {
-                return SoundTrigger.STATUS_ERROR;
-            }
-            ModelData modelData = mModelDataMap.get(modelId);
-            if (modelData == null) {
-                Slog.w(TAG, "SetParameter: Invalid model id:" + modelId);
-                return SoundTrigger.STATUS_BAD_VALUE;
-            }
-            if (!modelData.isModelLoaded()) {
-                Slog.i(TAG, "SetParameter: Given model is not loaded:" + modelId);
-                return SoundTrigger.STATUS_BAD_VALUE;
-            }
-
-            return mModule.setParameter(modelData.getHandle(), modelParam, value);
+            return setParameterLocked(mModelDataMap.get(modelId), modelParam, value);
         }
     }
 
-    int getParameter(@NonNull UUID modelId, @ModelParams int modelParam)
-            throws UnsupportedOperationException, IllegalArgumentException {
+    int setKeyphraseParameter(int keyphraseId, @ModelParams int modelParam, int value) {
         synchronized (mLock) {
-            MetricsLogger.count(mContext, "sth_get_parameter", 1);
-            if (mModule == null) {
-                throw new UnsupportedOperationException("SoundTriggerModule not initialized");
-            }
-
-            ModelData modelData = mModelDataMap.get(modelId);
-            if (modelData == null) {
-                throw new IllegalArgumentException("Invalid model id:" + modelId);
-            }
-            if (!modelData.isModelLoaded()) {
-                throw new UnsupportedOperationException("Given model is not loaded:" + modelId);
-            }
-
-            return mModule.getParameter(modelData.getHandle(), modelParam);
+            return setParameterLocked(getKeyphraseModelDataLocked(keyphraseId), modelParam, value);
         }
     }
 
+    private int setParameterLocked(@Nullable ModelData modelData, @ModelParams int modelParam,
+            int value) {
+        MetricsLogger.count(mContext, "sth_set_parameter", 1);
+        if (mModule == null) {
+            return SoundTrigger.STATUS_NO_INIT;
+        }
+        if (modelData == null || !modelData.isModelLoaded()) {
+            Slog.i(TAG, "SetParameter: Given model is not loaded:" + modelData);
+            return SoundTrigger.STATUS_BAD_VALUE;
+        }
+
+        return mModule.setParameter(modelData.getHandle(), modelParam, value);
+    }
+
+    int getParameter(@NonNull UUID modelId, @ModelParams int modelParam) {
+        synchronized (mLock) {
+            return getParameterLocked(mModelDataMap.get(modelId), modelParam);
+        }
+    }
+
+    int getKeyphraseParameter(int keyphraseId, @ModelParams int modelParam) {
+        synchronized (mLock) {
+            return getParameterLocked(getKeyphraseModelDataLocked(keyphraseId), modelParam);
+        }
+    }
+
+    private int getParameterLocked(@Nullable ModelData modelData, @ModelParams int modelParam) {
+        MetricsLogger.count(mContext, "sth_get_parameter", 1);
+        if (mModule == null) {
+            throw new UnsupportedOperationException("SoundTriggerModule not initialized");
+        }
+
+        if (modelData == null) {
+            throw new IllegalArgumentException("Invalid model id");
+        }
+        if (!modelData.isModelLoaded()) {
+            throw new UnsupportedOperationException("Given model is not loaded:" + modelData);
+        }
+
+        return mModule.getParameter(modelData.getHandle(), modelParam);
+    }
+
     @Nullable
     ModelParamRange queryParameter(@NonNull UUID modelId, @ModelParams int modelParam) {
         synchronized (mLock) {
-            MetricsLogger.count(mContext, "sth_query_parameter", 1);
-            if (mModule == null) {
-                return null;
-            }
-            ModelData modelData = mModelDataMap.get(modelId);
-            if (modelData == null) {
-                Slog.w(TAG, "queryParameter: Invalid model id:" + modelId);
-                return null;
-            }
-            if (!modelData.isModelLoaded()) {
-                Slog.i(TAG, "queryParameter: Given model is not loaded:" + modelId);
-                return null;
-            }
-
-            return mModule.queryParameter(modelData.getHandle(), modelParam);
+            return queryParameterLocked(mModelDataMap.get(modelId), modelParam);
         }
     }
 
+    @Nullable
+    ModelParamRange queryKeyphraseParameter(int keyphraseId, @ModelParams int modelParam) {
+        synchronized (mLock) {
+            return queryParameterLocked(getKeyphraseModelDataLocked(keyphraseId), modelParam);
+        }
+    }
+
+    @Nullable
+    private ModelParamRange queryParameterLocked(@Nullable ModelData modelData,
+            @ModelParams int modelParam) {
+        MetricsLogger.count(mContext, "sth_query_parameter", 1);
+        if (mModule == null) {
+            return null;
+        }
+        if (modelData == null) {
+            Slog.w(TAG, "queryParameter: Invalid model id");
+            return null;
+        }
+        if (!modelData.isModelLoaded()) {
+            Slog.i(TAG, "queryParameter: Given model is not loaded:" + modelData);
+            return null;
+        }
+
+        return mModule.queryParameter(modelData.getHandle(), modelParam);
+    }
+
     //---- SoundTrigger.StatusListener methods
     @Override
     public void onRecognition(RecognitionEvent event) {
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
index d05e044..54dffdc 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
@@ -16,17 +16,17 @@
 
 package com.android.server.soundtrigger;
 
+import android.annotation.Nullable;
 import android.hardware.soundtrigger.IRecognitionStatusCallback;
+import android.hardware.soundtrigger.ModelParams;
 import android.hardware.soundtrigger.SoundTrigger;
 import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
-import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionEvent;
-import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra;
 import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
+import android.hardware.soundtrigger.SoundTrigger.ModelParamRange;
 import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
 import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
-import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent;
-import android.hardware.soundtrigger.SoundTrigger.SoundModelEvent;
-import android.hardware.soundtrigger.SoundTriggerModule;
+
+import com.android.server.voiceinteraction.VoiceInteractionManagerService;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -71,6 +71,58 @@
     public abstract ModuleProperties getModuleProperties();
 
     /**
+     * Set a model specific {@link ModelParams} with the given value. This
+     * parameter will keep its value for the duration the model is loaded regardless of starting and
+     * stopping recognition. Once the model is unloaded, the value will be lost.
+     * {@link SoundTriggerInternal#queryParameter} should be checked first before calling this
+     * method.
+     *
+     * @param keyphraseId The identifier of the keyphrase for which
+     *        to modify model parameters
+     * @param modelParam   {@link ModelParams}
+     * @param value        Value to set
+     * @return - {@link SoundTrigger#STATUS_OK} in case of success
+     *         - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached
+     *         - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter
+     *         - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or
+     *           if API is not supported by HAL
+     */
+    public abstract int setParameter(int keyphraseId, @ModelParams int modelParam, int value);
+
+    /**
+     * Get a model specific {@link ModelParams}. This parameter will keep its value
+     * for the duration the model is loaded regardless of starting and stopping recognition.
+     * Once the model is unloaded, the value will be lost. If the value is not set, a default
+     * value is returned. See ModelParams for parameter default values.
+     * {@link SoundTriggerInternal#queryParameter} should be checked first before calling this
+     * method.
+     *
+     * @param keyphraseId The identifier of the keyphrase for which
+     *        to modify model parameters
+     * @param modelParam   {@link ModelParams}
+     * @return value of parameter
+     * @throws UnsupportedOperationException if hal or model do not support this API.
+     *         queryParameter should be checked first.
+     * @throws IllegalArgumentException if invalid model handle or parameter is passed.
+     *         queryParameter should be checked first.
+     */
+    public abstract int getParameter(int keyphraseId, @ModelParams int modelParam);
+
+    /**
+     * Determine if parameter control is supported for the given model handle.
+     * This method should be checked prior to calling {@link SoundTriggerInternal#setParameter}
+     * or {@link SoundTriggerInternal#getParameter}.
+     *
+     * @param keyphraseId The identifier of the keyphrase for which
+     *        to modify model parameters
+     * @param modelParam {@link ModelParams}
+     * @return supported range of parameter, null if not supported
+     */
+    @Nullable
+    public abstract ModelParamRange queryParameter(int keyphraseId,
+            @ModelParams int modelParam);
+
+    /**
      * Unloads (and stops if running) the given keyphraseId
      */
     public abstract int unloadKeyphraseModel(int keyphaseId);
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 68b16f3..e37755b 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -1446,26 +1446,45 @@
         @Override
         public int startRecognition(int keyphraseId, KeyphraseSoundModel soundModel,
                 IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig) {
-            if (!isInitialized()) return STATUS_ERROR;
+            if (!isInitialized()) throw new UnsupportedOperationException();
             return mSoundTriggerHelper.startKeyphraseRecognition(keyphraseId, soundModel, listener,
                     recognitionConfig);
         }
 
         @Override
         public synchronized int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) {
-            if (!isInitialized()) return STATUS_ERROR;
+            if (!isInitialized()) throw new UnsupportedOperationException();
             return mSoundTriggerHelper.stopKeyphraseRecognition(keyphraseId, listener);
         }
 
         @Override
         public ModuleProperties getModuleProperties() {
-            if (!isInitialized()) return null;
+            if (!isInitialized()) throw new UnsupportedOperationException();
             return mSoundTriggerHelper.getModuleProperties();
         }
 
         @Override
+        public int setParameter(int keyphraseId, @ModelParams int modelParam, int value) {
+            if (!isInitialized()) throw new UnsupportedOperationException();
+            return mSoundTriggerHelper.setKeyphraseParameter(keyphraseId, modelParam, value);
+        }
+
+        @Override
+        public int getParameter(int keyphraseId, @ModelParams int modelParam) {
+            if (!isInitialized()) throw new UnsupportedOperationException();
+            return mSoundTriggerHelper.getKeyphraseParameter(keyphraseId, modelParam);
+        }
+
+        @Override
+        @Nullable
+        public ModelParamRange queryParameter(int keyphraseId, @ModelParams int modelParam) {
+            if (!isInitialized()) throw new UnsupportedOperationException();
+            return mSoundTriggerHelper.queryKeyphraseParameter(keyphraseId, modelParam);
+        }
+
+        @Override
         public int unloadKeyphraseModel(int keyphraseId) {
-            if (!isInitialized()) return STATUS_ERROR;
+            if (!isInitialized()) throw new UnsupportedOperationException();
             return mSoundTriggerHelper.unloadKeyphraseSoundModel(keyphraseId);
         }
 
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index e9db31b..06c8074 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -41,7 +41,9 @@
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.hardware.soundtrigger.IRecognitionStatusCallback;
+import android.hardware.soundtrigger.ModelParams;
 import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
+import android.hardware.soundtrigger.SoundTrigger.ModelParamRange;
 import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
 import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
 import android.os.Binder;
@@ -1084,6 +1086,55 @@
             }
         }
 
+        @Override
+        public int setParameter(IVoiceInteractionService service, int keyphraseId,
+                @ModelParams int modelParam, int value) {
+            // Allow the call if this is the current voice interaction service.
+            synchronized (this) {
+                enforceIsCurrentVoiceInteractionService(service);
+            }
+
+            final long caller = Binder.clearCallingIdentity();
+            try {
+                return mSoundTriggerInternal.setParameter(keyphraseId, modelParam, value);
+            } finally {
+                Binder.restoreCallingIdentity(caller);
+            }
+        }
+
+        @Override
+        public int getParameter(IVoiceInteractionService service, int keyphraseId,
+                @ModelParams int modelParam) {
+            // Allow the call if this is the current voice interaction service.
+            synchronized (this) {
+                enforceIsCurrentVoiceInteractionService(service);
+            }
+
+            final long caller = Binder.clearCallingIdentity();
+            try {
+                return mSoundTriggerInternal.getParameter(keyphraseId, modelParam);
+            } finally {
+                Binder.restoreCallingIdentity(caller);
+            }
+        }
+
+        @Override
+        @Nullable
+        public ModelParamRange queryParameter(IVoiceInteractionService service,
+                int keyphraseId, @ModelParams int modelParam) {
+            // Allow the call if this is the current voice interaction service.
+            synchronized (this) {
+                enforceIsCurrentVoiceInteractionService(service);
+            }
+
+            final long caller = Binder.clearCallingIdentity();
+            try {
+                return mSoundTriggerInternal.queryParameter(keyphraseId, modelParam);
+            } finally {
+                Binder.restoreCallingIdentity(caller);
+            }
+        }
+
         private synchronized void unloadAllKeyphraseModels() {
             for (int i = 0; i < mLoadedKeyphraseIds.size(); i++) {
                 final long caller = Binder.clearCallingIdentity();
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 9cf4803..c4c1e21 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -49,6 +49,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.Executor;
 
 /**
@@ -882,7 +883,8 @@
      */
     public TelecomManager(Context context, ITelecomService telecomServiceImpl) {
         Context appContext = context.getApplicationContext();
-        if (appContext != null) {
+        if (appContext != null && Objects.equals(context.getFeatureId(),
+                appContext.getFeatureId())) {
             mContext = appContext;
         } else {
             mContext = context;
@@ -916,7 +918,7 @@
         try {
             if (isServiceConnected()) {
                 return getTelecomService().getDefaultOutgoingPhoneAccount(uriScheme,
-                        mContext.getOpPackageName());
+                        mContext.getOpPackageName(), mContext.getFeatureId());
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Error calling ITelecomService#getDefaultOutgoingPhoneAccount", e);
@@ -1113,7 +1115,8 @@
     public List<PhoneAccountHandle> getSelfManagedPhoneAccounts() {
         try {
             if (isServiceConnected()) {
-                return getTelecomService().getSelfManagedPhoneAccounts(mContext.getOpPackageName());
+                return getTelecomService().getSelfManagedPhoneAccounts(mContext.getOpPackageName(),
+                        mContext.getFeatureId());
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Error calling ITelecomService#getSelfManagedPhoneAccounts()", e);
@@ -1138,8 +1141,8 @@
             boolean includeDisabledAccounts) {
         try {
             if (isServiceConnected()) {
-                return getTelecomService().getCallCapablePhoneAccounts(
-                        includeDisabledAccounts, mContext.getOpPackageName());
+                return getTelecomService().getCallCapablePhoneAccounts(includeDisabledAccounts,
+                        mContext.getOpPackageName(), mContext.getFeatureId());
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Error calling ITelecomService#getCallCapablePhoneAccounts(" +
@@ -1442,7 +1445,7 @@
         try {
             if (isServiceConnected()) {
                 return getTelecomService().isVoiceMailNumber(accountHandle, number,
-                        mContext.getOpPackageName());
+                        mContext.getOpPackageName(), mContext.getFeatureId());
             }
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException calling ITelecomService#isVoiceMailNumber.", e);
@@ -1464,7 +1467,7 @@
         try {
             if (isServiceConnected()) {
                 return getTelecomService().getVoiceMailNumber(accountHandle,
-                        mContext.getOpPackageName());
+                        mContext.getOpPackageName(), mContext.getFeatureId());
             }
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException calling ITelecomService#hasVoiceMailNumber.", e);
@@ -1485,7 +1488,7 @@
         try {
             if (isServiceConnected()) {
                 return getTelecomService().getLine1Number(accountHandle,
-                        mContext.getOpPackageName());
+                        mContext.getOpPackageName(), mContext.getFeatureId());
             }
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException calling ITelecomService#getLine1Number.", e);
@@ -1506,7 +1509,8 @@
     public boolean isInCall() {
         try {
             if (isServiceConnected()) {
-                return getTelecomService().isInCall(mContext.getOpPackageName());
+                return getTelecomService().isInCall(mContext.getOpPackageName(),
+                        mContext.getFeatureId());
             }
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException calling isInCall().", e);
@@ -1531,7 +1535,8 @@
     public boolean isInManagedCall() {
         try {
             if (isServiceConnected()) {
-                return getTelecomService().isInManagedCall(mContext.getOpPackageName());
+                return getTelecomService().isInManagedCall(mContext.getOpPackageName(),
+                        mContext.getFeatureId());
             }
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException calling isInManagedCall().", e);
@@ -1711,7 +1716,8 @@
     public boolean isTtySupported() {
         try {
             if (isServiceConnected()) {
-                return getTelecomService().isTtySupported(mContext.getOpPackageName());
+                return getTelecomService().isTtySupported(mContext.getOpPackageName(),
+                        mContext.getFeatureId());
             }
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException attempting to get TTY supported state.", e);
@@ -1735,7 +1741,8 @@
     public @TtyMode int getCurrentTtyMode() {
         try {
             if (isServiceConnected()) {
-                return getTelecomService().getCurrentTtyMode(mContext.getOpPackageName());
+                return getTelecomService().getCurrentTtyMode(mContext.getOpPackageName(),
+                        mContext.getFeatureId());
             }
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException attempting to get the current TTY mode.", e);
@@ -1925,7 +1932,8 @@
         ITelecomService service = getTelecomService();
         if (service != null) {
             try {
-                service.showInCallScreen(showDialpad, mContext.getOpPackageName());
+                service.showInCallScreen(showDialpad, mContext.getOpPackageName(),
+                        mContext.getFeatureId());
             } catch (RemoteException e) {
                 Log.e(TAG, "Error calling ITelecomService#showCallScreen", e);
             }
@@ -1988,7 +1996,7 @@
             }
             try {
                 service.placeCall(address, extras == null ? new Bundle() : extras,
-                        mContext.getOpPackageName());
+                        mContext.getOpPackageName(), mContext.getFeatureId());
             } catch (RemoteException e) {
                 Log.e(TAG, "Error calling ITelecomService#placeCall", e);
             }
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index 204c37e..c54da6b 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -35,12 +35,13 @@
      *
      * @param showDialpad if true, make the dialpad visible initially.
      */
-    void showInCallScreen(boolean showDialpad, String callingPackage);
+    void showInCallScreen(boolean showDialpad, String callingPackage, String callingFeatureId);
 
     /**
      * @see TelecomServiceImpl#getDefaultOutgoingPhoneAccount
      */
-    PhoneAccountHandle getDefaultOutgoingPhoneAccount(in String uriScheme, String callingPackage);
+    PhoneAccountHandle getDefaultOutgoingPhoneAccount(in String uriScheme, String callingPackage,
+            String callingFeatureId);
 
     /**
      * @see TelecomServiceImpl#getUserSelectedOutgoingPhoneAccount
@@ -56,12 +57,13 @@
      * @see TelecomServiceImpl#getCallCapablePhoneAccounts
      */
     List<PhoneAccountHandle> getCallCapablePhoneAccounts(
-            boolean includeDisabledAccounts, String callingPackage);
+            boolean includeDisabledAccounts, String callingPackage, String callingFeatureId);
 
     /**
      * @see TelecomServiceImpl#getSelfManagedPhoneAccounts
      */
-    List<PhoneAccountHandle> getSelfManagedPhoneAccounts(String callingPackage);
+    List<PhoneAccountHandle> getSelfManagedPhoneAccounts(String callingPackage,
+            String callingFeatureId);
 
     /**
      * @see TelecomManager#getPhoneAccountsSupportingScheme
@@ -123,17 +125,19 @@
      * @see TelecomServiceImpl#isVoiceMailNumber
      */
     boolean isVoiceMailNumber(in PhoneAccountHandle accountHandle, String number,
-            String callingPackage);
+            String callingPackage, String callingFeatureId);
 
     /**
      * @see TelecomServiceImpl#getVoiceMailNumber
      */
-    String getVoiceMailNumber(in PhoneAccountHandle accountHandle, String callingPackage);
+    String getVoiceMailNumber(in PhoneAccountHandle accountHandle, String callingPackage,
+            String callingFeatureId);
 
     /**
      * @see TelecomServiceImpl#getLine1Number
      */
-    String getLine1Number(in PhoneAccountHandle accountHandle, String callingPackage);
+    String getLine1Number(in PhoneAccountHandle accountHandle, String callingPackage,
+            String callingFeatureId);
 
     /**
      * @see TelecomServiceImpl#getDefaultPhoneApp
@@ -172,12 +176,12 @@
     /**
      * @see TelecomServiceImpl#isInCall
      */
-    boolean isInCall(String callingPackage);
+    boolean isInCall(String callingPackage, String callingFeatureId);
 
     /**
      * @see TelecomServiceImpl#isInManagedCall
      */
-    boolean isInManagedCall(String callingPackage);
+    boolean isInManagedCall(String callingPackage, String callingFeatureId);
 
     /**
      * @see TelecomServiceImpl#isRinging
@@ -229,12 +233,12 @@
     /**
      * @see TelecomServiceImpl#isTtySupported
      */
-    boolean isTtySupported(String callingPackage);
+    boolean isTtySupported(String callingPackage, String callingFeatureId);
 
     /**
      * @see TelecomServiceImpl#getCurrentTtyMode
      */
-    int getCurrentTtyMode(String callingPackage);
+    int getCurrentTtyMode(String callingPackage, String callingFeatureId);
 
     /**
      * @see TelecomServiceImpl#addNewIncomingCall
@@ -249,7 +253,7 @@
     /**
      * @see TelecomServiceImpl#placeCall
      */
-    void placeCall(in Uri handle, in Bundle extras, String callingPackage);
+    void placeCall(in Uri handle, in Bundle extras, String callingPackage, String callingFeatureId);
 
     /**
      * @see TelecomServiceImpl#enablePhoneAccount
diff --git a/telephony/common/android/telephony/LocationAccessPolicy.java b/telephony/common/android/telephony/LocationAccessPolicy.java
index f39981f..aaafee2 100644
--- a/telephony/common/android/telephony/LocationAccessPolicy.java
+++ b/telephony/common/android/telephony/LocationAccessPolicy.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
diff --git a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
index eb02ea6f..97bcbc0 100644
--- a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
+++ b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
@@ -18,14 +18,16 @@
 
 import android.annotation.Nullable;
 import android.content.ContentResolver;
+import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.permission.IPermissionManager;
 import android.provider.Settings;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 import android.telephony.TelephonyManager;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -76,7 +78,7 @@
      */
     public static synchronized void disableCarrierAppsUntilPrivileged(String callingPackage,
             IPackageManager packageManager, IPermissionManager permissionManager,
-            TelephonyManager telephonyManager, ContentResolver contentResolver, int userId) {
+            TelephonyManager telephonyManager, int userId, Context context) {
         if (DEBUG) {
             Rlog.d(TAG, "disableCarrierAppsUntilPrivileged");
         }
@@ -85,6 +87,7 @@
                 config.getDisabledUntilUsedPreinstalledCarrierApps();
         ArrayMap<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed =
                 config.getDisabledUntilUsedPreinstalledCarrierAssociatedApps();
+        ContentResolver contentResolver = getContentResolverForUser(context, userId);
         disableCarrierAppsUntilPrivileged(callingPackage, packageManager, permissionManager,
                 telephonyManager, contentResolver, userId, systemCarrierAppsDisabledUntilUsed,
                 systemCarrierAssociatedAppsDisabledUntilUsed);
@@ -102,8 +105,8 @@
      * Manager can kill it, and this can lead to crashes as the app is in an unexpected state.
      */
     public static synchronized void disableCarrierAppsUntilPrivileged(String callingPackage,
-            IPackageManager packageManager, IPermissionManager permissionManager,
-            ContentResolver contentResolver, int userId) {
+            IPackageManager packageManager, IPermissionManager permissionManager, int userId,
+            Context context) {
         if (DEBUG) {
             Rlog.d(TAG, "disableCarrierAppsUntilPrivileged");
         }
@@ -114,15 +117,23 @@
 
         ArrayMap<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed =
                 config.getDisabledUntilUsedPreinstalledCarrierAssociatedApps();
+        ContentResolver contentResolver = getContentResolverForUser(context, userId);
         disableCarrierAppsUntilPrivileged(callingPackage, packageManager, permissionManager,
                 null /* telephonyManager */, contentResolver, userId,
                 systemCarrierAppsDisabledUntilUsed, systemCarrierAssociatedAppsDisabledUntilUsed);
     }
 
+    private static ContentResolver getContentResolverForUser(Context context, int userId) {
+        Context userContext = context.createContextAsUser(UserHandle.getUserHandleForUid(userId),
+                0);
+        return userContext.getContentResolver();
+    }
+
     /**
      * Disable carrier apps until they are privileged
      * Must be public b/c framework unit tests can't access package-private methods.
      */
+    // Must be public b/c framework unit tests can't access package-private methods.
     @VisibleForTesting
     public static void disableCarrierAppsUntilPrivileged(String callingPackage,
             IPackageManager packageManager, IPermissionManager permissionManager,
@@ -142,9 +153,8 @@
                 systemCarrierAssociatedAppsDisabledUntilUsed);
 
         List<String> enabledCarrierPackages = new ArrayList<>();
-
-        boolean hasRunOnce = Settings.Secure.getIntForUser(
-                contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 0, userId) == 1;
+        boolean hasRunOnce = Settings.Secure.getInt(contentResolver,
+                Settings.Secure.CARRIER_APPS_HANDLED, 0) == 1;
 
         try {
             for (ApplicationInfo ai : candidates) {
@@ -259,8 +269,7 @@
 
             // Mark the execution so we do not disable apps again.
             if (!hasRunOnce) {
-                Settings.Secure.putIntForUser(
-                        contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 1, userId);
+                Settings.Secure.putInt(contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 1);
             }
 
             if (!enabledCarrierPackages.isEmpty()) {
diff --git a/telephony/common/com/android/internal/telephony/GsmAlphabet.java b/telephony/common/com/android/internal/telephony/GsmAlphabet.java
index 22cbdaa0..a36ff93 100644
--- a/telephony/common/com/android/internal/telephony/GsmAlphabet.java
+++ b/telephony/common/com/android/internal/telephony/GsmAlphabet.java
@@ -19,7 +19,7 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.res.Resources;
 import android.os.Build;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 import android.text.TextUtils;
 import android.util.SparseIntArray;
 
diff --git a/telephony/common/com/android/internal/telephony/HbpcdUtils.java b/telephony/common/com/android/internal/telephony/HbpcdUtils.java
index 2f31942..45a563c 100644
--- a/telephony/common/com/android/internal/telephony/HbpcdUtils.java
+++ b/telephony/common/com/android/internal/telephony/HbpcdUtils.java
@@ -19,7 +19,7 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.Cursor;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 
 import com.android.internal.telephony.HbpcdLookup.ArbitraryMccSidMatch;
 import com.android.internal.telephony.HbpcdLookup.MccIdd;
diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java
index b302589..afb9b6f 100644
--- a/telephony/common/com/android/internal/telephony/SmsApplication.java
+++ b/telephony/common/com/android/internal/telephony/SmsApplication.java
@@ -40,7 +40,7 @@
 import android.provider.Telephony;
 import android.provider.Telephony.Sms.Intents;
 import android.telephony.PackageChangeReceiver;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 import android.telephony.TelephonyManager;
 import android.util.Log;
 
diff --git a/telephony/common/com/android/internal/telephony/SmsNumberUtils.java b/telephony/common/com/android/internal/telephony/SmsNumberUtils.java
index 06c08f5..06c3728 100644
--- a/telephony/common/com/android/internal/telephony/SmsNumberUtils.java
+++ b/telephony/common/com/android/internal/telephony/SmsNumberUtils.java
@@ -24,7 +24,7 @@
 import android.os.SystemProperties;
 import android.telephony.CarrierConfigManager;
 import android.telephony.PhoneNumberUtils;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 
diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
index 80a55b2..4109ca6 100644
--- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
@@ -28,7 +28,7 @@
 import android.os.Build;
 import android.os.Process;
 import android.os.UserHandle;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.util.Log;
diff --git a/telephony/java/android/service/carrier/CarrierIdentifier.java b/telephony/java/android/service/carrier/CarrierIdentifier.java
index af5bf74..7957c25 100644
--- a/telephony/java/android/service/carrier/CarrierIdentifier.java
+++ b/telephony/java/android/service/carrier/CarrierIdentifier.java
@@ -20,7 +20,7 @@
 import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 import android.telephony.TelephonyManager;
 
 import com.android.internal.telephony.uicc.IccUtils;
diff --git a/telephony/java/android/telephony/AnomalyReporter.java b/telephony/java/android/telephony/AnomalyReporter.java
index 9753d8b..097041f 100644
--- a/telephony/java/android/telephony/AnomalyReporter.java
+++ b/telephony/java/android/telephony/AnomalyReporter.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.content.Context;
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 4d57132..6a62237 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -3123,7 +3125,6 @@
      *   EAP-AKA: "0"
      *   EAP-SIM: "1"
      *   EAP-AKA_PRIME: "6"
-     * @hide
      */
     public static final String ENABLE_EAP_METHOD_PREFIX_BOOL = "enable_eap_method_prefix_bool";
 
@@ -3398,12 +3399,17 @@
         /** Prefix of all Ims.KEY_* constants. */
         public static final String KEY_PREFIX = "ims.";
 
-        //TODO: Add configs related to IMS.
+        /**
+         * Delay in milliseconds to turn off wifi when IMS is registered over wifi.
+         */
+        public static final String KEY_WIFI_OFF_DEFERRING_TIME_INT =
+                KEY_PREFIX + "wifi_off_deferring_time_int";
 
         private Ims() {}
 
         private static PersistableBundle getDefaults() {
             PersistableBundle defaults = new PersistableBundle();
+            defaults.putInt(KEY_WIFI_OFF_DEFERRING_TIME_INT, 0);
             return defaults;
         }
     }
diff --git a/telephony/java/android/telephony/CbGeoUtils.java b/telephony/java/android/telephony/CbGeoUtils.java
index 84be4e8..719ba8d 100644
--- a/telephony/java/android/telephony/CbGeoUtils.java
+++ b/telephony/java/android/telephony/CbGeoUtils.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.text.TextUtils;
diff --git a/telephony/java/android/telephony/CellIdentity.java b/telephony/java/android/telephony/CellIdentity.java
index 5345469..8e703fe 100644
--- a/telephony/java/android/telephony/CellIdentity.java
+++ b/telephony/java/android/telephony/CellIdentity.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.CallSuper;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
diff --git a/telephony/java/android/telephony/CellInfoCdma.java b/telephony/java/android/telephony/CellInfoCdma.java
index 2b1387c..acb21f4 100644
--- a/telephony/java/android/telephony/CellInfoCdma.java
+++ b/telephony/java/android/telephony/CellInfoCdma.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.NonNull;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
diff --git a/telephony/java/android/telephony/CellInfoGsm.java b/telephony/java/android/telephony/CellInfoGsm.java
index 4f7c7a9..79a9d44 100644
--- a/telephony/java/android/telephony/CellInfoGsm.java
+++ b/telephony/java/android/telephony/CellInfoGsm.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.NonNull;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
diff --git a/telephony/java/android/telephony/CellInfoLte.java b/telephony/java/android/telephony/CellInfoLte.java
index 6d19261..fed3ebf 100644
--- a/telephony/java/android/telephony/CellInfoLte.java
+++ b/telephony/java/android/telephony/CellInfoLte.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.NonNull;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
diff --git a/telephony/java/android/telephony/CellInfoTdscdma.java b/telephony/java/android/telephony/CellInfoTdscdma.java
index f1305f5..58ff8c9 100644
--- a/telephony/java/android/telephony/CellInfoTdscdma.java
+++ b/telephony/java/android/telephony/CellInfoTdscdma.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.NonNull;
 import android.os.Parcel;
 import android.os.Parcelable;
diff --git a/telephony/java/android/telephony/CellInfoWcdma.java b/telephony/java/android/telephony/CellInfoWcdma.java
index ee5fec8..33f6a55 100644
--- a/telephony/java/android/telephony/CellInfoWcdma.java
+++ b/telephony/java/android/telephony/CellInfoWcdma.java
@@ -18,7 +18,7 @@
 
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 
 import java.util.Objects;
 
diff --git a/telephony/java/android/telephony/CellLocation.java b/telephony/java/android/telephony/CellLocation.java
index 1193199..2d0bd52 100644
--- a/telephony/java/android/telephony/CellLocation.java
+++ b/telephony/java/android/telephony/CellLocation.java
@@ -53,8 +53,7 @@
     /**
      * Create a new CellLocation from a intent notifier Bundle
      *
-     * This method is used by PhoneStateIntentReceiver and maybe by
-     * external applications.
+     * This method maybe used by external applications.
      *
      * @param bundle Bundle from intent notifier
      * @return newly created CellLocation
diff --git a/telephony/java/android/telephony/CellSignalStrengthCdma.java b/telephony/java/android/telephony/CellSignalStrengthCdma.java
index 1998439..cab3b0c 100644
--- a/telephony/java/android/telephony/CellSignalStrengthCdma.java
+++ b/telephony/java/android/telephony/CellSignalStrengthCdma.java
@@ -20,7 +20,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.PersistableBundle;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 
 import java.util.Objects;
 
diff --git a/telephony/java/android/telephony/CellSignalStrengthGsm.java b/telephony/java/android/telephony/CellSignalStrengthGsm.java
index a9f3487..28052aa 100644
--- a/telephony/java/android/telephony/CellSignalStrengthGsm.java
+++ b/telephony/java/android/telephony/CellSignalStrengthGsm.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.IntRange;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
diff --git a/telephony/java/android/telephony/CellSignalStrengthLte.java b/telephony/java/android/telephony/CellSignalStrengthLte.java
index a6ba9c2..2ef2a52 100644
--- a/telephony/java/android/telephony/CellSignalStrengthLte.java
+++ b/telephony/java/android/telephony/CellSignalStrengthLte.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.IntRange;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
diff --git a/telephony/java/android/telephony/CellSignalStrengthNr.java b/telephony/java/android/telephony/CellSignalStrengthNr.java
index d28d750..4d67bcf 100644
--- a/telephony/java/android/telephony/CellSignalStrengthNr.java
+++ b/telephony/java/android/telephony/CellSignalStrengthNr.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.os.Parcel;
diff --git a/telephony/java/android/telephony/CellSignalStrengthTdscdma.java b/telephony/java/android/telephony/CellSignalStrengthTdscdma.java
index f4a3dbb..3bd9d58 100644
--- a/telephony/java/android/telephony/CellSignalStrengthTdscdma.java
+++ b/telephony/java/android/telephony/CellSignalStrengthTdscdma.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.os.Parcel;
diff --git a/telephony/java/android/telephony/CellSignalStrengthWcdma.java b/telephony/java/android/telephony/CellSignalStrengthWcdma.java
index 34b1385..535e952 100644
--- a/telephony/java/android/telephony/CellSignalStrengthWcdma.java
+++ b/telephony/java/android/telephony/CellSignalStrengthWcdma.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.IntRange;
 import android.annotation.StringDef;
 import android.compat.annotation.UnsupportedAppUsage;
diff --git a/telephony/java/android/telephony/NetworkScan.java b/telephony/java/android/telephony/NetworkScan.java
index b10649c..a6dedf7 100644
--- a/telephony/java/android/telephony/NetworkScan.java
+++ b/telephony/java/android/telephony/NetworkScan.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.IntDef;
 import android.os.RemoteException;
 
diff --git a/telephony/java/android/telephony/NetworkService.java b/telephony/java/android/telephony/NetworkService.java
index 8c5e107..844289c 100644
--- a/telephony/java/android/telephony/NetworkService.java
+++ b/telephony/java/android/telephony/NetworkService.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
diff --git a/telephony/java/android/telephony/NetworkServiceCallback.java b/telephony/java/android/telephony/NetworkServiceCallback.java
index 89b9665..214ab41 100644
--- a/telephony/java/android/telephony/NetworkServiceCallback.java
+++ b/telephony/java/android/telephony/NetworkServiceCallback.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java
index 6e86a42..2f9e6ac 100644
--- a/telephony/java/android/telephony/PhoneNumberUtils.java
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 66feb7b..2c62d06 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java
index 3350a33..1c58f8f 100644
--- a/telephony/java/android/telephony/SignalStrength.java
+++ b/telephony/java/android/telephony/SignalStrength.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -85,8 +87,7 @@
     /**
      * Create a new SignalStrength from a intent notifier Bundle
      *
-     * This method is used by PhoneStateIntentReceiver and maybe by
-     * external applications.
+     * This method may be used by external applications.
      *
      * @param m Bundle from intent notifier
      * @return newly created SignalStrength
diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java
index c0bc29d..c217b8b 100644
--- a/telephony/java/android/telephony/SmsMessage.java
+++ b/telephony/java/android/telephony/SmsMessage.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA;
 
 import android.annotation.Nullable;
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index 2c0a1c9..c24eeb7 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 321753b..4510fed 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import static android.net.NetworkPolicyManager.OVERRIDE_CONGESTED;
 import static android.net.NetworkPolicyManager.OVERRIDE_UNMETERED;
 
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 607450b..843c065 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -5868,7 +5868,10 @@
      * @param AID Application id. See ETSI 102.221 and 101.220.
      * @param p2 P2 parameter (described in ISO 7816-4).
      * @return an IccOpenLogicalChannelResponse object.
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
      */
+    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+    @Deprecated
     public IccOpenLogicalChannelResponse iccOpenLogicalChannel(String AID, int p2) {
         return iccOpenLogicalChannel(getSubId(), AID, p2);
     }
@@ -5899,7 +5902,10 @@
      * @param p2 P2 parameter (described in ISO 7816-4).
      * @return an IccOpenLogicalChannelResponse object.
      * @hide
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
      */
+    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+    @Deprecated
     public IccOpenLogicalChannelResponse iccOpenLogicalChannel(int subId, String AID, int p2) {
         try {
             ITelephony telephony = getITelephony();
@@ -5927,7 +5933,10 @@
      *            iccOpenLogicalChannel.
      * @return true if the channel was closed successfully.
      * @hide
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
      */
+    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+    @Deprecated
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     @SystemApi
     public boolean iccCloseLogicalChannelBySlot(int slotIndex, int channel) {
@@ -5954,7 +5963,10 @@
      * @param channel is the channel id to be closed as returned by a successful
      *            iccOpenLogicalChannel.
      * @return true if the channel was closed successfully.
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
      */
+    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+    @Deprecated
     public boolean iccCloseLogicalChannel(int channel) {
         return iccCloseLogicalChannel(getSubId(), channel);
     }
@@ -5973,7 +5985,10 @@
      *            iccOpenLogicalChannel.
      * @return true if the channel was closed successfully.
      * @hide
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
      */
+    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+    @Deprecated
     public boolean iccCloseLogicalChannel(int subId, int channel) {
         try {
             ITelephony telephony = getITelephony();
@@ -6009,7 +6024,10 @@
      * @return The APDU response from the ICC card with the status appended at the end, or null if
      * there is an issue connecting to the Telephony service.
      * @hide
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
      */
+    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+    @Deprecated
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     @SystemApi
     @Nullable
@@ -6047,7 +6065,10 @@
      * @param data Data to be sent with the APDU.
      * @return The APDU response from the ICC card with the status appended at
      *            the end.
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
      */
+    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+    @Deprecated
     public String iccTransmitApduLogicalChannel(int channel, int cla,
             int instruction, int p1, int p2, int p3, String data) {
         return iccTransmitApduLogicalChannel(getSubId(), channel, cla,
@@ -6076,7 +6097,10 @@
      * @return The APDU response from the ICC card with the status appended at
      *            the end.
      * @hide
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
      */
+    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+    @Deprecated
     public String iccTransmitApduLogicalChannel(int subId, int channel, int cla,
             int instruction, int p1, int p2, int p3, String data) {
         try {
@@ -6112,7 +6136,10 @@
      * @return The APDU response from the ICC card with the status appended at
      *            the end.
      * @hide
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
      */
+    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+    @Deprecated
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     @SystemApi
     @NonNull
@@ -6148,7 +6175,10 @@
      * @param data Data to be sent with the APDU.
      * @return The APDU response from the ICC card with the status appended at
      *            the end.
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
      */
+    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+    @Deprecated
     public String iccTransmitApduBasicChannel(int cla,
             int instruction, int p1, int p2, int p3, String data) {
         return iccTransmitApduBasicChannel(getSubId(), cla,
@@ -6175,7 +6205,10 @@
      * @return The APDU response from the ICC card with the status appended at
      *            the end.
      * @hide
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
      */
+    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+    @Deprecated
     public String iccTransmitApduBasicChannel(int subId, int cla,
             int instruction, int p1, int p2, int p3, String data) {
         try {
@@ -6203,7 +6236,10 @@
      * @param p3 P3 value of the APDU command.
      * @param filePath
      * @return The APDU response.
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
      */
+    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+    @Deprecated
     public byte[] iccExchangeSimIO(int fileID, int command, int p1, int p2, int p3,
             String filePath) {
         return iccExchangeSimIO(getSubId(), fileID, command, p1, p2, p3, filePath);
@@ -6225,7 +6261,10 @@
      * @param filePath
      * @return The APDU response.
      * @hide
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
      */
+    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+    @Deprecated
     public byte[] iccExchangeSimIO(int subId, int fileID, int command, int p1, int p2,
             int p3, String filePath) {
         try {
@@ -6251,7 +6290,10 @@
      * @return The APDU response from the ICC card in hexadecimal format
      *         with the last 4 bytes being the status word. If the command fails,
      *         returns an empty string.
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
      */
+    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+    @Deprecated
     public String sendEnvelopeWithStatus(String content) {
         return sendEnvelopeWithStatus(getSubId(), content);
     }
@@ -6271,7 +6313,10 @@
      *         with the last 4 bytes being the status word. If the command fails,
      *         returns an empty string.
      * @hide
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
      */
+    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+    @Deprecated
     public String sendEnvelopeWithStatus(int subId, String content) {
         try {
             ITelephony telephony = getITelephony();
diff --git a/telephony/java/android/telephony/TelephonyScanManager.java b/telephony/java/android/telephony/TelephonyScanManager.java
index 8e6c170..a1d40e8 100644
--- a/telephony/java/android/telephony/TelephonyScanManager.java
+++ b/telephony/java/android/telephony/TelephonyScanManager.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import static com.android.internal.util.Preconditions.checkNotNull;
 
 import android.annotation.Nullable;
diff --git a/telephony/java/android/telephony/UiccAccessRule.java b/telephony/java/android/telephony/UiccAccessRule.java
index 93ccba1..81a09c6 100644
--- a/telephony/java/android/telephony/UiccAccessRule.java
+++ b/telephony/java/android/telephony/UiccAccessRule.java
@@ -15,6 +15,8 @@
  */
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
diff --git a/telephony/java/android/telephony/VoLteServiceState.java b/telephony/java/android/telephony/VoLteServiceState.java
index 414b999..d4a27d9 100644
--- a/telephony/java/android/telephony/VoLteServiceState.java
+++ b/telephony/java/android/telephony/VoLteServiceState.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
 import android.os.Bundle;
@@ -51,8 +53,7 @@
     /**
      * Create a new VoLteServiceState from a intent notifier Bundle
      *
-     * This method is used by PhoneStateIntentReceiver and maybe by
-     * external applications.
+     * This method is maybe used by external applications.
      *
      * @param m Bundle from intent notifier
      * @return newly created VoLteServiceState
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index dbfb6a2..fab1bf2 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -28,7 +28,7 @@
 import android.provider.Telephony.Carriers;
 import android.telephony.Annotation.ApnType;
 import android.telephony.Annotation.NetworkType;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
diff --git a/telephony/java/android/telephony/data/DataService.java b/telephony/java/android/telephony/data/DataService.java
index 372bdf1..bff12b6 100644
--- a/telephony/java/android/telephony/data/DataService.java
+++ b/telephony/java/android/telephony/data/DataService.java
@@ -31,7 +31,7 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.telephony.AccessNetworkConstants;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
diff --git a/telephony/java/android/telephony/data/DataServiceCallback.java b/telephony/java/android/telephony/data/DataServiceCallback.java
index 11dc78a..d33d3f9 100644
--- a/telephony/java/android/telephony/data/DataServiceCallback.java
+++ b/telephony/java/android/telephony/data/DataServiceCallback.java
@@ -22,7 +22,7 @@
 import android.annotation.SystemApi;
 import android.net.LinkProperties;
 import android.os.RemoteException;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 import android.telephony.data.DataService.DataServiceProvider;
 
 import java.lang.annotation.Retention;
diff --git a/telephony/java/android/telephony/data/QualifiedNetworksService.java b/telephony/java/android/telephony/data/QualifiedNetworksService.java
index e793979..8220b16 100644
--- a/telephony/java/android/telephony/data/QualifiedNetworksService.java
+++ b/telephony/java/android/telephony/data/QualifiedNetworksService.java
@@ -28,7 +28,7 @@
 import android.os.RemoteException;
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
 import android.telephony.Annotation.ApnType;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
diff --git a/telephony/java/android/telephony/emergency/EmergencyNumber.java b/telephony/java/android/telephony/emergency/EmergencyNumber.java
index 1666265..cd3fc95 100644
--- a/telephony/java/android/telephony/emergency/EmergencyNumber.java
+++ b/telephony/java/android/telephony/emergency/EmergencyNumber.java
@@ -25,7 +25,7 @@
 import android.os.Parcelable;
 import android.telephony.CarrierConfigManager;
 import android.telephony.PhoneNumberUtils;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
diff --git a/telephony/java/android/telephony/ims/ImsConferenceState.java b/telephony/java/android/telephony/ims/ImsConferenceState.java
index 8d2049b..abfee61 100644
--- a/telephony/java/android/telephony/ims/ImsConferenceState.java
+++ b/telephony/java/android/telephony/ims/ImsConferenceState.java
@@ -24,7 +24,7 @@
 import android.os.Parcelable;
 import android.telecom.Call;
 import android.telecom.Connection;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 import android.util.Log;
 
 import java.util.HashMap;
diff --git a/telephony/java/android/telephony/ims/ImsExternalCallState.java b/telephony/java/android/telephony/ims/ImsExternalCallState.java
index dcb9c9d..136a83e 100644
--- a/telephony/java/android/telephony/ims/ImsExternalCallState.java
+++ b/telephony/java/android/telephony/ims/ImsExternalCallState.java
@@ -24,7 +24,7 @@
 import android.net.Uri;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
diff --git a/telephony/java/android/telephony/ims/ImsRcsManager.java b/telephony/java/android/telephony/ims/ImsRcsManager.java
index 4f0f089..c962714 100644
--- a/telephony/java/android/telephony/ims/ImsRcsManager.java
+++ b/telephony/java/android/telephony/ims/ImsRcsManager.java
@@ -35,6 +35,8 @@
 import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.util.Log;
 
+import com.android.internal.telephony.IIntegerConsumer;
+
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
@@ -158,9 +160,20 @@
         if (executor == null) {
             throw new IllegalArgumentException("Must include a non-null Executor.");
         }
+
+        IImsRcsController imsRcsController = getIImsRcsController();
+        if (imsRcsController == null) {
+            Log.e(TAG, "Register registration callback: IImsRcsController is null");
+            throw new ImsException("Cannot find remote IMS service",
+                    ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
+        }
+
         c.setExecutor(executor);
-        throw new UnsupportedOperationException("registerImsRegistrationCallback is not"
-                + "supported.");
+        try {
+            imsRcsController.registerImsRegistrationCallback(mSubId, c.getBinder());
+        } catch (RemoteException | IllegalStateException e) {
+            throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
+        }
     }
 
     /**{@inheritDoc}*/
@@ -171,8 +184,18 @@
         if (c == null) {
             throw new IllegalArgumentException("Must include a non-null RegistrationCallback.");
         }
-        throw new UnsupportedOperationException("unregisterImsRegistrationCallback is not"
-                + "supported.");
+
+        IImsRcsController imsRcsController = getIImsRcsController();
+        if (imsRcsController == null) {
+            Log.e(TAG, "Unregister registration callback: IImsRcsController is null");
+            throw new IllegalStateException("Cannot find remote IMS service");
+        }
+
+        try {
+            imsRcsController.unregisterImsRegistrationCallback(mSubId, c.getBinder());
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
     }
 
     /**{@inheritDoc}*/
@@ -186,8 +209,23 @@
         if (executor == null) {
             throw new IllegalArgumentException("Must include a non-null Executor.");
         }
-        throw new UnsupportedOperationException("getRegistrationState is not"
-                + "supported.");
+
+        IImsRcsController imsRcsController = getIImsRcsController();
+        if (imsRcsController == null) {
+            Log.e(TAG, "Get registration state error: IImsRcsController is null");
+            throw new IllegalStateException("Cannot find remote IMS service");
+        }
+
+        try {
+            imsRcsController.getImsRcsRegistrationState(mSubId, new IIntegerConsumer.Stub() {
+                @Override
+                public void accept(int result) {
+                    executor.execute(() -> stateCallback.accept(result));
+                }
+            });
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
     }
 
     /**{@inheritDoc}*/
@@ -202,10 +240,25 @@
         if (executor == null) {
             throw new IllegalArgumentException("Must include a non-null Executor.");
         }
-        throw new UnsupportedOperationException("getRegistrationTransportType is not"
-                + "supported.");
-    }
 
+        IImsRcsController imsRcsController = getIImsRcsController();
+        if (imsRcsController == null) {
+            Log.e(TAG, "Get registration transport type error: IImsRcsController is null");
+            throw new IllegalStateException("Cannot find remote IMS service");
+        }
+
+        try {
+            imsRcsController.getImsRcsRegistrationTransportType(mSubId,
+                    new IIntegerConsumer.Stub() {
+                        @Override
+                        public void accept(int result) {
+                            executor.execute(() -> transportTypeCallback.accept(result));
+                        }
+                    });
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
 
     /**
      * Registers an {@link AvailabilityCallback} with the system, which will provide RCS
diff --git a/telephony/java/android/telephony/ims/ImsSsData.java b/telephony/java/android/telephony/ims/ImsSsData.java
index 6b72859..2d2e638 100644
--- a/telephony/java/android/telephony/ims/ImsSsData.java
+++ b/telephony/java/android/telephony/ims/ImsSsData.java
@@ -22,7 +22,7 @@
 import android.annotation.TestApi;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
diff --git a/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl b/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl
index e81bac0..6f6aa44 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl
@@ -19,6 +19,9 @@
 import android.net.Uri;
 import android.telephony.ims.aidl.IImsCapabilityCallback;
 import android.telephony.ims.aidl.IRcsUceControllerCallback;
+import android.telephony.ims.aidl.IImsRegistrationCallback;
+
+import com.android.internal.telephony.IIntegerConsumer;
 
 /**
  * Interface used to interact with the Telephony IMS.
@@ -26,6 +29,13 @@
  * {@hide}
  */
 interface IImsRcsController {
+    // IMS RCS registration commands
+    void registerImsRegistrationCallback(int subId, IImsRegistrationCallback c);
+    void unregisterImsRegistrationCallback(int subId, IImsRegistrationCallback c);
+    void getImsRcsRegistrationState(int subId, IIntegerConsumer consumer);
+    void getImsRcsRegistrationTransportType(int subId, IIntegerConsumer consumer);
+
+    // IMS RCS capability commands
     void registerRcsAvailabilityCallback(int subId, IImsCapabilityCallback c);
     void unregisterRcsAvailabilityCallback(int subId, IImsCapabilityCallback c);
     boolean isCapable(int subId, int capability, int radioTech);
diff --git a/telephony/java/com/android/ims/ImsConfig.java b/telephony/java/com/android/ims/ImsConfig.java
index cfc803c..9116a3b 100644
--- a/telephony/java/com/android/ims/ImsConfig.java
+++ b/telephony/java/com/android/ims/ImsConfig.java
@@ -19,7 +19,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.RemoteException;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.ProvisioningManager;
 import android.telephony.ims.aidl.IImsConfig;
diff --git a/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java b/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java
index 1d6ec2d..8e86ff7 100644
--- a/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java
+++ b/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java
@@ -19,7 +19,7 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 import android.util.SparseIntArray;
 
 import com.android.internal.telephony.cdma.sms.UserData;
diff --git a/telephony/java/com/android/internal/telephony/TelephonyIntents.java b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
index f78c65f..54c07cf 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyIntents.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
@@ -123,32 +123,6 @@
     public static final String ACTION_EMERGENCY_CALL_STATE_CHANGED
             = TelephonyManager.ACTION_EMERGENCY_CALL_STATE_CHANGED;
 
-    /**
-     * Broadcast Action: The phone's signal strength has changed. The intent will have the
-     * following extra values:</p>
-     * <ul>
-     *   <li><em>phoneName</em> - A string version of the phone name.</li>
-     *   <li><em>asu</em> - A numeric value for the signal strength.
-     *          An ASU is 0-31 or -1 if unknown (for GSM, dBm = -113 - 2 * asu).
-     *          The following special values are defined:
-     *          <ul><li>0 means "-113 dBm or less".</li><li>31 means "-51 dBm or greater".</li></ul>
-     *   </li>
-     * </ul>
-     *
-     * <p class="note">
-     * You can <em>not</em> receive this through components declared
-     * in manifests, only by exlicitly registering for it with
-     * {@link android.content.Context#registerReceiver(android.content.BroadcastReceiver,
-     * android.content.IntentFilter) Context.registerReceiver()}.
-     *
-     * <p class="note">
-     * Requires the READ_PHONE_STATE permission.
-     *
-     * <p class="note">This is a protected intent that can only be sent
-     * by the system.
-     */
-    public static final String ACTION_SIGNAL_STRENGTH_CHANGED = "android.intent.action.SIG_STR";
-
 
     /**
      * Broadcast Action: The data connection state has changed for any one of the
diff --git a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
index e75c593..832502c 100644
--- a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
+++ b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
@@ -20,7 +20,7 @@
 import android.content.res.Resources;
 import android.sysprop.TelephonyProperties;
 import android.telephony.PhoneNumberUtils;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 import android.telephony.SmsCbLocation;
 import android.telephony.SmsCbMessage;
 import android.telephony.cdma.CdmaSmsCbProgramData;
diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java
index b5af646..cbf0f5c 100644
--- a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java
+++ b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java
@@ -18,7 +18,7 @@
 
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.res.Resources;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 import android.telephony.SmsCbCmasInfo;
 import android.telephony.cdma.CdmaSmsCbProgramData;
 import android.telephony.cdma.CdmaSmsCbProgramResults;
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
index 0681dc1..417aafd 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
@@ -28,7 +28,7 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.res.Resources;
 import android.telephony.PhoneNumberUtils;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 import android.text.TextUtils;
 
 import com.android.internal.telephony.EncodeException;
diff --git a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
index eed9a86..0dc7401 100644
--- a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
+++ b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
@@ -21,7 +21,7 @@
 import android.content.res.Resources.NotFoundException;
 import android.graphics.Bitmap;
 import android.graphics.Color;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.GsmAlphabet;
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index b4cafe4..656628eb 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -896,37 +896,76 @@
         assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_A);
     }
 
-    /** Test that observers execute correctly for different failure reasons */
+    /** Test that observers execute correctly for failures reasons that go through thresholding. */
     @Test
-    public void testFailureReasons() {
+    public void testNonImmediateFailureReasons() {
         PackageWatchdog watchdog = createWatchdog();
         TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
         TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
-        TestObserver observer3 = new TestObserver(OBSERVER_NAME_3);
-        TestObserver observer4 = new TestObserver(OBSERVER_NAME_4);
 
         watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
         watchdog.startObservingHealth(observer2, Arrays.asList(APP_B), SHORT_DURATION);
-        watchdog.startObservingHealth(observer3, Arrays.asList(APP_C), SHORT_DURATION);
-        watchdog.startObservingHealth(observer4, Arrays.asList(APP_D), SHORT_DURATION);
+
+        raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
+                VERSION_CODE)), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+        raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_B,
+                VERSION_CODE)), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING);
+
+        assertThat(observer1.getLastFailureReason()).isEqualTo(
+                PackageWatchdog.FAILURE_REASON_APP_CRASH);
+        assertThat(observer2.getLastFailureReason()).isEqualTo(
+                PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING);
+    }
+
+    /** Test that observers execute correctly for failures reasons that skip thresholding. */
+    @Test
+    public void testImmediateFailures() {
+        PackageWatchdog watchdog = createWatchdog();
+        TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
+
+        watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
 
         raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
                 VERSION_CODE)), PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
         raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_B,
                 VERSION_CODE)), PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK);
-        raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_C,
-                VERSION_CODE)), PackageWatchdog.FAILURE_REASON_APP_CRASH);
-        raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_D,
-                VERSION_CODE)), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING);
 
-        assertThat(observer1.getLastFailureReason()).isEqualTo(
-                PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
-        assertThat(observer2.getLastFailureReason()).isEqualTo(
-                PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK);
-        assertThat(observer3.getLastFailureReason()).isEqualTo(
-                PackageWatchdog.FAILURE_REASON_APP_CRASH);
-        assertThat(observer4.getLastFailureReason()).isEqualTo(
-                PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING);
+        assertThat(observer1.mMitigatedPackages).containsExactly(APP_A, APP_B);
+    }
+
+    /**
+     * Test that a persistent observer will mitigate failures if it wishes to observe a package.
+     */
+    @Test
+    public void testPersistentObserverWatchesPackage() {
+        PackageWatchdog watchdog = createWatchdog();
+        TestObserver persistentObserver = new TestObserver(OBSERVER_NAME_1);
+        persistentObserver.setPersistent(true);
+        persistentObserver.setMayObservePackages(true);
+
+        watchdog.startObservingHealth(persistentObserver, Arrays.asList(APP_B), SHORT_DURATION);
+
+        raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
+                VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN);
+        assertThat(persistentObserver.mHealthCheckFailedPackages).containsExactly(APP_A);
+    }
+
+    /**
+     * Test that a persistent observer will not mitigate failures if it does not wish to observe
+     * a given package.
+     */
+    @Test
+    public void testPersistentObserverDoesNotWatchPackage() {
+        PackageWatchdog watchdog = createWatchdog();
+        TestObserver persistentObserver = new TestObserver(OBSERVER_NAME_1);
+        persistentObserver.setPersistent(true);
+        persistentObserver.setMayObservePackages(false);
+
+        watchdog.startObservingHealth(persistentObserver, Arrays.asList(APP_B), SHORT_DURATION);
+
+        raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
+                VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN);
+        assertThat(persistentObserver.mHealthCheckFailedPackages).isEmpty();
     }
 
     private void adoptShellPermissions(String... permissions) {
@@ -964,7 +1003,12 @@
     /** Trigger package failures above the threshold. */
     private void raiseFatalFailureAndDispatch(PackageWatchdog watchdog,
             List<VersionedPackage> packages, int failureReason) {
-        for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) {
+        long triggerFailureCount = watchdog.getTriggerFailureCount();
+        if (failureReason == PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK
+                || failureReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
+            triggerFailureCount = 1;
+        }
+        for (int i = 0; i < triggerFailureCount; i++) {
             watchdog.onPackageFailure(packages, failureReason);
         }
         mTestLooper.dispatchAll();
@@ -1000,6 +1044,8 @@
         private final String mName;
         private int mImpact;
         private int mLastFailureReason;
+        private boolean mIsPersistent = false;
+        private boolean mMayObservePackages = false;
         final List<String> mHealthCheckFailedPackages = new ArrayList<>();
         final List<String> mMitigatedPackages = new ArrayList<>();
 
@@ -1028,9 +1074,25 @@
             return mName;
         }
 
+        public boolean isPersistent() {
+            return mIsPersistent;
+        }
+
+        public boolean mayObservePackage(String packageName) {
+            return mMayObservePackages;
+        }
+
         public int getLastFailureReason() {
             return mLastFailureReason;
         }
+
+        public void setPersistent(boolean persistent) {
+            mIsPersistent = persistent;
+        }
+
+        public void setMayObservePackages(boolean mayObservePackages) {
+            mMayObservePackages = mayObservePackages;
+        }
     }
 
     private static class TestController extends ExplicitHealthCheckController {
diff --git a/tests/utils/testutils/Android.bp b/tests/utils/testutils/Android.bp
index f71be7b..a6625ab 100644
--- a/tests/utils/testutils/Android.bp
+++ b/tests/utils/testutils/Android.bp
@@ -22,6 +22,7 @@
     static_libs: [
         "junit",
         "hamcrest-library",
+        "androidx.test.runner",
     ],
 
     libs: [
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MessageCapturingHandler.java b/tests/utils/testutils/java/com/android/server/accessibility/test/MessageCapturingHandler.java
similarity index 96%
rename from services/tests/servicestests/src/com/android/server/accessibility/MessageCapturingHandler.java
rename to tests/utils/testutils/java/com/android/server/accessibility/test/MessageCapturingHandler.java
index e2b517f..bce2ab5 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MessageCapturingHandler.java
+++ b/tests/utils/testutils/java/com/android/server/accessibility/test/MessageCapturingHandler.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.accessibility;
+package com.android.server.accessibility.test;
 
 import android.os.Handler;
 import android.os.Looper;
@@ -31,7 +31,7 @@
  * at their target.
  */
 public class MessageCapturingHandler extends Handler {
-    List<Pair<Message, Long>> timedMessages = new ArrayList<>();
+    public List<Pair<Message, Long>> timedMessages = new ArrayList<>();
 
     Handler.Callback mCallback;
 
diff --git a/tools/hiddenapi/generate_hiddenapi_lists.py b/tools/hiddenapi/generate_hiddenapi_lists.py
index 46105f4..0b2077d 100755
--- a/tools/hiddenapi/generate_hiddenapi_lists.py
+++ b/tools/hiddenapi/generate_hiddenapi_lists.py
@@ -149,7 +149,12 @@
         The package name of the class containing the field/method.
     """
     full_class_name = signature.split(";->")[0]
-    package_name = full_class_name[1:full_class_name.rindex("/")]
+    # Example: Landroid/hardware/radio/V1_2/IRadio$Proxy
+    if (full_class_name[0] != "L"):
+        raise ValueError("Expected to start with 'L': %s" % full_class_name)
+    full_class_name = full_class_name[1:]
+    # If full_class_name doesn't contain '/', then package_name will be ''.
+    package_name = full_class_name.rpartition("/")[0]
     return package_name.replace('/', '.')
 
 class FlagsDict:
diff --git a/tools/lock_agent/Android.bp b/tools/lock_agent/Android.bp
index 79dce4a..7b2ca9a 100644
--- a/tools/lock_agent/Android.bp
+++ b/tools/lock_agent/Android.bp
@@ -25,6 +25,7 @@
     srcs: ["agent.cpp"],
     static_libs: [
         "libbase",
+        "liblog",
         "libz",
         "slicer",
     ],
diff --git a/wifi/Android.bp b/wifi/Android.bp
index 09d5386..180368c 100644
--- a/wifi/Android.bp
+++ b/wifi/Android.bp
@@ -67,10 +67,15 @@
     optimize: {
         enabled: false
     },
+    hostdex: true, // for hiddenapi check
     visibility: [
         "//frameworks/base", // TODO(b/140299412) remove once all dependencies are fixed
         "//frameworks/opt/net/wifi/service:__subpackages__",
     ] + test_access_hidden_api_whitelist,
+    apex_available: [
+        "com.android.wifi",
+        "test_com.android.wifi",
+    ],
     plugins: ["java_api_finder"],
 }
 
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 71942f0..f490766 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -250,4 +250,6 @@
     void unregisterSuggestionConnectionStatusListener(int listenerIdentifier, String packageName);
 
     int calculateSignalLevel(int rssi);
+
+    List<WifiConfiguration> getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(in List<ScanResult> scanResults);
 }
diff --git a/wifi/java/android/net/wifi/SoftApConfiguration.java b/wifi/java/android/net/wifi/SoftApConfiguration.java
index 65e9b79..2b7f8af 100644
--- a/wifi/java/android/net/wifi/SoftApConfiguration.java
+++ b/wifi/java/android/net/wifi/SoftApConfiguration.java
@@ -182,6 +182,12 @@
     private final @SecurityType int mSecurityType;
 
     /**
+     * Delay in milliseconds before shutting down soft AP when
+     * there are no connected devices.
+     */
+    private final int mShutdownTimeoutMillis;
+
+    /**
      * Security types we support.
      */
     /** @hide */
@@ -213,7 +219,7 @@
     /** Private constructor for Builder and Parcelable implementation. */
     private SoftApConfiguration(@Nullable String ssid, @Nullable MacAddress bssid,
             @Nullable String passphrase, boolean hiddenSsid, @BandType int band, int channel,
-            @SecurityType int securityType, int maxNumberOfClients) {
+            @SecurityType int securityType, int maxNumberOfClients, int shutdownTimeoutMillis) {
         mSsid = ssid;
         mBssid = bssid;
         mPassphrase = passphrase;
@@ -222,6 +228,7 @@
         mChannel = channel;
         mSecurityType = securityType;
         mMaxNumberOfClients = maxNumberOfClients;
+        mShutdownTimeoutMillis = shutdownTimeoutMillis;
     }
 
     @Override
@@ -240,13 +247,14 @@
                 && mBand == other.mBand
                 && mChannel == other.mChannel
                 && mSecurityType == other.mSecurityType
-                && mMaxNumberOfClients == other.mMaxNumberOfClients;
+                && mMaxNumberOfClients == other.mMaxNumberOfClients
+                && mShutdownTimeoutMillis == other.mShutdownTimeoutMillis;
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(mSsid, mBssid, mPassphrase, mHiddenSsid,
-                mBand, mChannel, mSecurityType, mMaxNumberOfClients);
+                mBand, mChannel, mSecurityType, mMaxNumberOfClients, mShutdownTimeoutMillis);
     }
 
     @Override
@@ -261,6 +269,7 @@
         sbuf.append(" \n Channel =").append(mChannel);
         sbuf.append(" \n SecurityType=").append(getSecurityType());
         sbuf.append(" \n MaxClient=").append(mMaxNumberOfClients);
+        sbuf.append(" \n ShutdownTimeoutMillis=").append(mShutdownTimeoutMillis);
         return sbuf.toString();
     }
 
@@ -274,6 +283,7 @@
         dest.writeInt(mChannel);
         dest.writeInt(mSecurityType);
         dest.writeInt(mMaxNumberOfClients);
+        dest.writeInt(mShutdownTimeoutMillis);
     }
 
     @Override
@@ -289,7 +299,7 @@
                     in.readString(),
                     in.readParcelable(MacAddress.class.getClassLoader()),
                     in.readString(), in.readBoolean(), in.readInt(), in.readInt(), in.readInt(),
-                    in.readInt());
+                    in.readInt(), in.readInt());
         }
 
         @Override
@@ -316,19 +326,6 @@
         return mBssid;
     }
 
-    // TODO: Remove it after update the caller
-    /**
-     * Returns String set to be passphrase for the WPA2-PSK AP.
-     * {@link #setWpa2Passphrase(String)}.
-     */
-    @Nullable
-    public String getWpa2Passphrase() {
-        if (mSecurityType == SECURITY_TYPE_WPA2_PSK) {
-            return mPassphrase;
-        }
-        return null;
-    }
-
     /**
      * Returns String set to be passphrase for current AP.
      * {@link #setPassphrase(String, @SecurityType int)}.
@@ -381,6 +378,15 @@
     }
 
     /**
+     * Returns the shutdown timeout in milliseconds.
+     * The Soft AP will shutdown when there are no devices associated to it for
+     * the timeout duration. See {@link Builder#setShutdownTimeoutMillis(int)}.
+     */
+    public int getShutdownTimeoutMillis() {
+        return mShutdownTimeoutMillis;
+    }
+
+    /**
      * Builds a {@link SoftApConfiguration}, which allows an app to configure various aspects of a
      * Soft AP.
      *
@@ -396,6 +402,7 @@
         private int mChannel;
         private int mMaxNumberOfClients;
         private int mSecurityType;
+        private int mShutdownTimeoutMillis;
 
         /**
          * Constructs a Builder with default values (see {@link Builder}).
@@ -409,6 +416,7 @@
             mChannel = 0;
             mMaxNumberOfClients = 0;
             mSecurityType = SECURITY_TYPE_OPEN;
+            mShutdownTimeoutMillis = 0;
         }
 
         /**
@@ -425,6 +433,7 @@
             mChannel = other.mChannel;
             mMaxNumberOfClients = other.mMaxNumberOfClients;
             mSecurityType = other.mSecurityType;
+            mShutdownTimeoutMillis = other.mShutdownTimeoutMillis;
         }
 
         /**
@@ -435,7 +444,8 @@
         @NonNull
         public SoftApConfiguration build() {
             return new SoftApConfiguration(mSsid, mBssid, mPassphrase,
-                mHiddenSsid, mBand, mChannel, mSecurityType, mMaxNumberOfClients);
+                    mHiddenSsid, mBand, mChannel, mSecurityType, mMaxNumberOfClients,
+                    mShutdownTimeoutMillis);
         }
 
         /**
@@ -482,22 +492,6 @@
             return this;
         }
 
-        // TODO: Remove it after update the caller
-        /**
-         * Specifies that this AP should use WPA2-PSK with the given ASCII WPA2 passphrase.
-         * When set to null, an open network is created.
-         * <p>
-         *
-         * @param passphrase The passphrase to use, or null to unset a previously-set WPA2-PSK
-         *                   configuration.
-         * @return Builder for chaining.
-         * @throws IllegalArgumentException when the passphrase is the empty string
-         */
-        @NonNull
-        public Builder setWpa2Passphrase(@Nullable String passphrase) {
-            return setPassphrase(passphrase, SECURITY_TYPE_WPA2_PSK);
-        }
-
         /**
          * Specifies that this AP should use specific security type with the given ASCII passphrase.
          *
@@ -643,5 +637,30 @@
             mMaxNumberOfClients = maxNumberOfClients;
             return this;
         }
+
+        /**
+         * Specifies the shutdown timeout in milliseconds.
+         * The Soft AP will shut down when there are no devices connected to it for
+         * the timeout duration.
+         *
+         * Specify a value of 0 to have the framework automatically use default timeout
+         * setting which defined in {@link R.integer.config_wifi_framework_soft_ap_timeout_delay}
+         *
+         * <p>
+         * <li>If not set, defaults to 0</li>
+         * <li>The shut down timout will apply when
+         * {@link Settings.Global.SOFT_AP_TIMEOUT_ENABLED} is true</li>
+         *
+         * @param timeoutMillis milliseconds of the timeout delay.
+         * @return Builder for chaining.
+         */
+        @NonNull
+        public Builder setShutdownTimeoutMillis(int timeoutMillis) {
+            if (timeoutMillis < 0) {
+                throw new IllegalArgumentException("Invalid timeout value");
+            }
+            mShutdownTimeoutMillis = timeoutMillis;
+            return this;
+        }
     }
 }
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index e870481..1baab12 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -1366,6 +1366,36 @@
     }
 
     /**
+     * Retrieve a list of {@link WifiConfiguration} for available {@link WifiNetworkSuggestion}
+     * matching the given list of {@link ScanResult}.
+     *
+     * An available {@link WifiNetworkSuggestion} must satisfy:
+     * <ul>
+     * <li> Matching one of the {@link ScanResult} from the given list.
+     * <li> and {@link WifiNetworkSuggestion.Builder#setIsUserAllowedToManuallyConnect(boolean)} set
+     * to true.
+     * </ul>
+     *
+     * @param scanResults a list of scanResult.
+     * @return a list of @link WifiConfiguration} for available {@link WifiNetworkSuggestion}
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.NETWORK_SETTINGS,
+            android.Manifest.permission.NETWORK_SETUP_WIZARD
+    })
+    @NonNull
+    public List<WifiConfiguration> getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(
+            @NonNull List<ScanResult> scanResults) {
+        try {
+            return mService.getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(scanResults);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
      * Returns a list of unique Hotspot 2.0 OSU (Online Sign-Up) providers associated with a given
      * list of ScanResult.
      *
diff --git a/wifi/java/com/android/server/wifi/BaseWifiService.java b/wifi/java/com/android/server/wifi/BaseWifiService.java
index d91efbc..3c13562d 100644
--- a/wifi/java/com/android/server/wifi/BaseWifiService.java
+++ b/wifi/java/com/android/server/wifi/BaseWifiService.java
@@ -589,4 +589,10 @@
     public int calculateSignalLevel(int rssi) {
         throw new UnsupportedOperationException();
     }
+
+    @Override
+    public List<WifiConfiguration> getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(
+            List<ScanResult> scanResults) {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
index acd3343..eeea7e2 100644
--- a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
@@ -117,6 +117,7 @@
                 .setChannel(149, SoftApConfiguration.BAND_5GHZ)
                 .setHiddenSsid(true)
                 .setMaxNumberOfClients(10)
+                .setShutdownTimeoutMillis(500000)
                 .build();
         assertThat(original.getPassphrase()).isEqualTo("secretsecret");
         assertThat(original.getSecurityType()).isEqualTo(
@@ -125,6 +126,7 @@
         assertThat(original.getChannel()).isEqualTo(149);
         assertThat(original.isHiddenSsid()).isEqualTo(true);
         assertThat(original.getMaxNumberOfClients()).isEqualTo(10);
+        assertThat(original.getShutdownTimeoutMillis()).isEqualTo(500000);
 
         SoftApConfiguration unparceled = parcelUnparcel(original);
         assertThat(unparceled).isNotSameAs(original);
@@ -230,4 +232,10 @@
                 .build();
     }
 
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalieShutdownTimeoutMillis() {
+        SoftApConfiguration original = new SoftApConfiguration.Builder()
+                .setShutdownTimeoutMillis(-1)
+                .build();
+    }
 }
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index 5ac50a0..4b83718 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -2185,4 +2185,18 @@
         result = WifiManager.parseDppChannelList(channelList);
         assertEquals(result.size(), 0);
     }
+
+    /**
+     * Test getWifiConfigsForMatchedNetworkSuggestions for given scanResults.
+     */
+    @Test
+    public void testGetWifiConfigsForMatchedNetworkSuggestions() throws Exception {
+        List<WifiConfiguration> testResults = new ArrayList<>();
+        testResults.add(new WifiConfiguration());
+
+        when(mWifiService.getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(any(List.class)))
+                .thenReturn(testResults);
+        assertEquals(testResults, mWifiManager
+                .getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(new ArrayList<>()));
+    }
 }