Merge "Require permission to create trusted displays"
diff --git a/apct-tests/perftests/contentcapture/Android.bp b/apct-tests/perftests/contentcapture/Android.bp
index 66d7348..19a66ad 100644
--- a/apct-tests/perftests/contentcapture/Android.bp
+++ b/apct-tests/perftests/contentcapture/Android.bp
@@ -20,9 +20,10 @@
         "androidx.test.rules",
         "androidx.annotation_annotation",
         "apct-perftests-utils",
-        "collector-device-lib-platform",
+        "collector-device-lib",
         "compatibility-device-util-axt",
     ],
     platform_apis: true,
     test_suites: ["device-tests"],
+    data: [":perfetto_artifacts"],
 }
diff --git a/apct-tests/perftests/contentcapture/AndroidManifest.xml b/apct-tests/perftests/contentcapture/AndroidManifest.xml
index ee5577f..80957c7 100644
--- a/apct-tests/perftests/contentcapture/AndroidManifest.xml
+++ b/apct-tests/perftests/contentcapture/AndroidManifest.xml
@@ -16,11 +16,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.perftests.contentcapture">
 
-    <uses-sdk android:targetSdkVersion="28" />
-
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.REAL_GET_TASKS" />
-
     <application>
         <uses-library android:name="android.test.runner" />
         <activity android:name="android.view.contentcapture.CustomTestActivity"
diff --git a/apct-tests/perftests/contentcapture/AndroidTest.xml b/apct-tests/perftests/contentcapture/AndroidTest.xml
index d2386bb..d8e0a17 100644
--- a/apct-tests/perftests/contentcapture/AndroidTest.xml
+++ b/apct-tests/perftests/contentcapture/AndroidTest.xml
@@ -21,7 +21,39 @@
         <option name="test-file-name" value="ContentCapturePerfTests.apk" />
     </target_preparer>
 
+    <!-- Needed for pushing the trace config file -->
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="push-file" key="trace_config_detailed.textproto" value="/data/misc/perfetto-traces/trace_config.textproto" />
+    </target_preparer>
+
+    <!-- Needed for pulling the collected trace config on to the host -->
+    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+        <option name="pull-pattern-keys" value="perfetto_file_path" />
+    </metrics_collector>
+
+    <!-- Needed for storing the perfetto trace files in the sdcard/test_results-->
+    <option name="isolated-storage" value="false" />
+
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="com.android.perftests.contentcapture" />
+
+        <!-- Listener related args for collecting the traces and waiting for the device to stabilize. -->
+        <option name="device-listeners" value="android.device.collectors.ProcLoadListener,android.device.collectors.PerfettoListener" />
+        <!-- Guarantee that user defined RunListeners will be running before any of the default listeners defined in this runner. -->
+        <option name="instrumentation-arg" key="newRunListenerMode" value="true" />
+
+        <!-- ProcLoadListener related arguments -->
+        <!-- Wait for device last minute threshold to reach 3 with 2 minute timeout before starting the test run -->
+        <option name="instrumentation-arg" key="procload-collector:per_run" value="true" />
+        <option name="instrumentation-arg" key="proc-loadavg-threshold" value="3" />
+        <option name="instrumentation-arg" key="proc-loadavg-timeout" value="120000" />
+        <option name="instrumentation-arg" key="proc-loadavg-interval" value="10000" />
+
+        <!-- PerfettoListener related arguments -->
+        <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true" />
+        <option name="instrumentation-arg" key="perfetto_config_file" value="trace_config.textproto" />
+
+        <option name="instrumentation-arg" key="newRunListenerMode" value="true" />
     </test>
 </configuration>
diff --git a/apct-tests/perftests/windowmanager/Android.bp b/apct-tests/perftests/windowmanager/Android.bp
index 9fa99853..dbfe6ab 100644
--- a/apct-tests/perftests/windowmanager/Android.bp
+++ b/apct-tests/perftests/windowmanager/Android.bp
@@ -23,6 +23,7 @@
         "platform-test-annotations",
     ],
     test_suites: ["device-tests"],
+    data: [":perfetto_artifacts"],
     platform_apis: true,
     certificate: "platform",
 }
diff --git a/apct-tests/perftests/windowmanager/AndroidTest.xml b/apct-tests/perftests/windowmanager/AndroidTest.xml
index 0a80cf9..aee02c7 100644
--- a/apct-tests/perftests/windowmanager/AndroidTest.xml
+++ b/apct-tests/perftests/windowmanager/AndroidTest.xml
@@ -28,14 +28,42 @@
         <option name="run-command" value="cmd package compile -m speed com.android.perftests.wm" />
     </target_preparer>
 
+    <!-- Needed for pushing the trace config file -->
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="push-file" key="trace_config_detailed.textproto" value="/data/misc/perfetto-traces/trace_config.textproto" />
+    </target_preparer>
+
+    <!-- Needed for storing the perfetto trace files in the sdcard/test_results-->
+    <option name="isolated-storage" value="false" />
+
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="com.android.perftests.wm" />
         <option name="hidden-api-checks" value="false"/>
-        <option name="device-listeners" value="android.wm.WmPerfRunListener" />
+
+        <!-- Listener related args for collecting the traces and waiting for the device to stabilize. -->
+        <option name="device-listeners" value="android.wm.WmPerfRunListener,android.device.collectors.ProcLoadListener,android.device.collectors.PerfettoListener" />
+
+        <!-- Guarantee that user defined RunListeners will be running before any of the default listeners defined in this runner. -->
+        <option name="instrumentation-arg" key="newRunListenerMode" value="true" />
+
+        <!-- ProcLoadListener related arguments -->
+        <!-- Wait for device last minute threshold to reach 3 with 2 minute timeout before starting the test run -->
+        <option name="instrumentation-arg" key="procload-collector:per_run" value="true" />
+        <option name="instrumentation-arg" key="proc-loadavg-threshold" value="3" />
+        <option name="instrumentation-arg" key="proc-loadavg-timeout" value="120000" />
+        <option name="instrumentation-arg" key="proc-loadavg-interval" value="10000" />
+
+        <!-- PerfettoListener related arguments -->
+        <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true" />
+        <option name="instrumentation-arg" key="perfetto_config_file" value="trace_config.textproto" />
+
+        <option name="instrumentation-arg" key="newRunListenerMode" value="true" />
     </test>
 
     <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
-        <option name="directory-keys" value="/data/local/WmPerfTests" />
-        <option name="collect-on-run-ended-only" value="true" />
+        <option name="directory-keys" value="/data/local/tmp/WmPerfTests" />
+        <!-- Needed for pulling the collected trace config on to the host -->
+        <option name="pull-pattern-keys" value="perfetto_file_path" />
     </metrics_collector>
 </configuration>
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java b/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java
index dc6245b..cc0e939 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java
@@ -55,7 +55,7 @@
      * is in /data because while enabling method profling of system server, it cannot write the
      * trace to external storage.
      */
-    static final File BASE_OUT_PATH = new File("/data/local/WmPerfTests");
+    static final File BASE_OUT_PATH = new File("/data/local/tmp/WmPerfTests");
 
     @BeforeClass
     public static void setUpOnce() {
diff --git a/apex/appsearch/framework/java/android/app/appsearch/SearchSpec.java b/apex/appsearch/framework/java/android/app/appsearch/SearchSpec.java
index c276ae1..6e644ff 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/SearchSpec.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/SearchSpec.java
@@ -168,7 +168,9 @@
         /** Sets the maximum number of results to retrieve from the query */
         @NonNull
         public SearchSpec.Builder setNumToRetrieve(int numToRetrieve) {
-            mResultSpecBuilder.setNumToRetrieve(numToRetrieve);
+            // Just retrieve everything in one page.
+            // TODO(b/152359656): Realign these two apis properly.
+            mResultSpecBuilder.setNumPerPage(numToRetrieve);
             return this;
         }
 
diff --git a/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java b/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
index 876d73a..8e26052 100644
--- a/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
+++ b/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
@@ -125,10 +125,11 @@
     private int[] mTempExemptAppIds = mPowerExemptAllAppIds;
 
     /**
-     * Per-user packages that are in the EXEMPT bucket.
+     * Per-user packages that are in the EXEMPTED bucket.
      */
     @GuardedBy("mLock")
-    private final SparseSetArray<String> mExemptBucketPackages = new SparseSetArray<>();
+    @VisibleForTesting
+    final SparseSetArray<String> mExemptedBucketPackages = new SparseSetArray<>();
 
     @GuardedBy("mLock")
     final ArraySet<Listener> mListeners = new ArraySet<>();
@@ -180,7 +181,7 @@
         int ALL_UNEXEMPTED = 3;
         int ALL_EXEMPTION_LIST_CHANGED = 4;
         int TEMP_EXEMPTION_LIST_CHANGED = 5;
-        int EXEMPT_BUCKET_CHANGED = 6;
+        int EXEMPTED_BUCKET_CHANGED = 6;
         int FORCE_ALL_CHANGED = 7;
         int FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED = 8;
 
@@ -195,7 +196,7 @@
             "ALL_UNEXEMPTED",
             "ALL_EXEMPTION_LIST_CHANGED",
             "TEMP_EXEMPTION_LIST_CHANGED",
-            "EXEMPT_BUCKET_CHANGED",
+            "EXEMPTED_BUCKET_CHANGED",
             "FORCE_ALL_CHANGED",
             "FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED",
 
@@ -338,9 +339,9 @@
         }
 
         /**
-         * This is called when the EXEMPT bucket is updated.
+         * This is called when the EXEMPTED bucket is updated.
          */
-        private void onExemptBucketChanged(AppStateTrackerImpl sender) {
+        private void onExemptedBucketChanged(AppStateTrackerImpl sender) {
             // This doesn't happen very often, so just re-evaluate all jobs / alarms.
             updateAllJobs();
             unblockAllUnrestrictedAlarms();
@@ -424,6 +425,38 @@
         mHandler = new MyHandler(looper);
     }
 
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+            switch (intent.getAction()) {
+                case Intent.ACTION_USER_REMOVED:
+                    if (userId > 0) {
+                        mHandler.doUserRemoved(userId);
+                    }
+                    break;
+                case Intent.ACTION_BATTERY_CHANGED:
+                    synchronized (mLock) {
+                        mIsPluggedIn = (intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0);
+                    }
+                    updateForceAllAppStandbyState();
+                    break;
+                case Intent.ACTION_PACKAGE_REMOVED:
+                    if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+                        final String pkgName = intent.getData().getSchemeSpecificPart();
+                        final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
+                        // No need to notify for state change as all the alarms and jobs should be
+                        // removed too.
+                        mExemptedBucketPackages.remove(userId, pkgName);
+                        mRunAnyRestrictedPackages.remove(Pair.create(uid, pkgName));
+                        mActiveUids.delete(uid);
+                        mForegroundUids.delete(uid);
+                    }
+                    break;
+            }
+        }
+    };
+
     /**
      * Call it when the system is ready.
      */
@@ -465,8 +498,11 @@
             IntentFilter filter = new IntentFilter();
             filter.addAction(Intent.ACTION_USER_REMOVED);
             filter.addAction(Intent.ACTION_BATTERY_CHANGED);
-            filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
-            mContext.registerReceiver(new MyReceiver(), filter);
+            mContext.registerReceiver(mReceiver, filter);
+
+            filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
+            filter.addDataScheme(IntentFilter.SCHEME_PACKAGE);
+            mContext.registerReceiver(mReceiver, filter);
 
             refreshForcedAppStandbyUidPackagesLocked();
 
@@ -693,30 +729,6 @@
         }
     }
 
-    private final class MyReceiver extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) {
-                final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
-                if (userId > 0) {
-                    mHandler.doUserRemoved(userId);
-                }
-            } else if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) {
-                synchronized (mLock) {
-                    mIsPluggedIn = (intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0);
-                }
-                updateForceAllAppStandbyState();
-            } else if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())
-                    && !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
-                final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
-                final String pkgName = intent.getData().getSchemeSpecificPart();
-                if (mExemptBucketPackages.remove(userId, pkgName)) {
-                    mHandler.notifyExemptBucketChanged();
-                }
-            }
-        }
-    }
-
     final class StandbyTracker extends AppIdleStateChangeListener {
         @Override
         public void onAppIdleStateChanged(String packageName, int userId, boolean idle,
@@ -728,12 +740,12 @@
             synchronized (mLock) {
                 final boolean changed;
                 if (bucket == UsageStatsManager.STANDBY_BUCKET_EXEMPTED) {
-                    changed = mExemptBucketPackages.add(userId, packageName);
+                    changed = mExemptedBucketPackages.add(userId, packageName);
                 } else {
-                    changed = mExemptBucketPackages.remove(userId, packageName);
+                    changed = mExemptedBucketPackages.remove(userId, packageName);
                 }
                 if (changed) {
-                    mHandler.notifyExemptBucketChanged();
+                    mHandler.notifyExemptedBucketChanged();
                 }
             }
         }
@@ -755,7 +767,7 @@
         private static final int MSG_FORCE_ALL_CHANGED = 7;
         private static final int MSG_USER_REMOVED = 8;
         private static final int MSG_FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED = 9;
-        private static final int MSG_EXEMPT_BUCKET_CHANGED = 10;
+        private static final int MSG_EXEMPTED_BUCKET_CHANGED = 10;
 
         private static final int MSG_ON_UID_STATE_CHANGED = 11;
         private static final int MSG_ON_UID_ACTIVE = 12;
@@ -803,9 +815,9 @@
             obtainMessage(MSG_FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED).sendToTarget();
         }
 
-        public void notifyExemptBucketChanged() {
-            removeMessages(MSG_EXEMPT_BUCKET_CHANGED);
-            obtainMessage(MSG_EXEMPT_BUCKET_CHANGED).sendToTarget();
+        public void notifyExemptedBucketChanged() {
+            removeMessages(MSG_EXEMPTED_BUCKET_CHANGED);
+            obtainMessage(MSG_EXEMPTED_BUCKET_CHANGED).sendToTarget();
         }
 
         public void doUserRemoved(int userId) {
@@ -888,11 +900,11 @@
                     mStatLogger.logDurationStat(Stats.TEMP_EXEMPTION_LIST_CHANGED, start);
                     return;
 
-                case MSG_EXEMPT_BUCKET_CHANGED:
+                case MSG_EXEMPTED_BUCKET_CHANGED:
                     for (Listener l : cloneListeners()) {
-                        l.onExemptBucketChanged(sender);
+                        l.onExemptedBucketChanged(sender);
                     }
-                    mStatLogger.logDurationStat(Stats.EXEMPT_BUCKET_CHANGED, start);
+                    mStatLogger.logDurationStat(Stats.EXEMPTED_BUCKET_CHANGED, start);
                     return;
 
                 case MSG_FORCE_ALL_CHANGED:
@@ -1005,7 +1017,7 @@
             }
             cleanUpArrayForUser(mActiveUids, removedUserId);
             cleanUpArrayForUser(mForegroundUids, removedUserId);
-            mExemptBucketPackages.remove(removedUserId);
+            mExemptedBucketPackages.remove(removedUserId);
         }
     }
 
@@ -1148,7 +1160,7 @@
             }
             final int userId = UserHandle.getUserId(uid);
             if (mAppStandbyInternal.isAppIdleEnabled() && !mAppStandbyInternal.isInParole()
-                    && mExemptBucketPackages.contains(userId, packageName)) {
+                    && mExemptedBucketPackages.contains(userId, packageName)) {
                 return false;
             }
             return mForceAllAppsStandby;
@@ -1301,14 +1313,14 @@
 
             pw.println("Exempted bucket packages:");
             pw.increaseIndent();
-            for (int i = 0; i < mExemptBucketPackages.size(); i++) {
+            for (int i = 0; i < mExemptedBucketPackages.size(); i++) {
                 pw.print("User ");
-                pw.print(mExemptBucketPackages.keyAt(i));
+                pw.print(mExemptedBucketPackages.keyAt(i));
                 pw.println();
 
                 pw.increaseIndent();
-                for (int j = 0; j < mExemptBucketPackages.sizeAt(i); j++) {
-                    pw.print(mExemptBucketPackages.valueAt(i, j));
+                for (int j = 0; j < mExemptedBucketPackages.sizeAt(i); j++) {
+                    pw.print(mExemptedBucketPackages.valueAt(i, j));
                     pw.println();
                 }
                 pw.decreaseIndent();
@@ -1384,12 +1396,13 @@
                 proto.write(AppStateTrackerProto.TEMP_POWER_SAVE_EXEMPT_APP_IDS, appId);
             }
 
-            for (int i = 0; i < mExemptBucketPackages.size(); i++) {
-                for (int j = 0; j < mExemptBucketPackages.sizeAt(i); j++) {
+            for (int i = 0; i < mExemptedBucketPackages.size(); i++) {
+                for (int j = 0; j < mExemptedBucketPackages.sizeAt(i); j++) {
                     final long token2 = proto.start(AppStateTrackerProto.EXEMPTED_BUCKET_PACKAGES);
 
-                    proto.write(ExemptedPackage.USER_ID, mExemptBucketPackages.keyAt(i));
-                    proto.write(ExemptedPackage.PACKAGE_NAME, mExemptBucketPackages.valueAt(i, j));
+                    proto.write(ExemptedPackage.USER_ID, mExemptedBucketPackages.keyAt(i));
+                    proto.write(ExemptedPackage.PACKAGE_NAME,
+                            mExemptedBucketPackages.valueAt(i, j));
 
                     proto.end(token2);
                 }
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 4194fd0..e2f13c560 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -4310,7 +4310,7 @@
             filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
             filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
             filter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
-            filter.addDataScheme("package");
+            filter.addDataScheme(IntentFilter.SCHEME_PACKAGE);
             getContext().registerReceiver(this, filter);
              // Register for events related to sdcard installation.
             IntentFilter sdFilter = new IntentFilter();
diff --git a/api/current.txt b/api/current.txt
index 69700f3..9e0d88e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -45976,7 +45976,9 @@
     method public final void addExistingConnection(android.telecom.PhoneAccountHandle, android.telecom.Connection);
     method public final void conferenceRemoteConnections(android.telecom.RemoteConnection, android.telecom.RemoteConnection);
     method public final void connectionServiceFocusReleased();
+    method @Nullable public final android.telecom.RemoteConference createRemoteIncomingConference(@Nullable android.telecom.PhoneAccountHandle, @Nullable android.telecom.ConnectionRequest);
     method public final android.telecom.RemoteConnection createRemoteIncomingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
+    method @Nullable public final android.telecom.RemoteConference createRemoteOutgoingConference(@Nullable android.telecom.PhoneAccountHandle, @Nullable android.telecom.ConnectionRequest);
     method public final android.telecom.RemoteConnection createRemoteOutgoingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
     method public final java.util.Collection<android.telecom.Conference> getAllConferences();
     method public final java.util.Collection<android.telecom.Connection> getAllConnections();
@@ -46207,6 +46209,7 @@
 
   public final class RemoteConnection {
     method public void abort();
+    method public void addConferenceParticipants(@NonNull java.util.List<android.net.Uri>);
     method public void answer();
     method public void disconnect();
     method public android.net.Uri getAddress();
@@ -46984,7 +46987,7 @@
     method public long getNci();
     method @IntRange(from=0, to=3279165) public int getNrarfcn();
     method @IntRange(from=0, to=1007) public int getPci();
-    method @IntRange(from=0, to=65535) public int getTac();
+    method @IntRange(from=0, to=16777215) public int getTac();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityNr> CREATOR;
   }
@@ -65724,7 +65727,7 @@
 
 package java.math {
 
-  public class BigDecimal extends java.lang.Number implements java.lang.Comparable<java.math.BigDecimal> java.io.Serializable {
+  public class BigDecimal extends java.lang.Number implements java.lang.Comparable<java.math.BigDecimal> {
     ctor public BigDecimal(char[], int, int);
     ctor public BigDecimal(char[], int, int, java.math.MathContext);
     ctor public BigDecimal(char[]);
@@ -65811,19 +65814,20 @@
     field public static final java.math.BigDecimal ZERO;
   }
 
-  public class BigInteger extends java.lang.Number implements java.lang.Comparable<java.math.BigInteger> java.io.Serializable {
+  public class BigInteger extends java.lang.Number implements java.lang.Comparable<java.math.BigInteger> {
+    ctor public BigInteger(byte[]);
+    ctor public BigInteger(int, byte[]);
+    ctor public BigInteger(@NonNull String, int);
+    ctor public BigInteger(@NonNull String);
     ctor public BigInteger(int, @NonNull java.util.Random);
     ctor public BigInteger(int, int, @NonNull java.util.Random);
-    ctor public BigInteger(@NonNull String);
-    ctor public BigInteger(@NonNull String, int);
-    ctor public BigInteger(int, byte[]);
-    ctor public BigInteger(byte[]);
     method @NonNull public java.math.BigInteger abs();
     method @NonNull public java.math.BigInteger add(@NonNull java.math.BigInteger);
     method @NonNull public java.math.BigInteger and(@NonNull java.math.BigInteger);
     method @NonNull public java.math.BigInteger andNot(@NonNull java.math.BigInteger);
     method public int bitCount();
     method public int bitLength();
+    method public byte byteValueExact();
     method @NonNull public java.math.BigInteger clearBit(int);
     method public int compareTo(@NonNull java.math.BigInteger);
     method @NonNull public java.math.BigInteger divide(@NonNull java.math.BigInteger);
@@ -65834,8 +65838,10 @@
     method @NonNull public java.math.BigInteger gcd(@NonNull java.math.BigInteger);
     method public int getLowestSetBit();
     method public int intValue();
+    method public int intValueExact();
     method public boolean isProbablePrime(int);
     method public long longValue();
+    method public long longValueExact();
     method @NonNull public java.math.BigInteger max(@NonNull java.math.BigInteger);
     method @NonNull public java.math.BigInteger min(@NonNull java.math.BigInteger);
     method @NonNull public java.math.BigInteger mod(@NonNull java.math.BigInteger);
@@ -65852,6 +65858,7 @@
     method @NonNull public java.math.BigInteger setBit(int);
     method @NonNull public java.math.BigInteger shiftLeft(int);
     method @NonNull public java.math.BigInteger shiftRight(int);
+    method public short shortValueExact();
     method public int signum();
     method @NonNull public java.math.BigInteger subtract(@NonNull java.math.BigInteger);
     method public boolean testBit(int);
diff --git a/api/module-lib-current.txt b/api/module-lib-current.txt
index b587ea1..17545a4 100644
--- a/api/module-lib-current.txt
+++ b/api/module-lib-current.txt
@@ -7,6 +7,7 @@
 
   public class NotificationManager {
     method public boolean hasEnabledNotificationListener(@NonNull String, @NonNull android.os.UserHandle);
+    field public static final String ACTION_NOTIFICATION_LISTENER_ENABLED_CHANGED = "android.app.action.NOTIFICATION_LISTENER_ENABLED_CHANGED";
   }
 
 }
@@ -45,6 +46,13 @@
     field public static final int FLAG_EXCLUSIVE_GLOBAL_PRIORITY = 65536; // 0x10000
   }
 
+  public final class MediaSessionManager {
+    method public void dispatchMediaKeyEventAsSystemService(@NonNull android.view.KeyEvent);
+    method public boolean dispatchMediaKeyEventAsSystemService(@NonNull android.media.session.MediaSession.Token, @NonNull android.view.KeyEvent);
+    method public void dispatchVolumeKeyEventAsSystemService(@NonNull android.view.KeyEvent, int);
+    method public void dispatchVolumeKeyEventAsSystemService(@NonNull android.media.session.MediaSession.Token, @NonNull android.view.KeyEvent);
+  }
+
 }
 
 package android.net {
diff --git a/api/system-current.txt b/api/system-current.txt
index 721523c..3ab1645 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4196,7 +4196,8 @@
 
   public class AudioManager {
     method @Deprecated public int abandonAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDeviceForStrategyChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDeviceForStrategyChangedListener) throws java.lang.SecurityException;
+    method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDeviceForStrategyChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDeviceForStrategyChangedListener) throws java.lang.SecurityException;
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDevicesForStrategyChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDevicesForStrategyChangedListener) throws java.lang.SecurityException;
     method public void clearAudioServerStateCallback();
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int dispatchAudioFocusChange(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy);
     method @IntRange(from=0) public long getAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo);
@@ -4208,13 +4209,15 @@
     method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMaxVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
     method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMinVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
     method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.AudioDeviceAttributes getPreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy);
+    method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioDeviceAttributes> getPreferredDevicesForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy);
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int[] getSupportedSystemUsages();
     method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
     method public boolean isAudioServerRunning();
     method public boolean isHdmiSystemAudioSupported();
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int registerAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy);
     method public void registerVolumeGroupCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.VolumeGroupCallback);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDeviceForStrategyChangedListener(@NonNull android.media.AudioManager.OnPreferredDeviceForStrategyChangedListener);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDeviceForStrategyChangedListener(@NonNull android.media.AudioManager.OnPreferredDeviceForStrategyChangedListener);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDevicesForStrategyChangedListener(@NonNull android.media.AudioManager.OnPreferredDevicesForStrategyChangedListener);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean removePreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, @NonNull android.media.AudioAttributes, int, int) throws java.lang.IllegalArgumentException;
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.MODIFY_AUDIO_ROUTING}) public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, @NonNull android.media.AudioAttributes, int, int, android.media.audiopolicy.AudioPolicy) throws java.lang.IllegalArgumentException;
@@ -4224,6 +4227,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setDeviceVolumeBehavior(@NonNull android.media.AudioDeviceAttributes, int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setFocusRequestResult(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setPreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull android.media.AudioDeviceAttributes);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setPreferredDevicesForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull java.util.List<android.media.AudioDeviceAttributes>);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setSupportedSystemUsages(@NonNull int[]);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setVolumeIndexForAttributes(@NonNull android.media.AudioAttributes, int, int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy);
@@ -4247,8 +4251,12 @@
     method public void onAudioServerUp();
   }
 
-  public static interface AudioManager.OnPreferredDeviceForStrategyChangedListener {
-    method public void onPreferredDeviceForStrategyChanged(@NonNull android.media.audiopolicy.AudioProductStrategy, @Nullable android.media.AudioDeviceAttributes);
+  @Deprecated public static interface AudioManager.OnPreferredDeviceForStrategyChangedListener {
+    method @Deprecated public void onPreferredDeviceForStrategyChanged(@NonNull android.media.audiopolicy.AudioProductStrategy, @Nullable android.media.AudioDeviceAttributes);
+  }
+
+  public static interface AudioManager.OnPreferredDevicesForStrategyChangedListener {
+    method public void onPreferredDevicesForStrategyChanged(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull java.util.List<android.media.AudioDeviceAttributes>);
   }
 
   public abstract static class AudioManager.VolumeGroupCallback {
@@ -12163,6 +12171,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningStringValue(int, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setRcsProvisioningStatusForCapability(int, boolean);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.Callback);
+    field public static final int KEY_VOICE_OVER_WIFI_ENTITLEMENT_ID = 67; // 0x43
     field public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27; // 0x1b
     field public static final int KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE = 26; // 0x1a
     field public static final int PROVISIONING_VALUE_DISABLED = 0; // 0x0
diff --git a/api/test-current.txt b/api/test-current.txt
index 3332525..620c5b3 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -970,6 +970,7 @@
     method public boolean isSystemApp();
     field public static final int PRIVATE_FLAG_PRIVILEGED = 8; // 0x8
     field public int privateFlags;
+    field public int targetSandboxVersion;
   }
 
   public class LauncherApps {
@@ -1017,6 +1018,7 @@
     field public static final String FEATURE_ADOPTABLE_STORAGE = "android.software.adoptable_storage";
     field public static final String FEATURE_FILE_BASED_ENCRYPTION = "android.software.file_based_encryption";
     field public static final int FLAG_PERMISSION_APPLY_RESTRICTION = 16384; // 0x4000
+    field public static final int FLAG_PERMISSION_GRANTED_BY_DEFAULT = 32; // 0x20
     field public static final int FLAG_PERMISSION_GRANTED_BY_ROLE = 32768; // 0x8000
     field public static final int FLAG_PERMISSION_ONE_TIME = 65536; // 0x10000
     field public static final int FLAG_PERMISSION_POLICY_FIXED = 4; // 0x4
@@ -1780,6 +1782,9 @@
     method public static float getMasterBalance();
     method public static final int getNumStreamTypes();
     method public static int setMasterBalance(float);
+    field public static final int DEVICE_ROLE_DISABLED = 2; // 0x2
+    field public static final int DEVICE_ROLE_NONE = 0; // 0x0
+    field public static final int DEVICE_ROLE_PREFERRED = 1; // 0x1
     field public static final int STREAM_DEFAULT = -1; // 0xffffffff
   }
 
@@ -4613,6 +4618,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningStringValue(int, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setRcsProvisioningStatusForCapability(int, boolean);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.Callback);
+    field public static final int KEY_VOICE_OVER_WIFI_ENTITLEMENT_ID = 67; // 0x43
     field public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27; // 0x1b
     field public static final int KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE = 26; // 0x1a
     field public static final int PROVISIONING_VALUE_DISABLED = 0; // 0x0
diff --git a/cmds/incidentd/tests/section_list.cpp b/cmds/incidentd/tests/section_list.cpp
index 3a45af0..60f7fb7 100644
--- a/cmds/incidentd/tests/section_list.cpp
+++ b/cmds/incidentd/tests/section_list.cpp
@@ -1,4 +1,4 @@
-// This file is a dummy section_list.cpp used for test only.
+// This file is a stub section_list.cpp used for test only.
 #include "section_list.h"
 
 #include "frameworks/base/cmds/incidentd/tests/test_proto.pb.h"
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index 7c41951..88db1d8 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -73,10 +73,10 @@
         "src/HashableDimensionKey.cpp",
         "src/logd/LogEvent.cpp",
         "src/logd/LogEventQueue.cpp",
-        "src/matchers/CombinationLogMatchingTracker.cpp",
+        "src/matchers/CombinationAtomMatchingTracker.cpp",
         "src/matchers/EventMatcherWizard.cpp",
         "src/matchers/matcher_util.cpp",
-        "src/matchers/SimpleLogMatchingTracker.cpp",
+        "src/matchers/SimpleAtomMatchingTracker.cpp",
         "src/metadata_util.cpp",
         "src/metrics/CountMetricProducer.cpp",
         "src/metrics/duration_helper/MaxDurationTracker.cpp",
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index c95f4c0..6b335ee 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -4434,15 +4434,12 @@
         UNKNOWN = 0;
         CHIP_VIEWED = 1;
         CHIP_CLICKED = 2;
-        DIALOG_PRIVACY_SETTINGS = 3;
+        reserved 3; // Used only in beta builds, never shipped 
         DIALOG_DISMISS = 4;
         DIALOG_LINE_ITEM = 5;
     }
 
     optional Type type = 1 [(state_field_option).exclusive_state = true];
-
-    // Used if the type is LINE_ITEM
-    optional string package_name = 2;
 }
 
 /**
diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.cpp b/cmds/statsd/src/condition/CombinationConditionTracker.cpp
index e9875ba..7a1ece9 100644
--- a/cmds/statsd/src/condition/CombinationConditionTracker.cpp
+++ b/cmds/statsd/src/condition/CombinationConditionTracker.cpp
@@ -92,8 +92,8 @@
             mUnSlicedChildren.push_back(childIndex);
         }
         mChildren.push_back(childIndex);
-        mTrackerIndex.insert(childTracker->getLogTrackerIndex().begin(),
-                             childTracker->getLogTrackerIndex().end());
+        mTrackerIndex.insert(childTracker->getAtomMatchingTrackerIndex().begin(),
+                             childTracker->getAtomMatchingTrackerIndex().end());
     }
 
     mUnSlicedPartCondition = evaluateCombinationCondition(mUnSlicedChildren, mLogicalOperation,
diff --git a/cmds/statsd/src/condition/ConditionTracker.h b/cmds/statsd/src/condition/ConditionTracker.h
index 62736c8..9da1af4 100644
--- a/cmds/statsd/src/condition/ConditionTracker.h
+++ b/cmds/statsd/src/condition/ConditionTracker.h
@@ -18,7 +18,7 @@
 
 #include "condition/condition_util.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
-#include "matchers/LogMatchingTracker.h"
+#include "matchers/AtomMatchingTracker.h"
 #include "matchers/matcher_util.h"
 
 #include <utils/RefBase.h>
@@ -60,9 +60,9 @@
 
     // evaluate current condition given the new event.
     // event: the new log event
-    // eventMatcherValues: the results of the LogMatcherTrackers. LogMatcherTrackers always process
-    //                     event before ConditionTrackers, because ConditionTracker depends on
-    //                     LogMatchingTrackers.
+    // eventMatcherValues: the results of the AtomMatchingTrackers. AtomMatchingTrackers always
+    //                     process event before ConditionTrackers, because ConditionTracker depends
+    //                     on AtomMatchingTrackers.
     // mAllConditions: the list of all ConditionTracker
     // conditionCache: the cached non-sliced condition of the ConditionTrackers for this new event.
     // conditionChanged: the bit map to record whether the condition has changed.
@@ -88,8 +88,8 @@
             const bool isPartialLink,
             std::vector<ConditionState>& conditionCache) const = 0;
 
-    // return the list of LogMatchingTracker index that this ConditionTracker uses.
-    virtual const std::set<int>& getLogTrackerIndex() const {
+    // return the list of AtomMatchingTracker index that this ConditionTracker uses.
+    virtual const std::set<int>& getAtomMatchingTrackerIndex() const {
         return mTrackerIndex;
     }
 
@@ -136,7 +136,7 @@
     // if it's properly initialized.
     bool mInitialized;
 
-    // the list of LogMatchingTracker index that this ConditionTracker uses.
+    // the list of AtomMatchingTracker index that this ConditionTracker uses.
     std::set<int> mTrackerIndex;
 
     // This variable is only used for CombinationConditionTrackers.
diff --git a/cmds/statsd/src/matchers/LogMatchingTracker.h b/cmds/statsd/src/matchers/AtomMatchingTracker.h
similarity index 62%
rename from cmds/statsd/src/matchers/LogMatchingTracker.h
rename to cmds/statsd/src/matchers/AtomMatchingTracker.h
index 49a41ad..5194f99 100644
--- a/cmds/statsd/src/matchers/LogMatchingTracker.h
+++ b/cmds/statsd/src/matchers/AtomMatchingTracker.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef LOG_MATCHING_TRACKER_H
-#define LOG_MATCHING_TRACKER_H
+#ifndef ATOM_MATCHING_TRACKER_H
+#define ATOM_MATCHING_TRACKER_H
 
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 #include "logd/LogEvent.h"
@@ -31,35 +31,36 @@
 namespace os {
 namespace statsd {
 
-class LogMatchingTracker : public virtual RefBase {
+class AtomMatchingTracker : public virtual RefBase {
 public:
-    LogMatchingTracker(const int64_t& id, const int index, const uint64_t protoHash)
+    AtomMatchingTracker(const int64_t& id, const int index, const uint64_t protoHash)
         : mId(id), mIndex(index), mInitialized(false), mProtoHash(protoHash){};
 
-    virtual ~LogMatchingTracker(){};
+    virtual ~AtomMatchingTracker(){};
 
-    // Initialize this LogMatchingTracker.
-    // allLogMatchers: the list of the AtomMatcher proto config. This is needed because we don't
-    //                 store the proto object in memory. We only need it during initilization.
-    // allTrackers: the list of the LogMatchingTracker objects. It's a one-to-one mapping with
-    //              allLogMatchers. This is needed because the initialization is done recursively
-    //              for CombinationLogMatchingTrackers using DFS.
+    // Initialize this AtomMatchingTracker.
+    // allAtomMatchers: the list of the AtomMatcher proto config. This is needed because we don't
+    //                  store the proto object in memory. We only need it during initilization.
+    // allAtomMatchingTrackers: the list of the AtomMatchingTracker objects. It's a one-to-one
+    //                          mapping with allAtomMatchers. This is needed because the
+    //                          initialization is done recursively for
+    //                          CombinationAtomMatchingTrackers using DFS.
     // stack: a bit map to record which matcher has been visited on the stack. This is for detecting
     //        circle dependency.
-    virtual bool init(const std::vector<AtomMatcher>& allLogMatchers,
-                      const std::vector<sp<LogMatchingTracker>>& allTrackers,
+    virtual bool init(const std::vector<AtomMatcher>& allAtomMatchers,
+                      const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
                       const std::unordered_map<int64_t, int>& matcherMap,
                       std::vector<bool>& stack) = 0;
 
     // Called when a log event comes.
     // event: the log event.
-    // allTrackers: the list of all LogMatchingTrackers. This is needed because the log processing
-    //              is done recursively.
+    // allAtomMatchingTrackers: the list of all AtomMatchingTrackers. This is needed because the log
+    //                          processing is done recursively.
     // matcherResults: The cached results for all matchers for this event. Parent matchers can
     //                 directly access the children's matching results if they have been evaluated.
     //                 Otherwise, call children matchers' onLogEvent.
     virtual void onLogEvent(const LogEvent& event,
-                            const std::vector<sp<LogMatchingTracker>>& allTrackers,
+                            const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
                             std::vector<MatchingState>& matcherResults) = 0;
 
     // Get the tagIds that this matcher cares about. The combined collection is stored
@@ -81,23 +82,23 @@
     // Name of this matching. We don't really need the name, but it makes log message easy to debug.
     const int64_t mId;
 
-    // Index of this LogMatchingTracker in MetricsManager's container.
+    // Index of this AtomMatchingTracker in MetricsManager's container.
     const int mIndex;
 
-    // Whether this LogMatchingTracker has been properly initialized.
+    // Whether this AtomMatchingTracker has been properly initialized.
     bool mInitialized;
 
-    // The collection of the event tag ids that this LogMatchingTracker cares. So we can quickly
+    // The collection of the event tag ids that this AtomMatchingTracker cares. So we can quickly
     // return kNotMatched when we receive an event with an id not in the list. This is especially
-    // useful when we have a complex CombinationLogMatcherTracker.
+    // useful when we have a complex CombinationAtomMatchingTracker.
     std::set<int> mAtomIds;
 
     // Hash of the AtomMatcher's proto bytes from StatsdConfig.
     // Used to determine if the definition of this matcher has changed across a config update.
     const uint64_t mProtoHash;
 
-    FRIEND_TEST(MetricsManagerTest, TestCreateLogTrackerSimple);
-    FRIEND_TEST(MetricsManagerTest, TestCreateLogTrackerCombination);
+    FRIEND_TEST(MetricsManagerTest, TestCreateAtomMatchingTrackerSimple);
+    FRIEND_TEST(MetricsManagerTest, TestCreateAtomMatchingTrackerCombination);
     FRIEND_TEST(ConfigUpdateTest, TestUpdateMatchers);
 };
 
@@ -105,4 +106,4 @@
 }  // namespace os
 }  // namespace android
 
-#endif  // LOG_MATCHING_TRACKER_H
+#endif  // ATOM_MATCHING_TRACKER_H
diff --git a/cmds/statsd/src/matchers/CombinationLogMatchingTracker.cpp b/cmds/statsd/src/matchers/CombinationAtomMatchingTracker.cpp
similarity index 67%
rename from cmds/statsd/src/matchers/CombinationLogMatchingTracker.cpp
rename to cmds/statsd/src/matchers/CombinationAtomMatchingTracker.cpp
index 60bcc26..19637a4 100644
--- a/cmds/statsd/src/matchers/CombinationLogMatchingTracker.cpp
+++ b/cmds/statsd/src/matchers/CombinationAtomMatchingTracker.cpp
@@ -16,7 +16,8 @@
 
 #include "Log.h"
 
-#include "CombinationLogMatchingTracker.h"
+#include "CombinationAtomMatchingTracker.h"
+
 #include "matchers/matcher_util.h"
 
 namespace android {
@@ -27,18 +28,18 @@
 using std::unordered_map;
 using std::vector;
 
-CombinationLogMatchingTracker::CombinationLogMatchingTracker(const int64_t& id, const int index,
-                                                             const uint64_t protoHash)
-    : LogMatchingTracker(id, index, protoHash) {
+CombinationAtomMatchingTracker::CombinationAtomMatchingTracker(const int64_t& id, const int index,
+                                                               const uint64_t protoHash)
+    : AtomMatchingTracker(id, index, protoHash) {
 }
 
-CombinationLogMatchingTracker::~CombinationLogMatchingTracker() {
+CombinationAtomMatchingTracker::~CombinationAtomMatchingTracker() {
 }
 
-bool CombinationLogMatchingTracker::init(const vector<AtomMatcher>& allLogMatchers,
-                                         const vector<sp<LogMatchingTracker>>& allTrackers,
-                                         const unordered_map<int64_t, int>& matcherMap,
-                                         vector<bool>& stack) {
+bool CombinationAtomMatchingTracker::init(
+        const vector<AtomMatcher>& allAtomMatchers,
+        const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+        const unordered_map<int64_t, int>& matcherMap, vector<bool>& stack) {
     if (mInitialized) {
         return true;
     }
@@ -46,7 +47,7 @@
     // mark this node as visited in the recursion stack.
     stack[mIndex] = true;
 
-    AtomMatcher_Combination matcher = allLogMatchers[mIndex].combination();
+    AtomMatcher_Combination matcher = allAtomMatchers[mIndex].combination();
 
     // LogicalOperation is missing in the config
     if (!matcher.has_operation()) {
@@ -74,14 +75,15 @@
             return false;
         }
 
-        if (!allTrackers[childIndex]->init(allLogMatchers, allTrackers, matcherMap, stack)) {
+        if (!allAtomMatchingTrackers[childIndex]->init(allAtomMatchers, allAtomMatchingTrackers,
+                                                       matcherMap, stack)) {
             ALOGW("child matcher init failed %lld", (long long)child);
             return false;
         }
 
         mChildren.push_back(childIndex);
 
-        const set<int>& childTagIds = allTrackers[childIndex]->getAtomIds();
+        const set<int>& childTagIds = allAtomMatchingTrackers[childIndex]->getAtomIds();
         mAtomIds.insert(childTagIds.begin(), childTagIds.end());
     }
 
@@ -91,9 +93,9 @@
     return true;
 }
 
-void CombinationLogMatchingTracker::onLogEvent(const LogEvent& event,
-                                               const vector<sp<LogMatchingTracker>>& allTrackers,
-                                               vector<MatchingState>& matcherResults) {
+void CombinationAtomMatchingTracker::onLogEvent(
+        const LogEvent& event, const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+        vector<MatchingState>& matcherResults) {
     // this event has been processed.
     if (matcherResults[mIndex] != MatchingState::kNotComputed) {
         return;
@@ -107,8 +109,8 @@
     // evaluate children matchers if they haven't been evaluated.
     for (const int childIndex : mChildren) {
         if (matcherResults[childIndex] == MatchingState::kNotComputed) {
-            const sp<LogMatchingTracker>& child = allTrackers[childIndex];
-            child->onLogEvent(event, allTrackers, matcherResults);
+            const sp<AtomMatchingTracker>& child = allAtomMatchingTrackers[childIndex];
+            child->onLogEvent(event, allAtomMatchingTrackers, matcherResults);
         }
     }
 
diff --git a/cmds/statsd/src/matchers/CombinationLogMatchingTracker.h b/cmds/statsd/src/matchers/CombinationAtomMatchingTracker.h
similarity index 64%
rename from cmds/statsd/src/matchers/CombinationLogMatchingTracker.h
rename to cmds/statsd/src/matchers/CombinationAtomMatchingTracker.h
index 6b8a7fb..06a4932 100644
--- a/cmds/statsd/src/matchers/CombinationLogMatchingTracker.h
+++ b/cmds/statsd/src/matchers/CombinationAtomMatchingTracker.h
@@ -13,12 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-#ifndef COMBINATION_LOG_MATCHING_TRACKER_H
-#define COMBINATION_LOG_MATCHING_TRACKER_H
+#ifndef COMBINATION_ATOM_MATCHING_TRACKER_H
+#define COMBINATION_ATOM_MATCHING_TRACKER_H
 
 #include <unordered_map>
 #include <vector>
-#include "LogMatchingTracker.h"
+
+#include "AtomMatchingTracker.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 
 namespace android {
@@ -26,19 +27,18 @@
 namespace statsd {
 
 // Represents a AtomMatcher_Combination in the StatsdConfig.
-class CombinationLogMatchingTracker : public virtual LogMatchingTracker {
+class CombinationAtomMatchingTracker : public virtual AtomMatchingTracker {
 public:
-    CombinationLogMatchingTracker(const int64_t& id, const int index, const uint64_t protoHash);
+    CombinationAtomMatchingTracker(const int64_t& id, const int index, const uint64_t protoHash);
 
-    bool init(const std::vector<AtomMatcher>& allLogMatchers,
-              const std::vector<sp<LogMatchingTracker>>& allTrackers,
-              const std::unordered_map<int64_t, int>& matcherMap,
-              std::vector<bool>& stack);
+    bool init(const std::vector<AtomMatcher>& allAtomMatchers,
+              const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+              const std::unordered_map<int64_t, int>& matcherMap, std::vector<bool>& stack);
 
-    ~CombinationLogMatchingTracker();
+    ~CombinationAtomMatchingTracker();
 
     void onLogEvent(const LogEvent& event,
-                    const std::vector<sp<LogMatchingTracker>>& allTrackers,
+                    const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
                     std::vector<MatchingState>& matcherResults) override;
 
 private:
@@ -50,4 +50,4 @@
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
-#endif  // COMBINATION_LOG_MATCHING_TRACKER_H
+#endif  // COMBINATION_ATOM_MATCHING_TRACKER_H
diff --git a/cmds/statsd/src/matchers/EventMatcherWizard.h b/cmds/statsd/src/matchers/EventMatcherWizard.h
index 57ec2b3..5d780f2 100644
--- a/cmds/statsd/src/matchers/EventMatcherWizard.h
+++ b/cmds/statsd/src/matchers/EventMatcherWizard.h
@@ -16,7 +16,7 @@
 
 #pragma once
 
-#include "LogMatchingTracker.h"
+#include "AtomMatchingTracker.h"
 
 namespace android {
 namespace os {
@@ -25,7 +25,7 @@
 class EventMatcherWizard : public virtual android::RefBase {
 public:
     EventMatcherWizard(){};  // for testing
-    EventMatcherWizard(const std::vector<sp<LogMatchingTracker>>& eventTrackers)
+    EventMatcherWizard(const std::vector<sp<AtomMatchingTracker>>& eventTrackers)
         : mAllEventMatchers(eventTrackers){};
 
     virtual ~EventMatcherWizard(){};
@@ -33,7 +33,7 @@
     MatchingState matchLogEvent(const LogEvent& event, int matcher_index);
 
 private:
-    std::vector<sp<LogMatchingTracker>> mAllEventMatchers;
+    std::vector<sp<AtomMatchingTracker>> mAllEventMatchers;
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/matchers/SimpleAtomMatchingTracker.cpp b/cmds/statsd/src/matchers/SimpleAtomMatchingTracker.cpp
new file mode 100644
index 0000000..86b148d
--- /dev/null
+++ b/cmds/statsd/src/matchers/SimpleAtomMatchingTracker.cpp
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#define DEBUG false  // STOPSHIP if true
+#include "Log.h"
+
+#include "SimpleAtomMatchingTracker.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using std::unordered_map;
+using std::vector;
+
+SimpleAtomMatchingTracker::SimpleAtomMatchingTracker(const int64_t& id, const int index,
+                                                     const uint64_t protoHash,
+                                                     const SimpleAtomMatcher& matcher,
+                                                     const sp<UidMap>& uidMap)
+    : AtomMatchingTracker(id, index, protoHash), mMatcher(matcher), mUidMap(uidMap) {
+    if (!matcher.has_atom_id()) {
+        mInitialized = false;
+    } else {
+        mAtomIds.insert(matcher.atom_id());
+        mInitialized = true;
+    }
+}
+
+SimpleAtomMatchingTracker::~SimpleAtomMatchingTracker() {
+}
+
+bool SimpleAtomMatchingTracker::init(const vector<AtomMatcher>& allAtomMatchers,
+                                     const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+                                     const unordered_map<int64_t, int>& matcherMap,
+                                     vector<bool>& stack) {
+    // no need to do anything.
+    return mInitialized;
+}
+
+void SimpleAtomMatchingTracker::onLogEvent(
+        const LogEvent& event, const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+        vector<MatchingState>& matcherResults) {
+    if (matcherResults[mIndex] != MatchingState::kNotComputed) {
+        VLOG("Matcher %lld already evaluated ", (long long)mId);
+        return;
+    }
+
+    if (mAtomIds.find(event.GetTagId()) == mAtomIds.end()) {
+        matcherResults[mIndex] = MatchingState::kNotMatched;
+        return;
+    }
+
+    bool matched = matchesSimple(mUidMap, mMatcher, event);
+    matcherResults[mIndex] = matched ? MatchingState::kMatched : MatchingState::kNotMatched;
+    VLOG("Stats SimpleAtomMatcher %lld matched? %d", (long long)mId, matched);
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.h b/cmds/statsd/src/matchers/SimpleAtomMatchingTracker.h
similarity index 64%
rename from cmds/statsd/src/matchers/SimpleLogMatchingTracker.h
rename to cmds/statsd/src/matchers/SimpleAtomMatchingTracker.h
index e58e012..49cbe09 100644
--- a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.h
+++ b/cmds/statsd/src/matchers/SimpleAtomMatchingTracker.h
@@ -14,12 +14,13 @@
  * limitations under the License.
  */
 
-#ifndef SIMPLE_LOG_MATCHING_TRACKER_H
-#define SIMPLE_LOG_MATCHING_TRACKER_H
+#ifndef SIMPLE_ATOM_MATCHING_TRACKER_H
+#define SIMPLE_ATOM_MATCHING_TRACKER_H
 
 #include <unordered_map>
 #include <vector>
-#include "LogMatchingTracker.h"
+
+#include "AtomMatchingTracker.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 #include "packages/UidMap.h"
 
@@ -27,20 +28,20 @@
 namespace os {
 namespace statsd {
 
-class SimpleLogMatchingTracker : public virtual LogMatchingTracker {
+class SimpleAtomMatchingTracker : public virtual AtomMatchingTracker {
 public:
-    SimpleLogMatchingTracker(const int64_t& id, const int index, const uint64_t protoHash,
-                             const SimpleAtomMatcher& matcher, const sp<UidMap>& uidMap);
+    SimpleAtomMatchingTracker(const int64_t& id, const int index, const uint64_t protoHash,
+                              const SimpleAtomMatcher& matcher, const sp<UidMap>& uidMap);
 
-    ~SimpleLogMatchingTracker();
+    ~SimpleAtomMatchingTracker();
 
-    bool init(const std::vector<AtomMatcher>& allLogMatchers,
-              const std::vector<sp<LogMatchingTracker>>& allTrackers,
+    bool init(const std::vector<AtomMatcher>& allAtomMatchers,
+              const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
               const std::unordered_map<int64_t, int>& matcherMap,
               std::vector<bool>& stack) override;
 
     void onLogEvent(const LogEvent& event,
-                    const std::vector<sp<LogMatchingTracker>>& allTrackers,
+                    const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
                     std::vector<MatchingState>& matcherResults) override;
 
 private:
@@ -51,4 +52,4 @@
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
-#endif  // SIMPLE_LOG_MATCHING_TRACKER_H
+#endif  // SIMPLE_ATOM_MATCHING_TRACKER_H
diff --git a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp b/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp
deleted file mode 100644
index ff47d35..0000000
--- a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2017 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.
- */
-
-#define DEBUG false  // STOPSHIP if true
-#include "Log.h"
-
-#include "SimpleLogMatchingTracker.h"
-
-namespace android {
-namespace os {
-namespace statsd {
-
-using std::unordered_map;
-using std::vector;
-
-SimpleLogMatchingTracker::SimpleLogMatchingTracker(const int64_t& id, const int index,
-                                                   const uint64_t protoHash,
-                                                   const SimpleAtomMatcher& matcher,
-                                                   const sp<UidMap>& uidMap)
-    : LogMatchingTracker(id, index, protoHash), mMatcher(matcher), mUidMap(uidMap) {
-    if (!matcher.has_atom_id()) {
-        mInitialized = false;
-    } else {
-        mAtomIds.insert(matcher.atom_id());
-        mInitialized = true;
-    }
-}
-
-SimpleLogMatchingTracker::~SimpleLogMatchingTracker() {
-}
-
-bool SimpleLogMatchingTracker::init(const vector<AtomMatcher>& allLogMatchers,
-                                    const vector<sp<LogMatchingTracker>>& allTrackers,
-                                    const unordered_map<int64_t, int>& matcherMap,
-                                    vector<bool>& stack) {
-    // no need to do anything.
-    return mInitialized;
-}
-
-void SimpleLogMatchingTracker::onLogEvent(const LogEvent& event,
-                                          const vector<sp<LogMatchingTracker>>& allTrackers,
-                                          vector<MatchingState>& matcherResults) {
-    if (matcherResults[mIndex] != MatchingState::kNotComputed) {
-        VLOG("Matcher %lld already evaluated ", (long long)mId);
-        return;
-    }
-
-    if (mAtomIds.find(event.GetTagId()) == mAtomIds.end()) {
-        matcherResults[mIndex] = MatchingState::kNotMatched;
-        return;
-    }
-
-    bool matched = matchesSimple(mUidMap, mMatcher, event);
-    matcherResults[mIndex] = matched ? MatchingState::kMatched : MatchingState::kNotMatched;
-    VLOG("Stats SimpleLogMatcher %lld matched? %d", (long long)mId, matched);
-}
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/cmds/statsd/src/matchers/matcher_util.cpp b/cmds/statsd/src/matchers/matcher_util.cpp
index e6d9122..a7454c5 100644
--- a/cmds/statsd/src/matchers/matcher_util.cpp
+++ b/cmds/statsd/src/matchers/matcher_util.cpp
@@ -17,7 +17,7 @@
 #include "Log.h"
 
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
-#include "matchers/LogMatchingTracker.h"
+#include "matchers/AtomMatchingTracker.h"
 #include "matchers/matcher_util.h"
 #include "stats_util.h"
 
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.h b/cmds/statsd/src/metrics/GaugeMetricProducer.h
index 2fc772b..ef3a24a 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.h
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h
@@ -170,14 +170,14 @@
     // for each slice with the latest value.
     void updateCurrentSlicedBucketForAnomaly();
 
-    // Whitelist of fields to report. Empty means all are reported.
+    // Allowlist of fields to report. Empty means all are reported.
     std::vector<Matcher> mFieldMatchers;
 
     GaugeMetric::SamplingType mSamplingType;
 
     const int64_t mMaxPullDelayNs;
 
-    // apply a whitelist on the original input
+    // apply an allowlist on the original input
     std::shared_ptr<vector<FieldValue>> getGaugeFields(const LogEvent& event);
 
     // Util function to check whether the specified dimension hits the guardrail.
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index 2c3deca..5a52032 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -24,8 +24,8 @@
 #include "condition/CombinationConditionTracker.h"
 #include "condition/SimpleConditionTracker.h"
 #include "guardrail/StatsdStats.h"
-#include "matchers/CombinationLogMatchingTracker.h"
-#include "matchers/SimpleLogMatchingTracker.h"
+#include "matchers/CombinationAtomMatchingTracker.h"
+#include "matchers/SimpleAtomMatchingTracker.h"
 #include "parsing_utils/config_update_utils.h"
 #include "parsing_utils/metrics_manager_util.h"
 #include "state/StateManager.h"
@@ -77,14 +77,14 @@
     // Init the ttl end timestamp.
     refreshTtl(timeBaseNs);
 
-    mConfigValid =
-            initStatsdConfig(key, config, uidMap, pullerManager, anomalyAlarmMonitor,
-                             periodicAlarmMonitor, timeBaseNs, currentTimeNs, mTagIds,
-                             mAllAtomMatchers, mLogTrackerMap, mAllConditionTrackers,
-                             mAllMetricProducers, mAllAnomalyTrackers, mAllPeriodicAlarmTrackers,
-                             mConditionToMetricMap, mTrackerToMetricMap, mTrackerToConditionMap,
-                             mActivationAtomTrackerToMetricMap, mDeactivationAtomTrackerToMetricMap,
-                             mAlertTrackerMap, mMetricIndexesWithActivation, mNoReportMetricIds);
+    mConfigValid = initStatsdConfig(
+            key, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
+            timeBaseNs, currentTimeNs, mTagIds, mAllAtomMatchingTrackers, mAtomMatchingTrackerMap,
+            mAllConditionTrackers, mAllMetricProducers, mAllAnomalyTrackers,
+            mAllPeriodicAlarmTrackers, mConditionToMetricMap, mTrackerToMetricMap,
+            mTrackerToConditionMap, mActivationAtomTrackerToMetricMap,
+            mDeactivationAtomTrackerToMetricMap, mAlertTrackerMap, mMetricIndexesWithActivation,
+            mNoReportMetricIds);
 
     mHashStringsInReport = config.hash_strings_in_metric_report();
     mVersionStringsInReport = config.version_strings_in_metric_report();
@@ -93,7 +93,7 @@
     // Init allowed pushed atom uids.
     if (config.allowed_log_source_size() == 0) {
         mConfigValid = false;
-        ALOGE("Log source whitelist is empty! This config won't get any data. Suggest adding at "
+        ALOGE("Log source allowlist is empty! This config won't get any data. Suggest adding at "
                       "least AID_SYSTEM and AID_STATSD to the allowed_log_source field.");
     } else {
         for (const auto& source : config.allowed_log_source()) {
@@ -155,7 +155,7 @@
     // Guardrail. Reject the config if it's too big.
     if (mAllMetricProducers.size() > StatsdStats::kMaxMetricCountPerConfig ||
         mAllConditionTrackers.size() > StatsdStats::kMaxConditionCountPerConfig ||
-        mAllAtomMatchers.size() > StatsdStats::kMaxMatcherCountPerConfig) {
+        mAllAtomMatchingTrackers.size() > StatsdStats::kMaxMatcherCountPerConfig) {
         ALOGE("This config is too big! Reject!");
         mConfigValid = false;
     }
@@ -175,8 +175,9 @@
 
     // no matter whether this config is valid, log it in the stats.
     StatsdStats::getInstance().noteConfigReceived(
-            key, mAllMetricProducers.size(), mAllConditionTrackers.size(), mAllAtomMatchers.size(),
-            mAllAnomalyTrackers.size(), mAnnotations, mConfigValid);
+            key, mAllMetricProducers.size(), mAllConditionTrackers.size(),
+            mAllAtomMatchingTrackers.size(), mAllAnomalyTrackers.size(), mAnnotations,
+            mConfigValid);
     // Check active
     for (const auto& metric : mAllMetricProducers) {
         if (metric->isActive()) {
@@ -201,15 +202,15 @@
                                   const int64_t currentTimeNs,
                                   const sp<AlarmMonitor>& anomalyAlarmMonitor,
                                   const sp<AlarmMonitor>& periodicAlarmMonitor) {
-    vector<sp<LogMatchingTracker>> newAtomMatchers;
-    unordered_map<int64_t, int> newLogTrackerMap;
+    vector<sp<AtomMatchingTracker>> newAtomMatchingTrackers;
+    unordered_map<int64_t, int> newAtomMatchingTrackerMap;
     mTagIds.clear();
-    mConfigValid =
-            updateStatsdConfig(mConfigKey, config, mUidMap, mPullerManager, anomalyAlarmMonitor,
-                               periodicAlarmMonitor, timeBaseNs, currentTimeNs, mAllAtomMatchers,
-                               mLogTrackerMap, mTagIds, newAtomMatchers, newLogTrackerMap);
-    mAllAtomMatchers = newAtomMatchers;
-    mLogTrackerMap = newLogTrackerMap;
+    mConfigValid = updateStatsdConfig(
+            mConfigKey, config, mUidMap, mPullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
+            timeBaseNs, currentTimeNs, mAllAtomMatchingTrackers, mAtomMatchingTrackerMap, mTagIds,
+            newAtomMatchingTrackers, newAtomMatchingTrackerMap);
+    mAllAtomMatchingTrackers = newAtomMatchingTrackers;
+    mAtomMatchingTrackerMap = newAtomMatchingTrackerMap;
     return mConfigValid;
 }
 
@@ -502,11 +503,12 @@
         return;
     }
 
-    vector<MatchingState> matcherCache(mAllAtomMatchers.size(), MatchingState::kNotComputed);
+    vector<MatchingState> matcherCache(mAllAtomMatchingTrackers.size(),
+                                       MatchingState::kNotComputed);
 
     // Evaluate all atom matchers.
-    for (auto& matcher : mAllAtomMatchers) {
-        matcher->onLogEvent(event, mAllAtomMatchers, matcherCache);
+    for (auto& matcher : mAllAtomMatchingTrackers) {
+        matcher->onLogEvent(event, mAllAtomMatchingTrackers, matcherCache);
     }
 
     // Set of metrics that received an activation cancellation.
@@ -596,10 +598,10 @@
     }
 
     // For matched AtomMatchers, tell relevant metrics that a matched event has come.
-    for (size_t i = 0; i < mAllAtomMatchers.size(); i++) {
+    for (size_t i = 0; i < mAllAtomMatchingTrackers.size(); i++) {
         if (matcherCache[i] == MatchingState::kMatched) {
             StatsdStats::getInstance().noteMatcherMatched(mConfigKey,
-                                                          mAllAtomMatchers[i]->getId());
+                                                          mAllAtomMatchingTrackers[i]->getId());
             auto pair = mTrackerToMetricMap.find(i);
             if (pair != mTrackerToMetricMap.end()) {
                 auto& metricList = pair->second;
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index 6f9a2336..6f4b2d7 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -25,7 +25,7 @@
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 #include "frameworks/base/cmds/statsd/src/statsd_metadata.pb.h"
 #include "logd/LogEvent.h"
-#include "matchers/LogMatchingTracker.h"
+#include "matchers/AtomMatchingTracker.h"
 #include "metrics/MetricProducer.h"
 #include "packages/UidMap.h"
 
@@ -217,7 +217,7 @@
     // All event tags that are interesting to my metrics.
     std::set<int> mTagIds;
 
-    // We only store the sp of LogMatchingTracker, MetricProducer, and ConditionTracker in
+    // We only store the sp of AtomMatchingTracker, MetricProducer, and ConditionTracker in
     // MetricsManager. There are relationships between them, and the relationships are denoted by
     // index instead of pointers. The reasons for this are: (1) the relationship between them are
     // complicated, so storing index instead of pointers reduces the risk that A holds B's sp, and B
@@ -225,7 +225,7 @@
     // the related results from a cache using the index.
 
     // Hold all the atom matchers from the config.
-    std::vector<sp<LogMatchingTracker>> mAllAtomMatchers;
+    std::vector<sp<AtomMatchingTracker>> mAllAtomMatchingTrackers;
 
     // Hold all the conditions from the config.
     std::vector<sp<ConditionTracker>> mAllConditionTrackers;
@@ -239,29 +239,29 @@
     // Hold all periodic alarm trackers.
     std::vector<sp<AlarmTracker>> mAllPeriodicAlarmTrackers;
 
-    // To make updating configs faster, we map the id of a LogMatchingTracker, MetricProducer, and
+    // To make updating configs faster, we map the id of a AtomMatchingTracker, MetricProducer, and
     // ConditionTracker to its index in the corresponding vector.
 
-    // Maps the id of an atom matcher to its index in mAllAtomMatchers.
-    std::unordered_map<int64_t, int> mLogTrackerMap;
+    // Maps the id of an atom matcher to its index in mAllAtomMatchingTrackers.
+    std::unordered_map<int64_t, int> mAtomMatchingTrackerMap;
 
     // To make the log processing more efficient, we want to do as much filtering as possible
     // before we go into individual trackers and conditions to match.
 
     // 1st filter: check if the event tag id is in mTagIds.
     // 2nd filter: if it is, we parse the event because there is at least one member is interested.
-    //             then pass to all LogMatchingTrackers (itself also filter events by ids).
-    // 3nd filter: for LogMatchingTrackers that matched this event, we pass this event to the
+    //             then pass to all AtomMatchingTrackers (itself also filter events by ids).
+    // 3nd filter: for AtomMatchingTrackers that matched this event, we pass this event to the
     //             ConditionTrackers and MetricProducers that use this matcher.
     // 4th filter: for ConditionTrackers that changed value due to this event, we pass
     //             new conditions to  metrics that use this condition.
 
     // The following map is initialized from the statsd_config.
 
-    // Maps from the index of the LogMatchingTracker to index of MetricProducer.
+    // Maps from the index of the AtomMatchingTracker to index of MetricProducer.
     std::unordered_map<int, std::vector<int>> mTrackerToMetricMap;
 
-    // Maps from LogMatchingTracker to ConditionTracker
+    // Maps from AtomMatchingTracker to ConditionTracker
     std::unordered_map<int, std::vector<int>> mTrackerToConditionMap;
 
     // Maps from ConditionTracker to MetricProducer
diff --git a/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp
index 562e291..a9ae5a4 100644
--- a/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp
+++ b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp
@@ -29,9 +29,9 @@
 // Recursive function to determine if a matcher needs to be updated. Populates matcherToUpdate.
 // Returns whether the function was successful or not.
 bool determineMatcherUpdateStatus(const StatsdConfig& config, const int matcherIdx,
-                                  const unordered_map<int64_t, int>& oldLogTrackerMap,
-                                  const vector<sp<LogMatchingTracker>>& oldAtomMatchers,
-                                  const unordered_map<int64_t, int>& newLogTrackerMap,
+                                  const unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
+                                  const vector<sp<AtomMatchingTracker>>& oldAtomMatchingTrackers,
+                                  const unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
                                   vector<UpdateStatus>& matchersToUpdate,
                                   vector<bool>& cycleTracker) {
     // Have already examined this matcher.
@@ -42,8 +42,8 @@
     const AtomMatcher& matcher = config.atom_matcher(matcherIdx);
     int64_t id = matcher.id();
     // Check if new matcher.
-    const auto& oldLogTrackerIt = oldLogTrackerMap.find(id);
-    if (oldLogTrackerIt == oldLogTrackerMap.end()) {
+    const auto& oldAtomMatchingTrackerIt = oldAtomMatchingTrackerMap.find(id);
+    if (oldAtomMatchingTrackerIt == oldAtomMatchingTrackerMap.end()) {
         matchersToUpdate[matcherIdx] = UPDATE_REPLACE;
         return true;
     }
@@ -55,7 +55,7 @@
         return false;
     }
     uint64_t newProtoHash = Hash64(serializedMatcher);
-    if (newProtoHash != oldAtomMatchers[oldLogTrackerIt->second]->getProtoHash()) {
+    if (newProtoHash != oldAtomMatchingTrackers[oldAtomMatchingTrackerIt->second]->getProtoHash()) {
         matchersToUpdate[matcherIdx] = UPDATE_REPLACE;
         return true;
     }
@@ -70,8 +70,8 @@
             cycleTracker[matcherIdx] = true;
             UpdateStatus status = UPDATE_PRESERVE;
             for (const int64_t childMatcherId : matcher.combination().matcher()) {
-                const auto& childIt = newLogTrackerMap.find(childMatcherId);
-                if (childIt == newLogTrackerMap.end()) {
+                const auto& childIt = newAtomMatchingTrackerMap.find(childMatcherId);
+                if (childIt == newAtomMatchingTrackerMap.end()) {
                     ALOGW("Matcher %lld not found in the config", (long long)childMatcherId);
                     return false;
                 }
@@ -80,9 +80,9 @@
                     ALOGE("Cycle detected in matcher config");
                     return false;
                 }
-                if (!determineMatcherUpdateStatus(config, childIdx, oldLogTrackerMap,
-                                                  oldAtomMatchers, newLogTrackerMap,
-                                                  matchersToUpdate, cycleTracker)) {
+                if (!determineMatcherUpdateStatus(
+                            config, childIdx, oldAtomMatchingTrackerMap, oldAtomMatchingTrackers,
+                            newAtomMatchingTrackerMap, matchersToUpdate, cycleTracker)) {
                     return false;
                 }
 
@@ -103,25 +103,25 @@
     return true;
 }
 
-bool updateLogTrackers(const StatsdConfig& config, const sp<UidMap>& uidMap,
-                       const unordered_map<int64_t, int>& oldLogTrackerMap,
-                       const vector<sp<LogMatchingTracker>>& oldAtomMatchers, set<int>& allTagIds,
-                       unordered_map<int64_t, int>& newLogTrackerMap,
-                       vector<sp<LogMatchingTracker>>& newAtomMatchers) {
+bool updateAtomTrackers(const StatsdConfig& config, const sp<UidMap>& uidMap,
+                        const unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
+                        const vector<sp<AtomMatchingTracker>>& oldAtomMatchingTrackers,
+                        set<int>& allTagIds, unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
+                        vector<sp<AtomMatchingTracker>>& newAtomMatchingTrackers) {
     const int atomMatcherCount = config.atom_matcher_size();
 
     vector<AtomMatcher> matcherProtos;
     matcherProtos.reserve(atomMatcherCount);
-    newAtomMatchers.reserve(atomMatcherCount);
+    newAtomMatchingTrackers.reserve(atomMatcherCount);
 
     // Maps matcher id to their position in the config. For fast lookup of dependencies.
     for (int i = 0; i < atomMatcherCount; i++) {
         const AtomMatcher& matcher = config.atom_matcher(i);
-        if (newLogTrackerMap.find(matcher.id()) != newLogTrackerMap.end()) {
+        if (newAtomMatchingTrackerMap.find(matcher.id()) != newAtomMatchingTrackerMap.end()) {
             ALOGE("Duplicate atom matcher found for id %lld", (long long)matcher.id());
             return false;
         }
-        newLogTrackerMap[matcher.id()] = i;
+        newAtomMatchingTrackerMap[matcher.id()] = i;
         matcherProtos.push_back(matcher);
     }
 
@@ -129,8 +129,9 @@
     vector<UpdateStatus> matchersToUpdate(atomMatcherCount, UPDATE_UNKNOWN);
     vector<bool> cycleTracker(atomMatcherCount, false);
     for (int i = 0; i < atomMatcherCount; i++) {
-        if (!determineMatcherUpdateStatus(config, i, oldLogTrackerMap, oldAtomMatchers,
-                                          newLogTrackerMap, matchersToUpdate, cycleTracker)) {
+        if (!determineMatcherUpdateStatus(config, i, oldAtomMatchingTrackerMap,
+                                          oldAtomMatchingTrackers, newAtomMatchingTrackerMap,
+                                          matchersToUpdate, cycleTracker)) {
             return false;
         }
     }
@@ -140,23 +141,23 @@
         const int64_t id = matcher.id();
         switch (matchersToUpdate[i]) {
             case UPDATE_PRESERVE: {
-                const auto& oldLogTrackerIt = oldLogTrackerMap.find(id);
-                if (oldLogTrackerIt == oldLogTrackerMap.end()) {
+                const auto& oldAtomMatchingTrackerIt = oldAtomMatchingTrackerMap.find(id);
+                if (oldAtomMatchingTrackerIt == oldAtomMatchingTrackerMap.end()) {
                     ALOGE("Could not find AtomMatcher %lld in the previous config, but expected it "
                           "to be there",
                           (long long)id);
                     return false;
                 }
-                const int oldIndex = oldLogTrackerIt->second;
-                newAtomMatchers.push_back(oldAtomMatchers[oldIndex]);
+                const int oldIndex = oldAtomMatchingTrackerIt->second;
+                newAtomMatchingTrackers.push_back(oldAtomMatchingTrackers[oldIndex]);
                 break;
             }
             case UPDATE_REPLACE: {
-                sp<LogMatchingTracker> tracker = createLogTracker(matcher, i, uidMap);
+                sp<AtomMatchingTracker> tracker = createAtomMatchingTracker(matcher, i, uidMap);
                 if (tracker == nullptr) {
                     return false;
                 }
-                newAtomMatchers.push_back(tracker);
+                newAtomMatchingTrackers.push_back(tracker);
                 break;
             }
             default: {
@@ -168,8 +169,9 @@
     }
 
     std::fill(cycleTracker.begin(), cycleTracker.end(), false);
-    for (auto& matcher : newAtomMatchers) {
-        if (!matcher->init(matcherProtos, newAtomMatchers, newLogTrackerMap, cycleTracker)) {
+    for (auto& matcher : newAtomMatchingTrackers) {
+        if (!matcher->init(matcherProtos, newAtomMatchingTrackers, newAtomMatchingTrackerMap,
+                           cycleTracker)) {
             return false;
         }
         // Collect all the tag ids that are interesting. TagIds exist in leaf nodes only.
@@ -185,13 +187,14 @@
                         const sp<AlarmMonitor>& anomalyAlarmMonitor,
                         const sp<AlarmMonitor>& periodicAlarmMonitor, const int64_t timeBaseNs,
                         const int64_t currentTimeNs,
-                        const vector<sp<LogMatchingTracker>>& oldAtomMatchers,
-                        const unordered_map<int64_t, int>& oldLogTrackerMap, set<int>& allTagIds,
-                        vector<sp<LogMatchingTracker>>& newAtomMatchers,
-                        unordered_map<int64_t, int>& newLogTrackerMap) {
-    if (!updateLogTrackers(config, uidMap, oldLogTrackerMap, oldAtomMatchers, allTagIds,
-                           newLogTrackerMap, newAtomMatchers)) {
-        ALOGE("updateLogMatchingTrackers failed");
+                        const vector<sp<AtomMatchingTracker>>& oldAtomMatchingTrackers,
+                        const unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
+                        set<int>& allTagIds,
+                        vector<sp<AtomMatchingTracker>>& newAtomMatchingTrackers,
+                        unordered_map<int64_t, int>& newAtomMatchingTrackerMap) {
+    if (!updateAtomTrackers(config, uidMap, oldAtomMatchingTrackerMap, oldAtomMatchingTrackers,
+                            allTagIds, newAtomMatchingTrackerMap, newAtomMatchingTrackers)) {
+        ALOGE("updateAtomMatchingTrackers failed");
         return false;
     }
 
@@ -200,4 +203,4 @@
 
 }  // namespace statsd
 }  // namespace os
-}  // namespace android
\ No newline at end of file
+}  // namespace android
diff --git a/cmds/statsd/src/metrics/parsing_utils/config_update_utils.h b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.h
index 951ab03..ae7b216 100644
--- a/cmds/statsd/src/metrics/parsing_utils/config_update_utils.h
+++ b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.h
@@ -20,7 +20,7 @@
 
 #include "anomaly/AlarmMonitor.h"
 #include "external/StatsPullerManager.h"
-#include "matchers/LogMatchingTracker.h"
+#include "matchers/AtomMatchingTracker.h"
 
 namespace android {
 namespace os {
@@ -42,34 +42,34 @@
 // input:
 // [config]: the input StatsdConfig
 // [matcherIdx]: the index of the current matcher to be updated
-// [newLogTrackerMap]: matcher id to index mapping in the input StatsdConfig
-// [oldLogTrackerMap]: matcher id to index mapping in the existing MetricsManager
-// [oldAtomMatchers]: stores the existing LogMatchingTrackers
+// [newAtomMatchingTrackerMap]: matcher id to index mapping in the input StatsdConfig
+// [oldAtomMatchingTrackerMap]: matcher id to index mapping in the existing MetricsManager
+// [oldAtomMatchingTrackers]: stores the existing AtomMatchingTrackers
 // output:
 // [matchersToUpdate]: vector of the update status of each matcher. The matcherIdx index will
 //                     be updated from UPDATE_UNKNOWN after this call.
 // [cycleTracker]: intermediate param used during recursion.
 bool determineMatcherUpdateStatus(const StatsdConfig& config, const int matcherIdx,
-                                  const unordered_map<int64_t, int>& oldLogTrackerMap,
-                                  const vector<sp<LogMatchingTracker>>& oldAtomMatchers,
-                                  const unordered_map<int64_t, int>& newLogTrackerMap,
+                                  const unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
+                                  const vector<sp<AtomMatchingTracker>>& oldAtomMatchingTrackers,
+                                  const unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
                                   vector<UpdateStatus>& matchersToUpdate,
                                   vector<bool>& cycleTracker);
 
-// Updates the LogMatchingTrackers.
+// Updates the AtomMatchingTrackers.
 // input:
 // [config]: the input StatsdConfig
-// [oldLogTrackerMap]: existing matcher id to index mapping
-// [oldAtomMatchers]: stores the existing LogMatchingTrackers
+// [oldAtomMatchingTrackerMap]: existing matcher id to index mapping
+// [oldAtomMatchingTrackers]: stores the existing AtomMatchingTrackers
 // output:
 // [allTagIds]: contains the set of all interesting tag ids to this config.
-// [newLogTrackerMap]: new matcher id to index mapping
-// [newAtomMatchers]: stores the new LogMatchingTrackers
-bool updateLogTrackers(const StatsdConfig& config, const sp<UidMap>& uidMap,
-                       const unordered_map<int64_t, int>& oldLogTrackerMap,
-                       const vector<sp<LogMatchingTracker>>& oldAtomMatchers, set<int>& allTagIds,
-                       unordered_map<int64_t, int>& newLogTrackerMap,
-                       vector<sp<LogMatchingTracker>>& newAtomMatchers);
+// [newAtomMatchingTrackerMap]: new matcher id to index mapping
+// [newAtomMatchers]: stores the new AtomMatchingTrackers
+bool updateAtomTrackers(const StatsdConfig& config, const sp<UidMap>& uidMap,
+                        const unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
+                        const vector<sp<AtomMatchingTracker>>& oldAtomMatchingTrackers,
+                        set<int>& allTagIds, unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
+                        vector<sp<AtomMatchingTracker>>& newAtomMatchingTrackers);
 
 // Updates the existing MetricsManager from a new StatsdConfig.
 // Parameters are the members of MetricsManager. See MetricsManager for declaration.
@@ -78,12 +78,12 @@
                         const sp<AlarmMonitor>& anomalyAlarmMonitor,
                         const sp<AlarmMonitor>& periodicAlarmMonitor, const int64_t timeBaseNs,
                         const int64_t currentTimeNs,
-                        const std::vector<sp<LogMatchingTracker>>& oldAtomMatchers,
-                        const unordered_map<int64_t, int>& oldLogTrackerMap,
+                        const std::vector<sp<AtomMatchingTracker>>& oldAtomMatchingTrackers,
+                        const unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
                         std::set<int>& allTagIds,
-                        std::vector<sp<LogMatchingTracker>>& newAtomMatchers,
-                        unordered_map<int64_t, int>& newLogTrackerMap);
+                        std::vector<sp<AtomMatchingTracker>>& newAtomMatchingTrackers,
+                        unordered_map<int64_t, int>& newAtomMatchingTrackerMap);
 
 }  // namespace statsd
 }  // namespace os
-}  // namespace android
\ No newline at end of file
+}  // namespace android
diff --git a/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp b/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
index 52ef95d..daf67e9 100644
--- a/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
@@ -26,9 +26,9 @@
 #include "condition/SimpleConditionTracker.h"
 #include "external/StatsPullerManager.h"
 #include "hash.h"
-#include "matchers/CombinationLogMatchingTracker.h"
+#include "matchers/CombinationAtomMatchingTracker.h"
 #include "matchers/EventMatcherWizard.h"
-#include "matchers/SimpleLogMatchingTracker.h"
+#include "matchers/SimpleAtomMatchingTracker.h"
 #include "metrics/CountMetricProducer.h"
 #include "metrics/DurationMetricProducer.h"
 #include "metrics/EventMetricProducer.h"
@@ -62,8 +62,8 @@
 
 }  // namespace
 
-sp<LogMatchingTracker> createLogTracker(const AtomMatcher& logMatcher, const int index,
-                                        const sp<UidMap>& uidMap) {
+sp<AtomMatchingTracker> createAtomMatchingTracker(const AtomMatcher& logMatcher, const int index,
+                                                  const sp<UidMap>& uidMap) {
     string serializedMatcher;
     if (!logMatcher.SerializeToString(&serializedMatcher)) {
         ALOGE("Unable to serialize matcher %lld", (long long)logMatcher.id());
@@ -72,11 +72,11 @@
     uint64_t protoHash = Hash64(serializedMatcher);
     switch (logMatcher.contents_case()) {
         case AtomMatcher::ContentsCase::kSimpleAtomMatcher:
-            return new SimpleLogMatchingTracker(logMatcher.id(), index, protoHash,
-                                                logMatcher.simple_atom_matcher(), uidMap);
+            return new SimpleAtomMatchingTracker(logMatcher.id(), index, protoHash,
+                                                 logMatcher.simple_atom_matcher(), uidMap);
             break;
         case AtomMatcher::ContentsCase::kCombination:
-            return new CombinationLogMatchingTracker(logMatcher.id(), index, protoHash);
+            return new CombinationAtomMatchingTracker(logMatcher.id(), index, protoHash);
             break;
         default:
             ALOGE("Matcher \"%lld\" malformed", (long long)logMatcher.id());
@@ -84,18 +84,18 @@
     }
 }
 
-bool handleMetricWithLogTrackers(const int64_t what, const int metricIndex,
-                                 const bool usedForDimension,
-                                 const vector<sp<LogMatchingTracker>>& allAtomMatchers,
-                                 const unordered_map<int64_t, int>& logTrackerMap,
-                                 unordered_map<int, std::vector<int>>& trackerToMetricMap,
-                                 int& logTrackerIndex) {
-    auto logTrackerIt = logTrackerMap.find(what);
-    if (logTrackerIt == logTrackerMap.end()) {
+bool handleMetricWithAtomMatchingTrackers(
+        const int64_t what, const int metricIndex, const bool usedForDimension,
+        const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+        const unordered_map<int64_t, int>& atomMatchingTrackerMap,
+        unordered_map<int, std::vector<int>>& trackerToMetricMap, int& logTrackerIndex) {
+    auto logTrackerIt = atomMatchingTrackerMap.find(what);
+    if (logTrackerIt == atomMatchingTrackerMap.end()) {
         ALOGW("cannot find the AtomMatcher \"%lld\" in config", (long long)what);
         return false;
     }
-    if (usedForDimension && allAtomMatchers[logTrackerIt->second]->getAtomIds().size() > 1) {
+    if (usedForDimension &&
+        allAtomMatchingTrackers[logTrackerIt->second]->getAtomIds().size() > 1) {
         ALOGE("AtomMatcher \"%lld\" has more than one tag ids. When a metric has dimension, "
               "the \"what\" can only about one atom type.",
               (long long)what);
@@ -107,17 +107,17 @@
     return true;
 }
 
-bool handlePullMetricTriggerWithLogTrackers(
+bool handlePullMetricTriggerWithAtomMatchingTrackers(
         const int64_t trigger, const int metricIndex,
-        const vector<sp<LogMatchingTracker>>& allAtomMatchers,
-        const unordered_map<int64_t, int>& logTrackerMap,
+        const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+        const unordered_map<int64_t, int>& atomMatchingTrackerMap,
         unordered_map<int, std::vector<int>>& trackerToMetricMap, int& logTrackerIndex) {
-    auto logTrackerIt = logTrackerMap.find(trigger);
-    if (logTrackerIt == logTrackerMap.end()) {
+    auto logTrackerIt = atomMatchingTrackerMap.find(trigger);
+    if (logTrackerIt == atomMatchingTrackerMap.end()) {
         ALOGW("cannot find the AtomMatcher \"%lld\" in config", (long long)trigger);
         return false;
     }
-    if (allAtomMatchers[logTrackerIt->second]->getAtomIds().size() > 1) {
+    if (allAtomMatchingTrackers[logTrackerIt->second]->getAtomIds().size() > 1) {
         ALOGE("AtomMatcher \"%lld\" has more than one tag ids."
               "Trigger can only be one atom type.",
               (long long)trigger);
@@ -148,8 +148,6 @@
             ALOGW("cannot find Predicate \"%lld\" in the config", (long long)link.condition());
             return false;
         }
-        allConditionTrackers[condition_it->second]->setSliced(true);
-        allConditionTrackers[it->second]->setSliced(true);
     }
     conditionIndex = condition_it->second;
 
@@ -209,7 +207,7 @@
 bool handleMetricActivation(
         const StatsdConfig& config, const int64_t metricId, const int metricIndex,
         const unordered_map<int64_t, int>& metricToActivationMap,
-        const unordered_map<int64_t, int>& logTrackerMap,
+        const unordered_map<int64_t, int>& atomMatchingTrackerMap,
         unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap,
         unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
         vector<int>& metricsWithActivation,
@@ -225,8 +223,8 @@
     for (int i = 0; i < metricActivation.event_activation_size(); i++) {
         const EventActivation& activation = metricActivation.event_activation(i);
 
-        auto itr = logTrackerMap.find(activation.atom_matcher_id());
-        if (itr == logTrackerMap.end()) {
+        auto itr = atomMatchingTrackerMap.find(activation.atom_matcher_id());
+        if (itr == atomMatchingTrackerMap.end()) {
             ALOGE("Atom matcher not found for event activation.");
             return false;
         }
@@ -242,8 +240,8 @@
         eventActivationMap.emplace(atomMatcherIndex, activationWrapper);
 
         if (activation.has_deactivation_atom_matcher_id()) {
-            itr = logTrackerMap.find(activation.deactivation_atom_matcher_id());
-            if (itr == logTrackerMap.end()) {
+            itr = atomMatchingTrackerMap.find(activation.deactivation_atom_matcher_id());
+            if (itr == atomMatchingTrackerMap.end()) {
                 ALOGE("Atom matcher not found for event deactivation.");
                 return false;
             }
@@ -257,33 +255,35 @@
     return true;
 }
 
-bool initLogTrackers(const StatsdConfig& config, const sp<UidMap>& uidMap,
-                     unordered_map<int64_t, int>& logTrackerMap,
-                     vector<sp<LogMatchingTracker>>& allAtomMatchers, set<int>& allTagIds) {
+bool initAtomMatchingTrackers(const StatsdConfig& config, const sp<UidMap>& uidMap,
+                              unordered_map<int64_t, int>& atomMatchingTrackerMap,
+                              vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+                              set<int>& allTagIds) {
     vector<AtomMatcher> matcherConfigs;
     const int atomMatcherCount = config.atom_matcher_size();
     matcherConfigs.reserve(atomMatcherCount);
-    allAtomMatchers.reserve(atomMatcherCount);
+    allAtomMatchingTrackers.reserve(atomMatcherCount);
 
     for (int i = 0; i < atomMatcherCount; i++) {
         const AtomMatcher& logMatcher = config.atom_matcher(i);
-        int index = allAtomMatchers.size();
-        sp<LogMatchingTracker> tracker = createLogTracker(logMatcher, index, uidMap);
+        int index = allAtomMatchingTrackers.size();
+        sp<AtomMatchingTracker> tracker = createAtomMatchingTracker(logMatcher, index, uidMap);
         if (tracker == nullptr) {
             return false;
         }
-        allAtomMatchers.push_back(tracker);
-        if (logTrackerMap.find(logMatcher.id()) != logTrackerMap.end()) {
+        allAtomMatchingTrackers.push_back(tracker);
+        if (atomMatchingTrackerMap.find(logMatcher.id()) != atomMatchingTrackerMap.end()) {
             ALOGE("Duplicate AtomMatcher found!");
             return false;
         }
-        logTrackerMap[logMatcher.id()] = index;
+        atomMatchingTrackerMap[logMatcher.id()] = index;
         matcherConfigs.push_back(logMatcher);
     }
 
-    vector<bool> stackTracker2(allAtomMatchers.size(), false);
-    for (auto& matcher : allAtomMatchers) {
-        if (!matcher->init(matcherConfigs, allAtomMatchers, logTrackerMap, stackTracker2)) {
+    vector<bool> stackTracker2(allAtomMatchingTrackers.size(), false);
+    for (auto& matcher : allAtomMatchingTrackers) {
+        if (!matcher->init(matcherConfigs, allAtomMatchingTrackers, atomMatchingTrackerMap,
+                           stackTracker2)) {
             return false;
         }
         // Collect all the tag ids that are interesting. TagIds exist in leaf nodes only.
@@ -294,7 +294,7 @@
 }
 
 bool initConditions(const ConfigKey& key, const StatsdConfig& config,
-                    const unordered_map<int64_t, int>& logTrackerMap,
+                    const unordered_map<int64_t, int>& atomMatchingTrackerMap,
                     unordered_map<int64_t, int>& conditionTrackerMap,
                     vector<sp<ConditionTracker>>& allConditionTrackers,
                     unordered_map<int, std::vector<int>>& trackerToConditionMap,
@@ -312,7 +312,8 @@
         switch (condition.contents_case()) {
             case Predicate::ContentsCase::kSimplePredicate: {
                 allConditionTrackers.push_back(new SimpleConditionTracker(
-                        key, condition.id(), index, condition.simple_predicate(), logTrackerMap));
+                        key, condition.id(), index, condition.simple_predicate(),
+                        atomMatchingTrackerMap));
                 break;
             }
             case Predicate::ContentsCase::kCombination: {
@@ -339,7 +340,7 @@
                                     stackTracker, initialConditionCache)) {
             return false;
         }
-        for (const int trackerIndex : conditionTracker->getLogTrackerIndex()) {
+        for (const int trackerIndex : conditionTracker->getAtomMatchingTrackerIndex()) {
             auto& conditionList = trackerToConditionMap[trackerIndex];
             conditionList.push_back(i);
         }
@@ -367,9 +368,9 @@
 
 bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseTimeNs,
                  const int64_t currentTimeNs, const sp<StatsPullerManager>& pullerManager,
-                 const unordered_map<int64_t, int>& logTrackerMap,
+                 const unordered_map<int64_t, int>& atomMatchingTrackerMap,
                  const unordered_map<int64_t, int>& conditionTrackerMap,
-                 const vector<sp<LogMatchingTracker>>& allAtomMatchers,
+                 const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
                  const unordered_map<int64_t, int>& stateAtomIdMap,
                  const unordered_map<int64_t, unordered_map<int, int64_t>>& allStateGroupMaps,
                  vector<sp<ConditionTracker>>& allConditionTrackers,
@@ -382,7 +383,7 @@
                  unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
                  vector<int>& metricsWithActivation) {
     sp<ConditionWizard> wizard = new ConditionWizard(allConditionTrackers);
-    sp<EventMatcherWizard> matcherWizard = new EventMatcherWizard(allAtomMatchers);
+    sp<EventMatcherWizard> matcherWizard = new EventMatcherWizard(allAtomMatchingTrackers);
     const int allMetricsCount = config.count_metric_size() + config.duration_metric_size() +
                                 config.event_metric_size() + config.gauge_metric_size() +
                                 config.value_metric_size();
@@ -413,9 +414,10 @@
         int metricIndex = allMetricProducers.size();
         metricMap.insert({metric.id(), metricIndex});
         int trackerIndex;
-        if (!handleMetricWithLogTrackers(metric.what(), metricIndex,
-                                         metric.has_dimensions_in_what(), allAtomMatchers,
-                                         logTrackerMap, trackerToMetricMap, trackerIndex)) {
+        if (!handleMetricWithAtomMatchingTrackers(metric.what(), metricIndex,
+                                                  metric.has_dimensions_in_what(),
+                                                  allAtomMatchingTrackers, atomMatchingTrackerMap,
+                                                  trackerToMetricMap, trackerIndex)) {
             return false;
         }
 
@@ -450,7 +452,7 @@
         unordered_map<int, shared_ptr<Activation>> eventActivationMap;
         unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
         bool success = handleMetricActivation(
-                config, metric.id(), metricIndex, metricToActivationMap, logTrackerMap,
+                config, metric.id(), metricIndex, metricToActivationMap, atomMatchingTrackerMap,
                 activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
                 metricsWithActivation, eventActivationMap, eventDeactivationMap);
         if (!success) return false;
@@ -487,24 +489,27 @@
 
         int trackerIndices[3] = {-1, -1, -1};
         if (!simplePredicate.has_start() ||
-            !handleMetricWithLogTrackers(simplePredicate.start(), metricIndex,
-                                         metric.has_dimensions_in_what(), allAtomMatchers,
-                                         logTrackerMap, trackerToMetricMap, trackerIndices[0])) {
+            !handleMetricWithAtomMatchingTrackers(simplePredicate.start(), metricIndex,
+                                                  metric.has_dimensions_in_what(),
+                                                  allAtomMatchingTrackers, atomMatchingTrackerMap,
+                                                  trackerToMetricMap, trackerIndices[0])) {
             ALOGE("Duration metrics must specify a valid the start event matcher");
             return false;
         }
 
         if (simplePredicate.has_stop() &&
-            !handleMetricWithLogTrackers(simplePredicate.stop(), metricIndex,
-                                         metric.has_dimensions_in_what(), allAtomMatchers,
-                                         logTrackerMap, trackerToMetricMap, trackerIndices[1])) {
+            !handleMetricWithAtomMatchingTrackers(simplePredicate.stop(), metricIndex,
+                                                  metric.has_dimensions_in_what(),
+                                                  allAtomMatchingTrackers, atomMatchingTrackerMap,
+                                                  trackerToMetricMap, trackerIndices[1])) {
             return false;
         }
 
         if (simplePredicate.has_stop_all() &&
-            !handleMetricWithLogTrackers(simplePredicate.stop_all(), metricIndex,
-                                         metric.has_dimensions_in_what(), allAtomMatchers,
-                                         logTrackerMap, trackerToMetricMap, trackerIndices[2])) {
+            !handleMetricWithAtomMatchingTrackers(simplePredicate.stop_all(), metricIndex,
+                                                  metric.has_dimensions_in_what(),
+                                                  allAtomMatchingTrackers, atomMatchingTrackerMap,
+                                                  trackerToMetricMap, trackerIndices[2])) {
             return false;
         }
 
@@ -556,7 +561,7 @@
         unordered_map<int, shared_ptr<Activation>> eventActivationMap;
         unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
         bool success = handleMetricActivation(
-                config, metric.id(), metricIndex, metricToActivationMap, logTrackerMap,
+                config, metric.id(), metricIndex, metricToActivationMap, atomMatchingTrackerMap,
                 activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
                 metricsWithActivation, eventActivationMap, eventDeactivationMap);
         if (!success) return false;
@@ -580,8 +585,9 @@
             return false;
         }
         int trackerIndex;
-        if (!handleMetricWithLogTrackers(metric.what(), metricIndex, false, allAtomMatchers,
-                                         logTrackerMap, trackerToMetricMap, trackerIndex)) {
+        if (!handleMetricWithAtomMatchingTrackers(metric.what(), metricIndex, false,
+                                                  allAtomMatchingTrackers, atomMatchingTrackerMap,
+                                                  trackerToMetricMap, trackerIndex)) {
             return false;
         }
 
@@ -603,7 +609,7 @@
         unordered_map<int, shared_ptr<Activation>> eventActivationMap;
         unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
         bool success = handleMetricActivation(
-                config, metric.id(), metricIndex, metricToActivationMap, logTrackerMap,
+                config, metric.id(), metricIndex, metricToActivationMap, atomMatchingTrackerMap,
                 activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
                 metricsWithActivation, eventActivationMap, eventDeactivationMap);
         if (!success) return false;
@@ -636,13 +642,14 @@
         int metricIndex = allMetricProducers.size();
         metricMap.insert({metric.id(), metricIndex});
         int trackerIndex;
-        if (!handleMetricWithLogTrackers(metric.what(), metricIndex,
-                                         metric.has_dimensions_in_what(), allAtomMatchers,
-                                         logTrackerMap, trackerToMetricMap, trackerIndex)) {
+        if (!handleMetricWithAtomMatchingTrackers(metric.what(), metricIndex,
+                                                  metric.has_dimensions_in_what(),
+                                                  allAtomMatchingTrackers, atomMatchingTrackerMap,
+                                                  trackerToMetricMap, trackerIndex)) {
             return false;
         }
 
-        sp<LogMatchingTracker> atomMatcher = allAtomMatchers.at(trackerIndex);
+        sp<AtomMatchingTracker> atomMatcher = allAtomMatchingTrackers.at(trackerIndex);
         // If it is pulled atom, it should be simple matcher with one tagId.
         if (atomMatcher->getAtomIds().size() != 1) {
             return false;
@@ -691,7 +698,7 @@
         unordered_map<int, shared_ptr<Activation>> eventActivationMap;
         unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
         bool success = handleMetricActivation(
-                config, metric.id(), metricIndex, metricToActivationMap, logTrackerMap,
+                config, metric.id(), metricIndex, metricToActivationMap, atomMatchingTrackerMap,
                 activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
                 metricsWithActivation, eventActivationMap, eventDeactivationMap);
         if (!success) return false;
@@ -727,13 +734,14 @@
         int metricIndex = allMetricProducers.size();
         metricMap.insert({metric.id(), metricIndex});
         int trackerIndex;
-        if (!handleMetricWithLogTrackers(metric.what(), metricIndex,
-                                         metric.has_dimensions_in_what(), allAtomMatchers,
-                                         logTrackerMap, trackerToMetricMap, trackerIndex)) {
+        if (!handleMetricWithAtomMatchingTrackers(metric.what(), metricIndex,
+                                                  metric.has_dimensions_in_what(),
+                                                  allAtomMatchingTrackers, atomMatchingTrackerMap,
+                                                  trackerToMetricMap, trackerIndex)) {
             return false;
         }
 
-        sp<LogMatchingTracker> atomMatcher = allAtomMatchers.at(trackerIndex);
+        sp<AtomMatchingTracker> atomMatcher = allAtomMatchingTrackers.at(trackerIndex);
         // For GaugeMetric atom, it should be simple matcher with one tagId.
         if (atomMatcher->getAtomIds().size() != 1) {
             return false;
@@ -752,12 +760,13 @@
             if (metric.sampling_type() != GaugeMetric::FIRST_N_SAMPLES) {
                 return false;
             }
-            if (!handlePullMetricTriggerWithLogTrackers(metric.trigger_event(), metricIndex,
-                                                        allAtomMatchers, logTrackerMap,
-                                                        trackerToMetricMap, triggerTrackerIndex)) {
+            if (!handlePullMetricTriggerWithAtomMatchingTrackers(
+                        metric.trigger_event(), metricIndex, allAtomMatchingTrackers,
+                        atomMatchingTrackerMap, trackerToMetricMap, triggerTrackerIndex)) {
                 return false;
             }
-            sp<LogMatchingTracker> triggerAtomMatcher = allAtomMatchers.at(triggerTrackerIndex);
+            sp<AtomMatchingTracker> triggerAtomMatcher =
+                    allAtomMatchingTrackers.at(triggerTrackerIndex);
             triggerAtomId = *(triggerAtomMatcher->getAtomIds().begin());
         }
 
@@ -785,7 +794,7 @@
         unordered_map<int, shared_ptr<Activation>> eventActivationMap;
         unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
         bool success = handleMetricActivation(
-                config, metric.id(), metricIndex, metricToActivationMap, logTrackerMap,
+                config, metric.id(), metricIndex, metricToActivationMap, atomMatchingTrackerMap,
                 activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
                 metricsWithActivation, eventActivationMap, eventDeactivationMap);
         if (!success) return false;
@@ -923,8 +932,8 @@
                       const sp<AlarmMonitor>& anomalyAlarmMonitor,
                       const sp<AlarmMonitor>& periodicAlarmMonitor, const int64_t timeBaseNs,
                       const int64_t currentTimeNs, set<int>& allTagIds,
-                      vector<sp<LogMatchingTracker>>& allAtomMatchers,
-                      unordered_map<int64_t, int>& logTrackerMap,
+                      vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+                      unordered_map<int64_t, int>& atomMatchingTrackerMap,
                       vector<sp<ConditionTracker>>& allConditionTrackers,
                       vector<sp<MetricProducer>>& allMetricProducers,
                       vector<sp<AnomalyTracker>>& allAnomalyTrackers,
@@ -942,14 +951,15 @@
     unordered_map<int64_t, int> stateAtomIdMap;
     unordered_map<int64_t, unordered_map<int, int64_t>> allStateGroupMaps;
 
-    if (!initLogTrackers(config, uidMap, logTrackerMap, allAtomMatchers, allTagIds)) {
-        ALOGE("initLogMatchingTrackers failed");
+    if (!initAtomMatchingTrackers(config, uidMap, atomMatchingTrackerMap, allAtomMatchingTrackers,
+                                  allTagIds)) {
+        ALOGE("initAtomMatchingTrackers failed");
         return false;
     }
-    VLOG("initLogMatchingTrackers succeed...");
+    VLOG("initAtomMatchingTrackers succeed...");
 
-    if (!initConditions(key, config, logTrackerMap, conditionTrackerMap, allConditionTrackers,
-                        trackerToConditionMap, initialConditionCache)) {
+    if (!initConditions(key, config, atomMatchingTrackerMap, conditionTrackerMap,
+                        allConditionTrackers, trackerToConditionMap, initialConditionCache)) {
         ALOGE("initConditionTrackers failed");
         return false;
     }
@@ -958,12 +968,12 @@
         ALOGE("initStates failed");
         return false;
     }
-    if (!initMetrics(key, config, timeBaseNs, currentTimeNs, pullerManager, logTrackerMap,
-                     conditionTrackerMap, allAtomMatchers, stateAtomIdMap, allStateGroupMaps,
-                     allConditionTrackers, initialConditionCache, allMetricProducers,
-                     conditionToMetricMap, trackerToMetricMap, metricProducerMap, noReportMetricIds,
-                     activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
-                     metricsWithActivation)) {
+    if (!initMetrics(key, config, timeBaseNs, currentTimeNs, pullerManager, atomMatchingTrackerMap,
+                     conditionTrackerMap, allAtomMatchingTrackers, stateAtomIdMap,
+                     allStateGroupMaps, allConditionTrackers, initialConditionCache,
+                     allMetricProducers, conditionToMetricMap, trackerToMetricMap,
+                     metricProducerMap, noReportMetricIds, activationAtomTrackerToMetricMap,
+                     deactivationAtomTrackerToMetricMap, metricsWithActivation)) {
         ALOGE("initMetricProducers failed");
         return false;
     }
diff --git a/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.h b/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.h
index ed9951f..4cfd1b0 100644
--- a/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.h
+++ b/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.h
@@ -23,7 +23,7 @@
 #include "anomaly/AlarmTracker.h"
 #include "condition/ConditionTracker.h"
 #include "external/StatsPullerManager.h"
-#include "matchers/LogMatchingTracker.h"
+#include "matchers/AtomMatchingTracker.h"
 #include "metrics/MetricProducer.h"
 
 namespace android {
@@ -33,14 +33,14 @@
 // Helper functions for creating individual config components from StatsdConfig.
 // Should only be called from metrics_manager_util and config_update_utils.
 
-// Create a LogMatchingTracker.
+// Create a AtomMatchingTracker.
 // input:
 // [logMatcher]: the input AtomMatcher from the StatsdConfig
 // [index]: the index of the matcher
 // output:
-// new LogMatchingTracker, or null if the tracker is unable to be created
-sp<LogMatchingTracker> createLogTracker(const AtomMatcher& logMatcher, const int index,
-                                        const sp<UidMap>& uidMap);
+// new AtomMatchingTracker, or null if the tracker is unable to be created
+sp<AtomMatchingTracker> createAtomMatchingTracker(const AtomMatcher& logMatcher, const int index,
+                                                  const sp<UidMap>& uidMap);
 
 // Helper functions for MetricsManager to initialize from StatsdConfig.
 // *Note*: only initStatsdConfig() should be called from outside.
@@ -48,24 +48,24 @@
 // steps, created to make unit tests easier. And most of the parameters in these
 // functions are temporary objects in the initialization phase.
 
-// Initialize the LogMatchingTrackers.
+// Initialize the AtomMatchingTrackers.
 // input:
 // [key]: the config key that this config belongs to
 // [config]: the input StatsdConfig
 // output:
-// [logTrackerMap]: this map should contain matcher name to index mapping
-// [allAtomMatchers]: should store the sp to all the LogMatchingTracker
+// [atomMatchingTrackerMap]: this map should contain matcher name to index mapping
+// [allAtomMatchingTrackers]: should store the sp to all the AtomMatchingTracker
 // [allTagIds]: contains the set of all interesting tag ids to this config.
-bool initLogTrackers(const StatsdConfig& config, const sp<UidMap>& uidMap,
-                     std::unordered_map<int64_t, int>& logTrackerMap,
-                     std::vector<sp<LogMatchingTracker>>& allAtomMatchers,
-                     std::set<int>& allTagIds);
+bool initAtomMatchingTrackers(const StatsdConfig& config, const sp<UidMap>& uidMap,
+                              std::unordered_map<int64_t, int>& atomMatchingTrackerMap,
+                              std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+                              std::set<int>& allTagIds);
 
 // Initialize ConditionTrackers
 // input:
 // [key]: the config key that this config belongs to
 // [config]: the input config
-// [logTrackerMap]: LogMatchingTracker name to index mapping from previous step.
+// [atomMatchingTrackerMap]: AtomMatchingTracker name to index mapping from previous step.
 // output:
 // [conditionTrackerMap]: this map should contain condition name to index mapping
 // [allConditionTrackers]: stores the sp to all the ConditionTrackers
@@ -73,7 +73,7 @@
 //                        log tracker to condition trackers that use the log tracker
 // [initialConditionCache]: stores the initial conditions for each ConditionTracker
 bool initConditions(const ConfigKey& key, const StatsdConfig& config,
-                    const std::unordered_map<int64_t, int>& logTrackerMap,
+                    const std::unordered_map<int64_t, int>& atomMatchingTrackerMap,
                     std::unordered_map<int64_t, int>& conditionTrackerMap,
                     std::vector<sp<ConditionTracker>>& allConditionTrackers,
                     std::unordered_map<int, std::vector<int>>& trackerToConditionMap,
@@ -96,7 +96,7 @@
 // [key]: the config key that this config belongs to
 // [config]: the input config
 // [timeBaseSec]: start time base for all metrics
-// [logTrackerMap]: LogMatchingTracker name to index mapping from previous step.
+// [atomMatchingTrackerMap]: AtomMatchingTracker name to index mapping from previous step.
 // [conditionTrackerMap]: condition name to index mapping
 // [stateAtomIdMap]: contains the mapping from state ids to atom ids
 // [allStateGroupMaps]: contains the mapping from atom ids and state values to
@@ -109,10 +109,10 @@
 bool initMetrics(
         const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseTimeNs,
         const int64_t currentTimeNs, const sp<StatsPullerManager>& pullerManager,
-        const std::unordered_map<int64_t, int>& logTrackerMap,
+        const std::unordered_map<int64_t, int>& atomMatchingTrackerMap,
         const std::unordered_map<int64_t, int>& conditionTrackerMap,
         const std::unordered_map<int, std::vector<MetricConditionLink>>& eventConditionLinks,
-        const vector<sp<LogMatchingTracker>>& allAtomMatchers,
+        const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
         const unordered_map<int64_t, int>& stateAtomIdMap,
         const unordered_map<int64_t, unordered_map<int, int64_t>>& allStateGroupMaps,
         vector<sp<ConditionTracker>>& allConditionTrackers,
@@ -132,8 +132,8 @@
                       const sp<AlarmMonitor>& anomalyAlarmMonitor,
                       const sp<AlarmMonitor>& periodicAlarmMonitor, const int64_t timeBaseNs,
                       const int64_t currentTimeNs, std::set<int>& allTagIds,
-                      std::vector<sp<LogMatchingTracker>>& allAtomMatchers,
-                      std::unordered_map<int64_t, int>& logTrackerMap,
+                      std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+                      std::unordered_map<int64_t, int>& atomMatchingTrackerMap,
                       std::vector<sp<ConditionTracker>>& allConditionTrackers,
                       std::vector<sp<MetricProducer>>& allMetricProducers,
                       vector<sp<AnomalyTracker>>& allAnomalyTrackers,
diff --git a/cmds/statsd/src/stats_log_util.h b/cmds/statsd/src/stats_log_util.h
index eb65dc6..10e065e 100644
--- a/cmds/statsd/src/stats_log_util.h
+++ b/cmds/statsd/src/stats_log_util.h
@@ -97,7 +97,7 @@
     return message->ParseFromArray(pbBytes.c_str(), pbBytes.size());
 }
 
-// Checks the truncate timestamp annotation as well as the blacklisted range of 300,000 - 304,999.
+// Checks the truncate timestamp annotation as well as the restricted range of 300,000 - 304,999.
 // Returns the truncated timestamp to the nearest 5 minutes if needed.
 int64_t truncateTimestampIfNecessary(const LogEvent& event);
 
diff --git a/cmds/statsd/tests/MetricsManager_test.cpp b/cmds/statsd/tests/MetricsManager_test.cpp
index 8dd6083..2dd774e 100644
--- a/cmds/statsd/tests/MetricsManager_test.cpp
+++ b/cmds/statsd/tests/MetricsManager_test.cpp
@@ -23,7 +23,7 @@
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 #include "metrics/metrics_test_helper.h"
 #include "src/condition/ConditionTracker.h"
-#include "src/matchers/LogMatchingTracker.h"
+#include "src/matchers/AtomMatchingTracker.h"
 #include "src/metrics/CountMetricProducer.h"
 #include "src/metrics/GaugeMetricProducer.h"
 #include "src/metrics/MetricProducer.h"
diff --git a/cmds/statsd/tests/StatsLogProcessor_test.cpp b/cmds/statsd/tests/StatsLogProcessor_test.cpp
index 1e6680c..1409b62 100644
--- a/cmds/statsd/tests/StatsLogProcessor_test.cpp
+++ b/cmds/statsd/tests/StatsLogProcessor_test.cpp
@@ -68,7 +68,7 @@
     sp<StatsPullerManager> pullerManager = new StatsPullerManager();
     sp<AlarmMonitor> anomalyAlarmMonitor;
     sp<AlarmMonitor> periodicAlarmMonitor;
-    // Construct the processor with a dummy sendBroadcast function that does nothing.
+    // Construct the processor with a no-op sendBroadcast function that does nothing.
     StatsLogProcessor p(m, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, 0,
                         [](const ConfigKey& key) { return true; },
                         [](const int&, const vector<int64_t>&) {return true;});
@@ -896,8 +896,8 @@
     EXPECT_TRUE(metricProducer2->isActive());
 
     int i = 0;
-    for (; i < metricsManager1->mAllAtomMatchers.size(); i++) {
-        if (metricsManager1->mAllAtomMatchers[i]->getId() ==
+    for (; i < metricsManager1->mAllAtomMatchingTrackers.size(); i++) {
+        if (metricsManager1->mAllAtomMatchingTrackers[i]->getId() ==
             metric1ActivationTrigger1->atom_matcher_id()) {
             break;
         }
@@ -908,8 +908,8 @@
     EXPECT_EQ(kNotActive, activation1->state);
 
     i = 0;
-    for (; i < metricsManager1->mAllAtomMatchers.size(); i++) {
-        if (metricsManager1->mAllAtomMatchers[i]->getId() ==
+    for (; i < metricsManager1->mAllAtomMatchingTrackers.size(); i++) {
+        if (metricsManager1->mAllAtomMatchingTrackers[i]->getId() ==
             metric1ActivationTrigger2->atom_matcher_id()) {
             break;
         }
@@ -981,8 +981,8 @@
     EXPECT_TRUE(metricProducer1002->isActive());
 
     i = 0;
-    for (; i < metricsManager1001->mAllAtomMatchers.size(); i++) {
-        if (metricsManager1001->mAllAtomMatchers[i]->getId() ==
+    for (; i < metricsManager1001->mAllAtomMatchingTrackers.size(); i++) {
+        if (metricsManager1001->mAllAtomMatchingTrackers[i]->getId() ==
             metric1ActivationTrigger1->atom_matcher_id()) {
             break;
         }
@@ -993,8 +993,8 @@
     EXPECT_EQ(kNotActive, activation1001_1->state);
 
     i = 0;
-    for (; i < metricsManager1001->mAllAtomMatchers.size(); i++) {
-        if (metricsManager1001->mAllAtomMatchers[i]->getId() ==
+    for (; i < metricsManager1001->mAllAtomMatchingTrackers.size(); i++) {
+        if (metricsManager1001->mAllAtomMatchingTrackers[i]->getId() ==
             metric1ActivationTrigger2->atom_matcher_id()) {
             break;
         }
@@ -1082,8 +1082,8 @@
     EXPECT_TRUE(metricProducerTimeBase3_2->isActive());
 
     i = 0;
-    for (; i < metricsManagerTimeBase3->mAllAtomMatchers.size(); i++) {
-        if (metricsManagerTimeBase3->mAllAtomMatchers[i]->getId() ==
+    for (; i < metricsManagerTimeBase3->mAllAtomMatchingTrackers.size(); i++) {
+        if (metricsManagerTimeBase3->mAllAtomMatchingTrackers[i]->getId() ==
             metric1ActivationTrigger1->atom_matcher_id()) {
             break;
         }
@@ -1094,8 +1094,8 @@
     EXPECT_EQ(kNotActive, activationTimeBase3_1->state);
 
     i = 0;
-    for (; i < metricsManagerTimeBase3->mAllAtomMatchers.size(); i++) {
-        if (metricsManagerTimeBase3->mAllAtomMatchers[i]->getId() ==
+    for (; i < metricsManagerTimeBase3->mAllAtomMatchingTrackers.size(); i++) {
+        if (metricsManagerTimeBase3->mAllAtomMatchingTrackers[i]->getId() ==
             metric1ActivationTrigger2->atom_matcher_id()) {
             break;
         }
@@ -1184,8 +1184,8 @@
     EXPECT_TRUE(metricProducerTimeBase4_2->isActive());
 
     i = 0;
-    for (; i < metricsManagerTimeBase4->mAllAtomMatchers.size(); i++) {
-        if (metricsManagerTimeBase4->mAllAtomMatchers[i]->getId() ==
+    for (; i < metricsManagerTimeBase4->mAllAtomMatchingTrackers.size(); i++) {
+        if (metricsManagerTimeBase4->mAllAtomMatchingTrackers[i]->getId() ==
             metric1ActivationTrigger1->atom_matcher_id()) {
             break;
         }
@@ -1196,8 +1196,8 @@
     EXPECT_EQ(kNotActive, activationTimeBase4_1->state);
 
     i = 0;
-    for (; i < metricsManagerTimeBase4->mAllAtomMatchers.size(); i++) {
-        if (metricsManagerTimeBase4->mAllAtomMatchers[i]->getId() ==
+    for (; i < metricsManagerTimeBase4->mAllAtomMatchingTrackers.size(); i++) {
+        if (metricsManagerTimeBase4->mAllAtomMatchingTrackers[i]->getId() ==
             metric1ActivationTrigger2->atom_matcher_id()) {
             break;
         }
@@ -1585,8 +1585,8 @@
     EXPECT_TRUE(metricProducer3->isActive());
 
     // Check event activations.
-    ASSERT_EQ(metricsManager1->mAllAtomMatchers.size(), 4);
-    EXPECT_EQ(metricsManager1->mAllAtomMatchers[0]->getId(),
+    ASSERT_EQ(metricsManager1->mAllAtomMatchingTrackers.size(), 4);
+    EXPECT_EQ(metricsManager1->mAllAtomMatchingTrackers[0]->getId(),
               metric1ActivationTrigger1->atom_matcher_id());
     const auto& activation1 = metricProducer1->mEventActivationMap.at(0);
     EXPECT_EQ(100 * NS_PER_SEC, activation1->ttl_ns);
@@ -1594,7 +1594,7 @@
     EXPECT_EQ(kNotActive, activation1->state);
     EXPECT_EQ(ACTIVATE_ON_BOOT, activation1->activationType);
 
-    EXPECT_EQ(metricsManager1->mAllAtomMatchers[1]->getId(),
+    EXPECT_EQ(metricsManager1->mAllAtomMatchingTrackers[1]->getId(),
               metric1ActivationTrigger2->atom_matcher_id());
     const auto& activation2 = metricProducer1->mEventActivationMap.at(1);
     EXPECT_EQ(200 * NS_PER_SEC, activation2->ttl_ns);
@@ -1602,7 +1602,7 @@
     EXPECT_EQ(kNotActive, activation2->state);
     EXPECT_EQ(ACTIVATE_IMMEDIATELY, activation2->activationType);
 
-    EXPECT_EQ(metricsManager1->mAllAtomMatchers[2]->getId(),
+    EXPECT_EQ(metricsManager1->mAllAtomMatchingTrackers[2]->getId(),
               metric2ActivationTrigger1->atom_matcher_id());
     const auto& activation3 = metricProducer2->mEventActivationMap.at(2);
     EXPECT_EQ(100 * NS_PER_SEC, activation3->ttl_ns);
@@ -1610,7 +1610,7 @@
     EXPECT_EQ(kNotActive, activation3->state);
     EXPECT_EQ(ACTIVATE_ON_BOOT, activation3->activationType);
 
-    EXPECT_EQ(metricsManager1->mAllAtomMatchers[3]->getId(),
+    EXPECT_EQ(metricsManager1->mAllAtomMatchingTrackers[3]->getId(),
               metric2ActivationTrigger2->atom_matcher_id());
     const auto& activation4 = metricProducer2->mEventActivationMap.at(3);
     EXPECT_EQ(200 * NS_PER_SEC, activation4->ttl_ns);
@@ -1685,8 +1685,8 @@
     // Activation 1 is kActiveOnBoot.
     // Activation 2 and 3 are not active.
     // Activation 4 is active.
-    ASSERT_EQ(metricsManager2->mAllAtomMatchers.size(), 4);
-    EXPECT_EQ(metricsManager2->mAllAtomMatchers[0]->getId(),
+    ASSERT_EQ(metricsManager2->mAllAtomMatchingTrackers.size(), 4);
+    EXPECT_EQ(metricsManager2->mAllAtomMatchingTrackers[0]->getId(),
               metric1ActivationTrigger1->atom_matcher_id());
     const auto& activation1001 = metricProducer1001->mEventActivationMap.at(0);
     EXPECT_EQ(100 * NS_PER_SEC, activation1001->ttl_ns);
@@ -1694,7 +1694,7 @@
     EXPECT_EQ(kActiveOnBoot, activation1001->state);
     EXPECT_EQ(ACTIVATE_ON_BOOT, activation1001->activationType);
 
-    EXPECT_EQ(metricsManager2->mAllAtomMatchers[1]->getId(),
+    EXPECT_EQ(metricsManager2->mAllAtomMatchingTrackers[1]->getId(),
               metric1ActivationTrigger2->atom_matcher_id());
     const auto& activation1002 = metricProducer1001->mEventActivationMap.at(1);
     EXPECT_EQ(200 * NS_PER_SEC, activation1002->ttl_ns);
@@ -1702,7 +1702,7 @@
     EXPECT_EQ(kNotActive, activation1002->state);
     EXPECT_EQ(ACTIVATE_IMMEDIATELY, activation1002->activationType);
 
-    EXPECT_EQ(metricsManager2->mAllAtomMatchers[2]->getId(),
+    EXPECT_EQ(metricsManager2->mAllAtomMatchingTrackers[2]->getId(),
               metric2ActivationTrigger1->atom_matcher_id());
     const auto& activation1003 = metricProducer1002->mEventActivationMap.at(2);
     EXPECT_EQ(100 * NS_PER_SEC, activation1003->ttl_ns);
@@ -1710,7 +1710,7 @@
     EXPECT_EQ(kNotActive, activation1003->state);
     EXPECT_EQ(ACTIVATE_ON_BOOT, activation1003->activationType);
 
-    EXPECT_EQ(metricsManager2->mAllAtomMatchers[3]->getId(),
+    EXPECT_EQ(metricsManager2->mAllAtomMatchingTrackers[3]->getId(),
               metric2ActivationTrigger2->atom_matcher_id());
     const auto& activation1004 = metricProducer1002->mEventActivationMap.at(3);
     EXPECT_EQ(200 * NS_PER_SEC, activation1004->ttl_ns);
diff --git a/cmds/statsd/tests/UidMap_test.cpp b/cmds/statsd/tests/UidMap_test.cpp
index 293e8ed..33bdc64 100644
--- a/cmds/statsd/tests/UidMap_test.cpp
+++ b/cmds/statsd/tests/UidMap_test.cpp
@@ -44,7 +44,7 @@
     sp<StatsPullerManager> pullerManager = new StatsPullerManager();
     sp<AlarmMonitor> anomalyAlarmMonitor;
     sp<AlarmMonitor> subscriberAlarmMonitor;
-    // Construct the processor with a dummy sendBroadcast function that does nothing.
+    // Construct the processor with a no-op sendBroadcast function that does nothing.
     StatsLogProcessor p(
             m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
             [](const ConfigKey& key) { return true; },
diff --git a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
index d96ff8a..ba919f1 100644
--- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
@@ -23,7 +23,7 @@
 
 #include "logd/LogEvent.h"
 #include "metrics_test_helper.h"
-#include "src/matchers/SimpleLogMatchingTracker.h"
+#include "src/matchers/SimpleAtomMatchingTracker.h"
 #include "src/metrics/MetricProducer.h"
 #include "src/stats_log_util.h"
 #include "stats_event.h"
diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
index 5524ebc..1000aea 100644
--- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
@@ -22,7 +22,7 @@
 #include <vector>
 
 #include "metrics_test_helper.h"
-#include "src/matchers/SimpleLogMatchingTracker.h"
+#include "src/matchers/SimpleAtomMatchingTracker.h"
 #include "src/metrics/MetricProducer.h"
 #include "src/stats_log_util.h"
 #include "tests/statsd_test_util.h"
diff --git a/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp b/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp
index 6b50fe5..f6d3061 100644
--- a/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp
+++ b/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp
@@ -49,8 +49,8 @@
 sp<AlarmMonitor> anomalyAlarmMonitor;
 sp<AlarmMonitor> periodicAlarmMonitor;
 set<int> allTagIds;
-vector<sp<LogMatchingTracker>> oldAtomMatchers;
-unordered_map<int64_t, int> oldLogTrackerMap;
+vector<sp<AtomMatchingTracker>> oldAtomMatchingTrackers;
+unordered_map<int64_t, int> oldAtomMatchingTrackerMap;
 vector<sp<ConditionTracker>> oldConditionTrackers;
 vector<sp<MetricProducer>> oldMetricProducers;
 std::vector<sp<AnomalyTracker>> oldAnomalyTrackers;
@@ -71,8 +71,8 @@
 
     void SetUp() override {
         allTagIds.clear();
-        oldAtomMatchers.clear();
-        oldLogTrackerMap.clear();
+        oldAtomMatchingTrackers.clear();
+        oldAtomMatchingTrackerMap.clear();
         oldConditionTrackers.clear();
         oldMetricProducers.clear();
         oldAnomalyTrackers.clear();
@@ -89,13 +89,13 @@
 };
 
 bool initConfig(const StatsdConfig& config) {
-    return initStatsdConfig(key, config, uidMap, pullerManager, anomalyAlarmMonitor,
-                            periodicAlarmMonitor, timeBaseNs, timeBaseNs, allTagIds,
-                            oldAtomMatchers, oldLogTrackerMap, oldConditionTrackers,
-                            oldMetricProducers, oldAnomalyTrackers, oldAlarmTrackers,
-                            conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
-                            activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
-                            alertTrackerMap, metricsWithActivation, noReportMetricIds);
+    return initStatsdConfig(
+            key, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
+            timeBaseNs, timeBaseNs, allTagIds, oldAtomMatchingTrackers, oldAtomMatchingTrackerMap,
+            oldConditionTrackers, oldMetricProducers, oldAnomalyTrackers, oldAlarmTrackers,
+            conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
+            activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, alertTrackerMap,
+            metricsWithActivation, noReportMetricIds);
 }
 
 }  // anonymous namespace
@@ -111,10 +111,11 @@
 
     vector<UpdateStatus> matchersToUpdate(1, UPDATE_UNKNOWN);
     vector<bool> cycleTracker(1, false);
-    unordered_map<int64_t, int> newLogTrackerMap;
-    newLogTrackerMap[matcherId] = 0;
-    EXPECT_TRUE(determineMatcherUpdateStatus(config, 0, oldLogTrackerMap, oldAtomMatchers,
-                                             newLogTrackerMap, matchersToUpdate, cycleTracker));
+    unordered_map<int64_t, int> newAtomMatchingTrackerMap;
+    newAtomMatchingTrackerMap[matcherId] = 0;
+    EXPECT_TRUE(determineMatcherUpdateStatus(config, 0, oldAtomMatchingTrackerMap,
+                                             oldAtomMatchingTrackers, newAtomMatchingTrackerMap,
+                                             matchersToUpdate, cycleTracker));
     EXPECT_EQ(matchersToUpdate[0], UPDATE_PRESERVE);
 }
 
@@ -134,10 +135,11 @@
 
     vector<UpdateStatus> matchersToUpdate(1, UPDATE_UNKNOWN);
     vector<bool> cycleTracker(1, false);
-    unordered_map<int64_t, int> newLogTrackerMap;
-    newLogTrackerMap[matcherId] = 0;
-    EXPECT_TRUE(determineMatcherUpdateStatus(newConfig, 0, oldLogTrackerMap, oldAtomMatchers,
-                                             newLogTrackerMap, matchersToUpdate, cycleTracker));
+    unordered_map<int64_t, int> newAtomMatchingTrackerMap;
+    newAtomMatchingTrackerMap[matcherId] = 0;
+    EXPECT_TRUE(determineMatcherUpdateStatus(newConfig, 0, oldAtomMatchingTrackerMap,
+                                             oldAtomMatchingTrackers, newAtomMatchingTrackerMap,
+                                             matchersToUpdate, cycleTracker));
     EXPECT_EQ(matchersToUpdate[0], UPDATE_REPLACE);
 }
 
@@ -163,20 +165,21 @@
     EXPECT_TRUE(initConfig(config));
 
     StatsdConfig newConfig;
-    unordered_map<int64_t, int> newLogTrackerMap;
+    unordered_map<int64_t, int> newAtomMatchingTrackerMap;
     // Same matchers, different order, all should be preserved.
     *newConfig.add_atom_matcher() = matcher2;
-    newLogTrackerMap[matcher2Id] = 0;
+    newAtomMatchingTrackerMap[matcher2Id] = 0;
     *newConfig.add_atom_matcher() = matcher3;
-    newLogTrackerMap[matcher3Id] = 1;
+    newAtomMatchingTrackerMap[matcher3Id] = 1;
     *newConfig.add_atom_matcher() = matcher1;
-    newLogTrackerMap[matcher1Id] = 2;
+    newAtomMatchingTrackerMap[matcher1Id] = 2;
 
     vector<UpdateStatus> matchersToUpdate(3, UPDATE_UNKNOWN);
     vector<bool> cycleTracker(3, false);
     // Only update the combination. It should recurse the two child matchers and preserve all 3.
-    EXPECT_TRUE(determineMatcherUpdateStatus(newConfig, 1, oldLogTrackerMap, oldAtomMatchers,
-                                             newLogTrackerMap, matchersToUpdate, cycleTracker));
+    EXPECT_TRUE(determineMatcherUpdateStatus(newConfig, 1, oldAtomMatchingTrackerMap,
+                                             oldAtomMatchingTrackers, newAtomMatchingTrackerMap,
+                                             matchersToUpdate, cycleTracker));
     EXPECT_EQ(matchersToUpdate[0], UPDATE_PRESERVE);
     EXPECT_EQ(matchersToUpdate[1], UPDATE_PRESERVE);
     EXPECT_EQ(matchersToUpdate[2], UPDATE_PRESERVE);
@@ -207,19 +210,20 @@
     matcher3.mutable_combination()->set_operation(LogicalOperation::AND);
 
     StatsdConfig newConfig;
-    unordered_map<int64_t, int> newLogTrackerMap;
+    unordered_map<int64_t, int> newAtomMatchingTrackerMap;
     *newConfig.add_atom_matcher() = matcher2;
-    newLogTrackerMap[matcher2Id] = 0;
+    newAtomMatchingTrackerMap[matcher2Id] = 0;
     *newConfig.add_atom_matcher() = matcher3;
-    newLogTrackerMap[matcher3Id] = 1;
+    newAtomMatchingTrackerMap[matcher3Id] = 1;
     *newConfig.add_atom_matcher() = matcher1;
-    newLogTrackerMap[matcher1Id] = 2;
+    newAtomMatchingTrackerMap[matcher1Id] = 2;
 
     vector<UpdateStatus> matchersToUpdate(3, UPDATE_UNKNOWN);
     vector<bool> cycleTracker(3, false);
     // Only update the combination. The simple matchers should not be evaluated.
-    EXPECT_TRUE(determineMatcherUpdateStatus(newConfig, 1, oldLogTrackerMap, oldAtomMatchers,
-                                             newLogTrackerMap, matchersToUpdate, cycleTracker));
+    EXPECT_TRUE(determineMatcherUpdateStatus(newConfig, 1, oldAtomMatchingTrackerMap,
+                                             oldAtomMatchingTrackers, newAtomMatchingTrackerMap,
+                                             matchersToUpdate, cycleTracker));
     EXPECT_EQ(matchersToUpdate[0], UPDATE_UNKNOWN);
     EXPECT_EQ(matchersToUpdate[1], UPDATE_REPLACE);
     EXPECT_EQ(matchersToUpdate[2], UPDATE_UNKNOWN);
@@ -250,19 +254,20 @@
     matcher2.mutable_simple_atom_matcher()->set_atom_id(12);
 
     StatsdConfig newConfig;
-    unordered_map<int64_t, int> newLogTrackerMap;
+    unordered_map<int64_t, int> newAtomMatchingTrackerMap;
     *newConfig.add_atom_matcher() = matcher2;
-    newLogTrackerMap[matcher2Id] = 0;
+    newAtomMatchingTrackerMap[matcher2Id] = 0;
     *newConfig.add_atom_matcher() = matcher3;
-    newLogTrackerMap[matcher3Id] = 1;
+    newAtomMatchingTrackerMap[matcher3Id] = 1;
     *newConfig.add_atom_matcher() = matcher1;
-    newLogTrackerMap[matcher1Id] = 2;
+    newAtomMatchingTrackerMap[matcher1Id] = 2;
 
     vector<UpdateStatus> matchersToUpdate(3, UPDATE_UNKNOWN);
     vector<bool> cycleTracker(3, false);
     // Only update the combination.
-    EXPECT_TRUE(determineMatcherUpdateStatus(newConfig, 1, oldLogTrackerMap, oldAtomMatchers,
-                                             newLogTrackerMap, matchersToUpdate, cycleTracker));
+    EXPECT_TRUE(determineMatcherUpdateStatus(newConfig, 1, oldAtomMatchingTrackerMap,
+                                             oldAtomMatchingTrackers, newAtomMatchingTrackerMap,
+                                             matchersToUpdate, cycleTracker));
     // Matcher 2 and matcher3 must be reevaluated. Matcher 1 might, but does not need to be.
     EXPECT_EQ(matchersToUpdate[0], UPDATE_REPLACE);
     EXPECT_EQ(matchersToUpdate[1], UPDATE_REPLACE);
@@ -330,47 +335,48 @@
     *newConfig.add_atom_matcher() = combination1;
 
     set<int> newTagIds;
-    unordered_map<int64_t, int> newLogTrackerMap;
-    vector<sp<LogMatchingTracker>> newAtomMatchers;
-    EXPECT_TRUE(updateLogTrackers(newConfig, uidMap, oldLogTrackerMap, oldAtomMatchers, newTagIds,
-                                  newLogTrackerMap, newAtomMatchers));
+    unordered_map<int64_t, int> newAtomMatchingTrackerMap;
+    vector<sp<AtomMatchingTracker>> newAtomMatchingTrackers;
+    EXPECT_TRUE(updateAtomTrackers(newConfig, uidMap, oldAtomMatchingTrackerMap,
+                                   oldAtomMatchingTrackers, newTagIds, newAtomMatchingTrackerMap,
+                                   newAtomMatchingTrackers));
 
     ASSERT_EQ(newTagIds.size(), 3);
     EXPECT_EQ(newTagIds.count(10), 1);
     EXPECT_EQ(newTagIds.count(111), 1);
     EXPECT_EQ(newTagIds.count(13), 1);
 
-    ASSERT_EQ(newLogTrackerMap.size(), 6);
-    EXPECT_EQ(newLogTrackerMap.at(combination3Id), 0);
-    EXPECT_EQ(newLogTrackerMap.at(simple2Id), 1);
-    EXPECT_EQ(newLogTrackerMap.at(combination2Id), 2);
-    EXPECT_EQ(newLogTrackerMap.at(simple1Id), 3);
-    EXPECT_EQ(newLogTrackerMap.at(simple4Id), 4);
-    EXPECT_EQ(newLogTrackerMap.at(combination1Id), 5);
+    ASSERT_EQ(newAtomMatchingTrackerMap.size(), 6);
+    EXPECT_EQ(newAtomMatchingTrackerMap.at(combination3Id), 0);
+    EXPECT_EQ(newAtomMatchingTrackerMap.at(simple2Id), 1);
+    EXPECT_EQ(newAtomMatchingTrackerMap.at(combination2Id), 2);
+    EXPECT_EQ(newAtomMatchingTrackerMap.at(simple1Id), 3);
+    EXPECT_EQ(newAtomMatchingTrackerMap.at(simple4Id), 4);
+    EXPECT_EQ(newAtomMatchingTrackerMap.at(combination1Id), 5);
 
-    ASSERT_EQ(newAtomMatchers.size(), 6);
+    ASSERT_EQ(newAtomMatchingTrackers.size(), 6);
     // Make sure all atom matchers are initialized:
-    for (const sp<LogMatchingTracker>& tracker : newAtomMatchers) {
+    for (const sp<AtomMatchingTracker>& tracker : newAtomMatchingTrackers) {
         EXPECT_TRUE(tracker->mInitialized);
     }
     // Make sure preserved atom matchers are the same.
-    EXPECT_EQ(oldAtomMatchers[oldLogTrackerMap.at(simple1Id)],
-              newAtomMatchers[newLogTrackerMap.at(simple1Id)]);
-    EXPECT_EQ(oldAtomMatchers[oldLogTrackerMap.at(combination1Id)],
-              newAtomMatchers[newLogTrackerMap.at(combination1Id)]);
+    EXPECT_EQ(oldAtomMatchingTrackers[oldAtomMatchingTrackerMap.at(simple1Id)],
+              newAtomMatchingTrackers[newAtomMatchingTrackerMap.at(simple1Id)]);
+    EXPECT_EQ(oldAtomMatchingTrackers[oldAtomMatchingTrackerMap.at(combination1Id)],
+              newAtomMatchingTrackers[newAtomMatchingTrackerMap.at(combination1Id)]);
     // Make sure replaced matchers are different.
-    EXPECT_NE(oldAtomMatchers[oldLogTrackerMap.at(simple2Id)],
-              newAtomMatchers[newLogTrackerMap.at(simple2Id)]);
-    EXPECT_NE(oldAtomMatchers[oldLogTrackerMap.at(combination2Id)],
-              newAtomMatchers[newLogTrackerMap.at(combination2Id)]);
+    EXPECT_NE(oldAtomMatchingTrackers[oldAtomMatchingTrackerMap.at(simple2Id)],
+              newAtomMatchingTrackers[newAtomMatchingTrackerMap.at(simple2Id)]);
+    EXPECT_NE(oldAtomMatchingTrackers[oldAtomMatchingTrackerMap.at(combination2Id)],
+              newAtomMatchingTrackers[newAtomMatchingTrackerMap.at(combination2Id)]);
 
     // Validation, make sure the matchers have the proper ids. Could do more checks here.
-    EXPECT_EQ(newAtomMatchers[0]->getId(), combination3Id);
-    EXPECT_EQ(newAtomMatchers[1]->getId(), simple2Id);
-    EXPECT_EQ(newAtomMatchers[2]->getId(), combination2Id);
-    EXPECT_EQ(newAtomMatchers[3]->getId(), simple1Id);
-    EXPECT_EQ(newAtomMatchers[4]->getId(), simple4Id);
-    EXPECT_EQ(newAtomMatchers[5]->getId(), combination1Id);
+    EXPECT_EQ(newAtomMatchingTrackers[0]->getId(), combination3Id);
+    EXPECT_EQ(newAtomMatchingTrackers[1]->getId(), simple2Id);
+    EXPECT_EQ(newAtomMatchingTrackers[2]->getId(), combination2Id);
+    EXPECT_EQ(newAtomMatchingTrackers[3]->getId(), simple1Id);
+    EXPECT_EQ(newAtomMatchingTrackers[4]->getId(), simple4Id);
+    EXPECT_EQ(newAtomMatchingTrackers[5]->getId(), combination1Id);
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/tests/metrics/parsing_utils/metrics_manager_util_test.cpp b/cmds/statsd/tests/metrics/parsing_utils/metrics_manager_util_test.cpp
index 4e97eaf..d6db4c1 100644
--- a/cmds/statsd/tests/metrics/parsing_utils/metrics_manager_util_test.cpp
+++ b/cmds/statsd/tests/metrics/parsing_utils/metrics_manager_util_test.cpp
@@ -24,7 +24,7 @@
 
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 #include "src/condition/ConditionTracker.h"
-#include "src/matchers/LogMatchingTracker.h"
+#include "src/matchers/AtomMatchingTracker.h"
 #include "src/metrics/CountMetricProducer.h"
 #include "src/metrics/GaugeMetricProducer.h"
 #include "src/metrics/MetricProducer.h"
@@ -383,7 +383,7 @@
     sp<AlarmMonitor> periodicAlarmMonitor;
     StatsdConfig config = buildConfigWithDifferentPredicates();
     set<int> allTagIds;
-    vector<sp<LogMatchingTracker>> allAtomMatchers;
+    vector<sp<AtomMatchingTracker>> allAtomMatchingTrackers;
     unordered_map<int64_t, int> logTrackerMap;
     vector<sp<ConditionTracker>> allConditionTrackers;
     vector<sp<MetricProducer>> allMetricProducers;
@@ -400,7 +400,7 @@
 
     EXPECT_TRUE(initStatsdConfig(
             kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
-            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchers, logTrackerMap,
+            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchingTrackers, logTrackerMap,
             allConditionTrackers, allMetricProducers, allAnomalyTrackers, allAlarmTrackers,
             conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
             activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, alertTrackerMap,
@@ -432,7 +432,7 @@
     sp<AlarmMonitor> periodicAlarmMonitor;
     StatsdConfig config = buildGoodConfig();
     set<int> allTagIds;
-    vector<sp<LogMatchingTracker>> allAtomMatchers;
+    vector<sp<AtomMatchingTracker>> allAtomMatchingTrackers;
     unordered_map<int64_t, int> logTrackerMap;
     vector<sp<ConditionTracker>> allConditionTrackers;
     vector<sp<MetricProducer>> allMetricProducers;
@@ -449,7 +449,7 @@
 
     EXPECT_TRUE(initStatsdConfig(
             kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
-            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchers, logTrackerMap,
+            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchingTrackers, logTrackerMap,
             allConditionTrackers, allMetricProducers, allAnomalyTrackers, allAlarmTrackers,
             conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
             activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, alertTrackerMap,
@@ -469,7 +469,7 @@
     sp<AlarmMonitor> periodicAlarmMonitor;
     StatsdConfig config = buildDimensionMetricsWithMultiTags();
     set<int> allTagIds;
-    vector<sp<LogMatchingTracker>> allAtomMatchers;
+    vector<sp<AtomMatchingTracker>> allAtomMatchingTrackers;
     unordered_map<int64_t, int> logTrackerMap;
     vector<sp<ConditionTracker>> allConditionTrackers;
     vector<sp<MetricProducer>> allMetricProducers;
@@ -486,7 +486,7 @@
 
     EXPECT_FALSE(initStatsdConfig(
             kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
-            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchers, logTrackerMap,
+            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchingTrackers, logTrackerMap,
             allConditionTrackers, allMetricProducers, allAnomalyTrackers, allAlarmTrackers,
             conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
             activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, alertTrackerMap,
@@ -500,7 +500,7 @@
     sp<AlarmMonitor> periodicAlarmMonitor;
     StatsdConfig config = buildCircleMatchers();
     set<int> allTagIds;
-    vector<sp<LogMatchingTracker>> allAtomMatchers;
+    vector<sp<AtomMatchingTracker>> allAtomMatchingTrackers;
     unordered_map<int64_t, int> logTrackerMap;
     vector<sp<ConditionTracker>> allConditionTrackers;
     vector<sp<MetricProducer>> allMetricProducers;
@@ -517,7 +517,7 @@
 
     EXPECT_FALSE(initStatsdConfig(
             kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
-            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchers, logTrackerMap,
+            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchingTrackers, logTrackerMap,
             allConditionTrackers, allMetricProducers, allAnomalyTrackers, allAlarmTrackers,
             conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
             activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, alertTrackerMap,
@@ -531,7 +531,7 @@
     sp<AlarmMonitor> periodicAlarmMonitor;
     StatsdConfig config = buildMissingMatchers();
     set<int> allTagIds;
-    vector<sp<LogMatchingTracker>> allAtomMatchers;
+    vector<sp<AtomMatchingTracker>> allAtomMatchingTrackers;
     unordered_map<int64_t, int> logTrackerMap;
     vector<sp<ConditionTracker>> allConditionTrackers;
     vector<sp<MetricProducer>> allMetricProducers;
@@ -547,7 +547,7 @@
     std::set<int64_t> noReportMetricIds;
     EXPECT_FALSE(initStatsdConfig(
             kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
-            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchers, logTrackerMap,
+            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchingTrackers, logTrackerMap,
             allConditionTrackers, allMetricProducers, allAnomalyTrackers, allAlarmTrackers,
             conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
             activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, alertTrackerMap,
@@ -561,7 +561,7 @@
     sp<AlarmMonitor> periodicAlarmMonitor;
     StatsdConfig config = buildMissingPredicate();
     set<int> allTagIds;
-    vector<sp<LogMatchingTracker>> allAtomMatchers;
+    vector<sp<AtomMatchingTracker>> allAtomMatchingTrackers;
     unordered_map<int64_t, int> logTrackerMap;
     vector<sp<ConditionTracker>> allConditionTrackers;
     vector<sp<MetricProducer>> allMetricProducers;
@@ -577,7 +577,7 @@
     std::set<int64_t> noReportMetricIds;
     EXPECT_FALSE(initStatsdConfig(
             kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
-            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchers, logTrackerMap,
+            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchingTrackers, logTrackerMap,
             allConditionTrackers, allMetricProducers, allAnomalyTrackers, allAlarmTrackers,
             conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
             activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, alertTrackerMap,
@@ -591,7 +591,7 @@
     sp<AlarmMonitor> periodicAlarmMonitor;
     StatsdConfig config = buildCirclePredicates();
     set<int> allTagIds;
-    vector<sp<LogMatchingTracker>> allAtomMatchers;
+    vector<sp<AtomMatchingTracker>> allAtomMatchingTrackers;
     unordered_map<int64_t, int> logTrackerMap;
     vector<sp<ConditionTracker>> allConditionTrackers;
     vector<sp<MetricProducer>> allMetricProducers;
@@ -608,7 +608,7 @@
 
     EXPECT_FALSE(initStatsdConfig(
             kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
-            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchers, logTrackerMap,
+            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchingTrackers, logTrackerMap,
             allConditionTrackers, allMetricProducers, allAnomalyTrackers, allAlarmTrackers,
             conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
             activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, alertTrackerMap,
@@ -622,7 +622,7 @@
     sp<AlarmMonitor> periodicAlarmMonitor;
     StatsdConfig config = buildAlertWithUnknownMetric();
     set<int> allTagIds;
-    vector<sp<LogMatchingTracker>> allAtomMatchers;
+    vector<sp<AtomMatchingTracker>> allAtomMatchingTrackers;
     unordered_map<int64_t, int> logTrackerMap;
     vector<sp<ConditionTracker>> allConditionTrackers;
     vector<sp<MetricProducer>> allMetricProducers;
@@ -639,21 +639,21 @@
 
     EXPECT_FALSE(initStatsdConfig(
             kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
-            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchers, logTrackerMap,
+            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchingTrackers, logTrackerMap,
             allConditionTrackers, allMetricProducers, allAnomalyTrackers, allAlarmTrackers,
             conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
             activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, alertTrackerMap,
             metricsWithActivation, noReportMetricIds));
 }
 
-TEST(MetricsManagerTest, TestCreateLogTrackerInvalidMatcher) {
+TEST(MetricsManagerTest, TestCreateAtomMatchingTrackerInvalidMatcher) {
     sp<UidMap> uidMap = new UidMap();
     AtomMatcher matcher;
     matcher.set_id(21);
-    EXPECT_EQ(createLogTracker(matcher, 0, uidMap), nullptr);
+    EXPECT_EQ(createAtomMatchingTracker(matcher, 0, uidMap), nullptr);
 }
 
-TEST(MetricsManagerTest, TestCreateLogTrackerSimple) {
+TEST(MetricsManagerTest, TestCreateAtomMatchingTrackerSimple) {
     int index = 1;
     int64_t id = 123;
     sp<UidMap> uidMap = new UidMap();
@@ -666,7 +666,7 @@
     simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int(
             android::view::DisplayStateEnum::DISPLAY_STATE_ON);
 
-    sp<LogMatchingTracker> tracker = createLogTracker(matcher, index, uidMap);
+    sp<AtomMatchingTracker> tracker = createAtomMatchingTracker(matcher, index, uidMap);
     EXPECT_NE(tracker, nullptr);
 
     EXPECT_TRUE(tracker->mInitialized);
@@ -677,7 +677,7 @@
     EXPECT_EQ(atomIds.count(util::SCREEN_STATE_CHANGED), 1);
 }
 
-TEST(MetricsManagerTest, TestCreateLogTrackerCombination) {
+TEST(MetricsManagerTest, TestCreateAtomMatchingTrackerCombination) {
     int index = 1;
     int64_t id = 123;
     sp<UidMap> uidMap = new UidMap();
@@ -688,7 +688,7 @@
     combination->add_matcher(123);
     combination->add_matcher(223);
 
-    sp<LogMatchingTracker> tracker = createLogTracker(matcher, index, uidMap);
+    sp<AtomMatchingTracker> tracker = createAtomMatchingTracker(matcher, index, uidMap);
     EXPECT_NE(tracker, nullptr);
 
     // Combination matchers need to be initialized first.
diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp
index 0be983f..1761d5d 100644
--- a/cmds/statsd/tests/statsd_test_util.cpp
+++ b/cmds/statsd/tests/statsd_test_util.cpp
@@ -16,7 +16,7 @@
 
 #include <aidl/android/util/StatsEventParcel.h>
 
-#include "matchers/SimpleLogMatchingTracker.h"
+#include "matchers/SimpleAtomMatchingTracker.h"
 #include "stats_event.h"
 
 using aidl::android::util::StatsEventParcel;
@@ -1008,8 +1008,8 @@
     }
     uint64_t matcherHash = 0x12345678;
     int64_t matcherId = 678;
-    return new EventMatcherWizard({new SimpleLogMatchingTracker(matcherId, matcherIndex,
-                                                                matcherHash, atomMatcher, uidMap)});
+    return new EventMatcherWizard({new SimpleAtomMatchingTracker(
+            matcherId, matcherIndex, matcherHash, atomMatcher, uidMap)});
 }
 
 void ValidateWakelockAttributionUidAndTagDimension(const DimensionsValue& value, const int atomId,
diff --git a/config/boot-image-profile.txt b/config/boot-image-profile.txt
index ab2f42b..f43bd2b 100644
--- a/config/boot-image-profile.txt
+++ b/config/boot-image-profile.txt
@@ -33629,6 +33629,35 @@
 HSPLjava/math/BigInteger;->abs()Ljava/math/BigInteger;
 HSPLjava/math/BigInteger;->add(Ljava/math/BigInteger;)Ljava/math/BigInteger;
 HSPLjava/math/BigInteger;->bitLength()I
+#Temporary manual additions to avoid slowing tests down too much
+#Carefully positioned for clean merge
+HSPLjava/math/BigInteger;->add([IJ)[I
+HSPLjava/math/BigInteger;->add([I[I)[I
+HSPLjava/math/BigInteger;->subtract([IJ)[I
+HSPLjava/math/BigInteger;->subtract([I[I)[I
+HSPLjava/math/BigInteger;->jacobiSymbol(ILjava/math/BigInteger;)I
+HSPLjava/math/BigInteger;->lucasLehmerSequence(ILjava/math/BigInteger;Ljava/math/BigInteger;)Ljava/math/BigInteger;
+HSPLjava/math/BigInteger;->multiplyToLen([II[II[I)[I
+HSPLjava/math/MutableBigInteger;->add(Ljava/math/MutableBigInteger;)V
+HSPLjava/math/MutableBigInteger;->addShifted(Ljava/math/MutableBigInteger;)V
+HSPLjava/math/MutableBigInteger;->subtract(Ljava/math/MutableBigInteger;)V
+HSPLjava/math/MutableBigInteger;->difference(Ljava/math/MutableBigInteger;)V
+HSPLjava/math/MutableBigInteger;->divideKnuth(Ljava/math/MutableBigInteger;Ljava/math/MutableBigInteger;Z)Ljava/math/MutableBigInteger;
+HSPLjava/math/MutableBigInteger;->divideMagnitude(Ljava/math/MutableBigInteger;Ljava/math/MutableBigInteger;Z)Ljava/math/MutableBigInteger;
+HSPLjava/math/MutableBigInteger;->divideLongMagnitude(JLjava/math/MutableBigInteger;)Ljava/math/MutableBigInteger;
+HSPLjava/math/MutableBigInteger;->divideOneWord(ILjava/math/MutableBigInteger;)I
+HSPLjava/math/MutableBigInteger;->divadd([I[II)I
+HSPLjava/math/MutableBigInteger;->mulsub([I[IIII)I
+HSPLjava/math/MutableBigInteger;->mulsubBorrow([I[IIII)I
+HSPLjava/math/MutableBigInteger;->copyAndShift([III[III)V
+HSPLjava/math/MutableBigInteger;->multiply(Ljava/math/MutableBigInteger;Ljava/math/MutableBigInteger;)V
+HSPLjava/math/MutableBigInteger;->mul(ILjava/math/MutableBigInteger;)V
+HSPLjava/math/MutableBigInteger;->multiply(ILjava/math/MutableBigInteger;)V
+HSPLjava/math/MutableBigInteger;->primitiveRightShift(I)I
+HSPLjava/math/MutableBigInteger;->primitiveLeftShift(I)I
+HSPLjava/math/MutableBigInteger;->binaryGCD(Ljava/math/MutableBigInteger;)Ljava/math/MutableBigInteger;
+HSPLjava/math/MutableBigInteger;->binaryGCD(II)I
+#End of maual additions
 HSPLjava/math/BigInteger;->compareTo(Ljava/math/BigInteger;)I
 HSPLjava/math/BigInteger;->divide(Ljava/math/BigInteger;)Ljava/math/BigInteger;
 HSPLjava/math/BigInteger;->divideAndRemainder(Ljava/math/BigInteger;)[Ljava/math/BigInteger;
diff --git a/config/preloaded-classes b/config/preloaded-classes
index e43c7d4..f56656b 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -3709,9 +3709,9 @@
 android.media.IRingtonePlayer$Stub$Proxy
 android.media.IRingtonePlayer$Stub
 android.media.IRingtonePlayer
-android.media.IStrategyPreferredDeviceDispatcher$Stub$Proxy
-android.media.IStrategyPreferredDeviceDispatcher$Stub
-android.media.IStrategyPreferredDeviceDispatcher
+android.media.IStrategyPreferredDevicesDispatcher$Stub$Proxy
+android.media.IStrategyPreferredDevicesDispatcher$Stub
+android.media.IStrategyPreferredDevicesDispatcher
 android.media.IVolumeController$Stub$Proxy
 android.media.IVolumeController$Stub
 android.media.IVolumeController
diff --git a/core/java/android/app/ApplicationLoaders.java b/core/java/android/app/ApplicationLoaders.java
index 15237be..08cd0b3 100644
--- a/core/java/android/app/ApplicationLoaders.java
+++ b/core/java/android/app/ApplicationLoaders.java
@@ -243,8 +243,8 @@
 
         // cached must be built and loaded in the same environment
         if (!sharedLibrariesEquals(sharedLibraries, cached.sharedLibraries)) {
-            Log.w(TAG, "Unexpected environment for cached library: (" + sharedLibraries + "|"
-                    + cached.sharedLibraries + ")");
+            Log.w(TAG, "Unexpected environment loading cached library " + zip + " (real|cached): ("
+                    + sharedLibraries + "|" + cached.sharedLibraries + ")");
             return null;
         }
 
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index dedd870..676c6c0 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -684,8 +684,7 @@
 
     @Override
     public int checkPermission(String permName, String pkgName) {
-        return PermissionManager
-                .checkPackageNamePermission(permName, pkgName, getUserId());
+        return PermissionManager.checkPackageNamePermission(permName, pkgName, getUserId());
     }
 
     @Override
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 93dfc79..f37ca61b 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -461,7 +461,7 @@
     @UnsupportedAppUsage
     Rect getTaskBounds(int taskId);
     @UnsupportedAppUsage
-    boolean setProcessMemoryTrimLevel(in String process, int uid, int level);
+    boolean setProcessMemoryTrimLevel(in String process, int userId, int level);
 
 
     // Start of L transactions
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 6f3e892..6737972 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -5187,19 +5187,11 @@
             bindHeaderChronometerAndTime(contentView, p);
             bindProfileBadge(contentView, p);
             bindAlertedIcon(contentView, p);
-            bindActivePermissions(contentView, p);
             bindFeedbackIcon(contentView, p);
             bindExpandButton(contentView, p);
             mN.mUsesStandardHeader = true;
         }
 
-        private void bindActivePermissions(RemoteViews contentView, StandardTemplateParams p) {
-            int color = getNeutralColor(p);
-            contentView.setDrawableTint(R.id.camera, false, color, PorterDuff.Mode.SRC_ATOP);
-            contentView.setDrawableTint(R.id.mic, false, color, PorterDuff.Mode.SRC_ATOP);
-            contentView.setDrawableTint(R.id.overlay, false, color, PorterDuff.Mode.SRC_ATOP);
-        }
-
         private void bindFeedbackIcon(RemoteViews contentView, StandardTemplateParams p) {
             int color = getNeutralColor(p);
             contentView.setDrawableTint(R.id.feedback, false, color, PorterDuff.Mode.SRC_ATOP);
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 8ee995d..0627bc8 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -282,6 +282,16 @@
             = "android.app.action.INTERRUPTION_FILTER_CHANGED";
 
     /**
+     * Intent that is broadcast when the state of
+     * {@link #hasEnabledNotificationListener(String, UserHandle)} changes.
+     * @hide
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static final String ACTION_NOTIFICATION_LISTENER_ENABLED_CHANGED =
+            "android.app.action.NOTIFICATION_LISTENER_ENABLED_CHANGED";
+
+    /**
      * Intent that is broadcast when the state of getCurrentInterruptionFilter() changes.
      * @hide
      */
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index 54f3f10..6c6c04e 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -413,7 +413,7 @@
     public final void disableLocal() {
         synchronized (mLock) {
             mDisabled = true;
-            mCache.clear();
+            clear();
         }
     }
 
@@ -463,7 +463,7 @@
                             cacheName(), mCache.size(),
                             mLastSeenNonce, currentNonce));
                     }
-                    mCache.clear();
+                    clear();
                     mLastSeenNonce = currentNonce;
                     cachedResult = null;
                 }
@@ -728,9 +728,13 @@
      * It's better to use explicit cork and uncork pairs that tighly surround big batches of
      * invalidations, but it's not always practical to tell where these invalidation batches
      * might occur. AutoCorker's time-based corking is a decent alternative.
+     *
+     * The auto-cork delay is configurable but it should not be too long.  The purpose of
+     * the delay is to minimize the number of times a server writes to the system property
+     * when invalidating the cache.  One write every 50ms does not hurt system performance.
      */
     public static final class AutoCorker {
-        public static final int DEFAULT_AUTO_CORK_DELAY_MS = 2000;
+        public static final int DEFAULT_AUTO_CORK_DELAY_MS = 50;
 
         private final String mPropertyName;
         private final int mAutoCorkDelayMs;
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index fe509de..4139b2f 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -65,6 +65,33 @@
         {
             "name": "CtsInstantAppTests",
             "file_patterns": ["(/|^)InstantAppResolve[^/]*"]
+        },
+        {
+            "name": "CtsAutoFillServiceTestCases",
+            "options": [
+                {
+                    "include-filter": "android.autofillservice.cts.PreSimpleSaveActivityTest"
+                },
+                {
+                    "exclude-annotation": "androidx.test.filters.FlakyTest"
+                }
+            ],
+            "file_patterns": ["(/|^)Activity.java"]
+        },
+        {
+            "name": "CtsAutoFillServiceTestCases",
+            "options": [
+                {
+                    "include-filter": "android.autofillservice.cts.SimpleSaveActivityTest"
+                },
+                {
+                    "exclude-annotation": "androidx.test.filters.FlakyTest"
+                },
+                {
+                    "exclude-annotation": "android.platform.test.annotations.AppModeFull"
+                }
+            ],
+            "file_patterns": ["(/|^)Activity.java"]
         }
     ],
     "postsubmit": [
diff --git a/core/java/android/app/slice/SliceProvider.java b/core/java/android/app/slice/SliceProvider.java
index bd1eea5..cef6ab0 100644
--- a/core/java/android/app/slice/SliceProvider.java
+++ b/core/java/android/app/slice/SliceProvider.java
@@ -155,10 +155,6 @@
     /**
      * @hide
      */
-    public static final String EXTRA_PROVIDER_PKG = "provider_pkg";
-    /**
-     * @hide
-     */
     public static final String EXTRA_RESULT = "result";
 
     private static final boolean DEBUG = false;
@@ -519,7 +515,6 @@
                 com.android.internal.R.string.config_slicePermissionComponent)));
         intent.putExtra(EXTRA_BIND_URI, sliceUri);
         intent.putExtra(EXTRA_PKG, callingPackage);
-        intent.putExtra(EXTRA_PROVIDER_PKG, context.getPackageName());
         // Unique pending intent.
         intent.setData(sliceUri.buildUpon().appendQueryParameter("package", callingPackage)
                 .build());
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index ee9bd3d..7fe29a9 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -277,6 +277,14 @@
     public static final String SCHEME_HTTPS = "https";
 
     /**
+     * Package scheme
+     *
+     * @see #addDataScheme(String)
+     * @hide
+     */
+    public static final String SCHEME_PACKAGE = "package";
+
+    /**
      * The value to indicate a wildcard for incoming match arguments.
      * @hide
      */
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 043953d..0c6810c 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -1134,6 +1134,7 @@
      * @hide
      */
     @SystemApi
+    @TestApi
     public int targetSandboxVersion;
 
     /**
diff --git a/core/java/android/content/pm/InstantAppRequest.java b/core/java/android/content/pm/InstantAppRequest.java
index 84f5021..5b5d109f 100644
--- a/core/java/android/content/pm/InstantAppRequest.java
+++ b/core/java/android/content/pm/InstantAppRequest.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.content.Intent;
 import android.os.Bundle;
 
@@ -40,7 +41,7 @@
     /** Whether or not the requesting package was an instant app */
     public final boolean isRequesterInstantApp;
     /** ID of the user requesting the instant application */
-    public final int userId;
+    public final @UserIdInt int userId;
     /**
      * Optional extra bundle provided by the source application to the installer for additional
      * verification.
@@ -60,7 +61,7 @@
 
     public InstantAppRequest(AuxiliaryResolveInfo responseObj, Intent origIntent,
             String resolvedType, String callingPackage, @Nullable String callingFeatureId,
-            boolean isRequesterInstantApp, int userId, Bundle verificationBundle,
+            boolean isRequesterInstantApp, @UserIdInt int userId, Bundle verificationBundle,
             boolean resolveForStart, @Nullable int[] hostDigestPrefixSecure,
             @NonNull String token) {
         this.responseObj = responseObj;
diff --git a/core/java/android/content/pm/IntentFilterVerificationInfo.java b/core/java/android/content/pm/IntentFilterVerificationInfo.java
index 67bda2c..0a913ac 100644
--- a/core/java/android/content/pm/IntentFilterVerificationInfo.java
+++ b/core/java/android/content/pm/IntentFilterVerificationInfo.java
@@ -56,19 +56,19 @@
 
     private ArraySet<String> mDomains = new ArraySet<>();
     private String mPackageName;
-    private int mMainStatus;
+    private int mStatus;
 
     /** @hide */
     public IntentFilterVerificationInfo() {
         mPackageName = null;
-        mMainStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
+        mStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
     }
 
     /** @hide */
     public IntentFilterVerificationInfo(String packageName, ArraySet<String> domains) {
         mPackageName = packageName;
         mDomains = domains;
-        mMainStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
+        mStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
     }
 
     /** @hide */
@@ -87,14 +87,14 @@
     }
 
     public int getStatus() {
-        return mMainStatus;
+        return mStatus;
     }
 
     /** @hide */
     public void setStatus(int s) {
         if (s >= INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED &&
                 s <= INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) {
-            mMainStatus = s;
+            mStatus = s;
         } else {
             Log.w(TAG, "Trying to set a non supported status: " + s);
         }
@@ -156,7 +156,7 @@
         if (status == -1) {
             Log.e(TAG, "Unknown status value: " + status);
         }
-        mMainStatus = status;
+        mStatus = status;
 
         int outerDepth = parser.getDepth();
         int type;
@@ -184,7 +184,7 @@
     /** @hide */
     public void writeToXml(XmlSerializer serializer) throws IOException {
         serializer.attribute(null, ATTR_PACKAGE_NAME, mPackageName);
-        serializer.attribute(null, ATTR_STATUS, String.valueOf(mMainStatus));
+        serializer.attribute(null, ATTR_STATUS, String.valueOf(mStatus));
         for (String str : mDomains) {
             serializer.startTag(null, TAG_DOMAIN);
             serializer.attribute(null, ATTR_DOMAIN_NAME, str);
@@ -194,7 +194,7 @@
 
     /** @hide */
     public String getStatusString() {
-        return getStatusStringFromValue(((long)mMainStatus) << 32);
+        return getStatusStringFromValue(((long) mStatus) << 32);
     }
 
     /** @hide */
@@ -233,7 +233,7 @@
 
     private void readFromParcel(Parcel source) {
         mPackageName = source.readString();
-        mMainStatus = source.readInt();
+        mStatus = source.readInt();
         ArrayList<String> list = new ArrayList<>();
         source.readStringList(list);
         mDomains.addAll(list);
@@ -242,7 +242,7 @@
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeString(mPackageName);
-        dest.writeInt(mMainStatus);
+        dest.writeInt(mStatus);
         dest.writeStringList(new ArrayList<>(mDomains));
     }
 
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index e08af55..7b2955d 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -3373,6 +3373,7 @@
      * @hide
      */
     @SystemApi
+    @TestApi
     public static final int FLAG_PERMISSION_GRANTED_BY_DEFAULT =  1 << 5;
 
     /**
diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java
index bc41806..b0437ac 100644
--- a/core/java/android/content/res/ApkAssets.java
+++ b/core/java/android/content/res/ApkAssets.java
@@ -101,14 +101,11 @@
     public @interface FormatType {}
 
     @GuardedBy("this")
-    private final long mNativePtr;
+    private long mNativePtr;  // final, except cleared in finalizer.
 
     @Nullable
     @GuardedBy("this")
-    private final StringBlock mStringBlock;
-
-    @GuardedBy("this")
-    private boolean mOpen = true;
+    private final StringBlock mStringBlock;  // null or closed if mNativePtr = 0.
 
     @PropertyFlags
     private final int mFlags;
@@ -380,12 +377,16 @@
     /** @hide */
     @Nullable
     public OverlayableInfo getOverlayableInfo(String overlayableName) throws IOException {
-        return nativeGetOverlayableInfo(mNativePtr, overlayableName);
+        synchronized (this) {
+            return nativeGetOverlayableInfo(mNativePtr, overlayableName);
+        }
     }
 
     /** @hide */
     public boolean definesOverlayable() throws IOException {
-        return nativeDefinesOverlayable(mNativePtr);
+        synchronized (this) {
+            return nativeDefinesOverlayable(mNativePtr);
+        }
     }
 
     /**
@@ -412,12 +413,12 @@
      */
     public void close() {
         synchronized (this) {
-            if (mOpen) {
-                mOpen = false;
+            if (mNativePtr != 0) {
                 if (mStringBlock != null) {
                     mStringBlock.close();
                 }
                 nativeDestroy(mNativePtr);
+                mNativePtr = 0;
             }
         }
     }
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index a605f5d..f86eb90 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -300,26 +300,6 @@
     }
 
     /**
-     * Reset the lockout when user authenticates with strong auth (e.g. PIN, pattern or password)
-     *
-     * @param userId this operation takes effect for.
-     * @param hardwareAuthToken an opaque token returned by password confirmation.
-     * @hide
-     */
-    @RequiresPermission(USE_BIOMETRIC_INTERNAL)
-    public void resetLockout(int userId, byte[] hardwareAuthToken) {
-        if (mService != null) {
-            try {
-                mService.resetLockout(userId, hardwareAuthToken);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
-        } else {
-            Slog.w(TAG, "resetLockout(): Service not connected");
-        }
-    }
-
-    /**
      * Get a list of AuthenticatorIDs for biometric authenticators which have 1) enrolled templates,
      * and 2) meet the requirements for integrating with Keystore. The AuthenticatorIDs are known
      * in Keystore land as SIDs, and are used during key generation.
diff --git a/core/java/android/hardware/biometrics/IAuthService.aidl b/core/java/android/hardware/biometrics/IAuthService.aidl
index c1d0ad4..dd6aa73 100644
--- a/core/java/android/hardware/biometrics/IAuthService.aidl
+++ b/core/java/android/hardware/biometrics/IAuthService.aidl
@@ -46,9 +46,6 @@
     // Register callback for when keyguard biometric eligibility changes.
     void registerEnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback);
 
-    // Reset the lockout when user authenticates with strong auth (e.g. PIN, pattern or password)
-    void resetLockout(int userId, in byte [] hardwareAuthToken);
-
     // Get a list of AuthenticatorIDs for authenticators which have enrolled templates and meet
     // the requirements for integrating with Keystore. The AuthenticatorID are known in Keystore
     // land as SIDs, and are used during key generation.
diff --git a/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl b/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl
index 8eb22da..5e6fe68 100644
--- a/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl
@@ -53,9 +53,6 @@
     // Return the LockoutTracker status for the specified user
     int getLockoutModeForUser(int userId);
 
-    // Reset the lockout when user authenticates with strong auth (e.g. PIN, pattern or password)
-    void resetLockout(int userId, in byte [] hardwareAuthToken);
-
     // Gets the authenticator ID representing the current set of enrolled templates
     long getAuthenticatorId(int callingUserId);
 }
diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl
index a5b3abb..005ed32 100644
--- a/core/java/android/hardware/biometrics/IBiometricService.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricService.aidl
@@ -56,9 +56,6 @@
     // Client lifecycle is still managed in <Biometric>Service.
     void onReadyForAuthentication(int cookie);
 
-    // Reset the lockout when user authenticates with strong auth (e.g. PIN, pattern or password)
-    void resetLockout(int userId, in byte [] hardwareAuthToken);
-
     // Get a list of AuthenticatorIDs for authenticators which have enrolled templates and meet
     // the requirements for integrating with Keystore. The AuthenticatorID are known in Keystore
     // land as SIDs, and are used during key generation.
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 19cb13c..1b114d3 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -72,6 +72,8 @@
     private static final int MSG_SET_FEATURE_COMPLETED = 107;
     private static final int MSG_CHALLENGE_GENERATED = 108;
     private static final int MSG_FACE_DETECTED = 109;
+    private static final int MSG_CHALLENGE_INTERRUPTED = 110;
+    private static final int MSG_CHALLENGE_INTERRUPT_FINISHED = 111;
 
     private final IFaceService mService;
     private final Context mContext;
@@ -141,14 +143,19 @@
         }
 
         @Override
-        public void onChallengeGenerated(long challenge) {
-            if (mGenerateChallengeCallback instanceof InternalGenerateChallengeCallback) {
-                // Perform this on system_server thread, since the application's thread is
-                // blocked waiting for the result
-                mGenerateChallengeCallback.onGenerateChallengeResult(challenge);
-            } else {
-                mHandler.obtainMessage(MSG_CHALLENGE_GENERATED, challenge).sendToTarget();
-            }
+        public void onChallengeGenerated(int sensorId, long challenge) {
+            mHandler.obtainMessage(MSG_CHALLENGE_GENERATED, sensorId, 0, challenge)
+                    .sendToTarget();
+        }
+
+        @Override
+        public void onChallengeInterrupted(int sensorId) {
+            mHandler.obtainMessage(MSG_CHALLENGE_INTERRUPTED, sensorId).sendToTarget();
+        }
+
+        @Override
+        public void onChallengeInterruptFinished(int sensorId) {
+            mHandler.obtainMessage(MSG_CHALLENGE_INTERRUPT_FINISHED, sensorId).sendToTarget();
         }
     };
 
@@ -405,35 +412,6 @@
     }
 
     /**
-     * Same as {@link #generateChallenge(GenerateChallengeCallback)}, except blocks until the
-     * TEE/hardware operation is complete.
-     * @return challenge generated in the TEE/hardware
-     * @hide
-     */
-    @RequiresPermission(MANAGE_BIOMETRIC)
-    public long generateChallengeBlocking() {
-        final AtomicReference<Long> result = new AtomicReference<>();
-        final CountDownLatch latch = new CountDownLatch(1);
-        final GenerateChallengeCallback callback = new InternalGenerateChallengeCallback() {
-            @Override
-            public void onGenerateChallengeResult(long challenge) {
-                result.set(challenge);
-                latch.countDown();
-            }
-        };
-
-        generateChallenge(callback);
-
-        try {
-            latch.await(1, TimeUnit.SECONDS);
-        } catch (InterruptedException e) {
-            Slog.e(TAG, "Interrupted while generatingChallenge", e);
-            e.printStackTrace();
-        }
-        return result.get();
-    }
-
-    /**
      * Generates a unique random challenge in the TEE. A typical use case is to have it wrapped in a
      * HardwareAuthenticationToken, minted by Gatekeeper upon PIN/Pattern/Password verification.
      * The HardwareAuthenticationToken can then be sent to the biometric HAL together with a
@@ -446,11 +424,12 @@
      * @hide
      */
     @RequiresPermission(MANAGE_BIOMETRIC)
-    public void generateChallenge(GenerateChallengeCallback callback) {
+    public void generateChallenge(int sensorId, GenerateChallengeCallback callback) {
         if (mService != null) {
             try {
                 mGenerateChallengeCallback = callback;
-                mService.generateChallenge(mToken, mServiceReceiver, mContext.getOpPackageName());
+                mService.generateChallenge(mToken, sensorId, mServiceReceiver,
+                        mContext.getOpPackageName());
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -458,15 +437,66 @@
     }
 
     /**
-     * Invalidates the current auth token.
+     * Same as {@link #generateChallenge(int, GenerateChallengeCallback)}, but assumes the first
+     * enumerated sensor.
+     * @hide
+     */
+    @RequiresPermission(MANAGE_BIOMETRIC)
+    public void generateChallenge(GenerateChallengeCallback callback) {
+        final List<FaceSensorProperties> faceSensorProperties = getSensorProperties();
+        if (faceSensorProperties.isEmpty()) {
+            Slog.e(TAG, "No sensors");
+            return;
+        }
+
+        final int sensorId = faceSensorProperties.get(0).sensorId;
+        generateChallenge(sensorId, callback);
+    }
+
+    /**
+     * Invalidates the current challenge.
      *
      * @hide
      */
     @RequiresPermission(MANAGE_BIOMETRIC)
     public void revokeChallenge() {
+        final List<FaceSensorProperties> faceSensorProperties = getSensorProperties();
+        if (faceSensorProperties.isEmpty()) {
+            Slog.e(TAG, "No sensors during revokeChallenge");
+        }
+        revokeChallenge(faceSensorProperties.get(0).sensorId);
+    }
+
+    /**
+     * Invalidates the current challenge.
+     *
+     * @hide
+     */
+    @RequiresPermission(MANAGE_BIOMETRIC)
+    public void revokeChallenge(int sensorId) {
         if (mService != null) {
             try {
-                mService.revokeChallenge(mToken, mContext.getOpPackageName());
+                mService.revokeChallenge(mToken, sensorId, mContext.getOpPackageName());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Reset the lockout when user authenticates with strong auth (e.g. PIN, pattern or password)
+     *
+     * @param sensorId Sensor ID that this operation takes effect for
+     * @param userId User ID that this operation takes effect for.
+     * @param hardwareAuthToken An opaque token returned by password confirmation.
+     * @hide
+     */
+    @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+    public void resetLockout(int sensorId, int userId, @Nullable byte[] hardwareAuthToken) {
+        if (mService != null) {
+            try {
+                mService.resetLockout(mToken, sensorId, userId, hardwareAuthToken,
+                        mContext.getOpPackageName());
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -1071,14 +1101,26 @@
     }
 
     /**
+     * Callback structure provided to {@link #generateChallenge(int, GenerateChallengeCallback)}.
      * @hide
      */
     public interface GenerateChallengeCallback {
-        void onGenerateChallengeResult(long challenge);
-    }
+        /**
+         * Invoked when a challenge has been generated.
+         */
+        void onGenerateChallengeResult(int sensorId, long challenge);
 
-    private abstract static class InternalGenerateChallengeCallback
-            implements GenerateChallengeCallback {}
+        /**
+         * Invoked if the challenge has not been revoked and a subsequent caller/owner invokes
+         * {@link #generateChallenge(int, GenerateChallengeCallback)}, but
+         */
+        default void onChallengeInterrupted(int sensorId) {}
+
+        /**
+         * Invoked when the interrupting client has finished (e.g. revoked its challenge).
+         */
+        default void onChallengeInterruptFinished(int sensorId) {}
+    }
 
     private class OnEnrollCancelListener implements OnCancelListener {
         @Override
@@ -1151,12 +1193,18 @@
                     args.recycle();
                     break;
                 case MSG_CHALLENGE_GENERATED:
-                    sendChallengeGenerated((long) msg.obj /* challenge */);
+                    sendChallengeGenerated(msg.arg1 /* sensorId */, (long) msg.obj /* challenge */);
                     break;
                 case MSG_FACE_DETECTED:
                     sendFaceDetected(msg.arg1 /* sensorId */, msg.arg2 /* userId */,
                             (boolean) msg.obj /* isStrongBiometric */);
                     break;
+                case MSG_CHALLENGE_INTERRUPTED:
+                    sendChallengeInterrupted((int) msg.obj /* sensorId */);
+                    break;
+                case MSG_CHALLENGE_INTERRUPT_FINISHED:
+                    sendChallengeInterruptFinished((int) msg.obj /* sensorId */);
+                    break;
                 default:
                     Slog.w(TAG, "Unknown message: " + msg.what);
             }
@@ -1178,11 +1226,11 @@
         mGetFeatureCallback.onCompleted(success, feature, value);
     }
 
-    private void sendChallengeGenerated(long challenge) {
+    private void sendChallengeGenerated(int sensorId, long challenge) {
         if (mGenerateChallengeCallback == null) {
             return;
         }
-        mGenerateChallengeCallback.onGenerateChallengeResult(challenge);
+        mGenerateChallengeCallback.onGenerateChallengeResult(sensorId, challenge);
     }
 
     private void sendFaceDetected(int sensorId, int userId, boolean isStrongBiometric) {
@@ -1193,6 +1241,22 @@
         mFaceDetectionCallback.onFaceDetected(sensorId, userId, isStrongBiometric);
     }
 
+    private void sendChallengeInterrupted(int sensorId) {
+        if (mGenerateChallengeCallback == null) {
+            Slog.e(TAG, "sendChallengeInterrupted, callback null");
+            return;
+        }
+        mGenerateChallengeCallback.onChallengeInterrupted(sensorId);
+    }
+
+    private void sendChallengeInterruptFinished(int sensorId) {
+        if (mGenerateChallengeCallback == null) {
+            Slog.e(TAG, "sendChallengeInterruptFinished, callback null");
+            return;
+        }
+        mGenerateChallengeCallback.onChallengeInterruptFinished(sensorId);
+    }
+
     private void sendRemovedResult(Face face, int remaining) {
         if (mRemovalCallback == null) {
             return;
diff --git a/core/java/android/hardware/face/FaceSensorProperties.java b/core/java/android/hardware/face/FaceSensorProperties.java
index e3b2fbb..1015724 100644
--- a/core/java/android/hardware/face/FaceSensorProperties.java
+++ b/core/java/android/hardware/face/FaceSensorProperties.java
@@ -25,20 +25,36 @@
  */
 public class FaceSensorProperties implements Parcelable {
 
+    /**
+     * A statically configured ID representing this sensor. Sensor IDs must be unique across all
+     * biometrics across the device, starting at 0, and in increments of 1.
+     */
     public final int sensorId;
+    /**
+     * True if the sensor is able to perform generic face detection, without running the
+     * matching algorithm, and without affecting the lockout counter.
+     */
     public final boolean supportsFaceDetection;
+    /**
+     * True if the sensor is able to provide self illumination in dark scenarios, without support
+     * from above the HAL.
+     */
+    public final boolean supportsSelfIllumination;
 
     /**
      * Initializes SensorProperties with specified values
      */
-    public FaceSensorProperties(int sensorId, boolean supportsFaceDetection) {
+    public FaceSensorProperties(int sensorId, boolean supportsFaceDetection,
+            boolean supportsSelfIllumination) {
         this.sensorId = sensorId;
         this.supportsFaceDetection = supportsFaceDetection;
+        this.supportsSelfIllumination = supportsSelfIllumination;
     }
 
     protected FaceSensorProperties(Parcel in) {
         sensorId = in.readInt();
         supportsFaceDetection = in.readBoolean();
+        supportsSelfIllumination = in.readBoolean();
     }
 
     public static final Creator<FaceSensorProperties> CREATOR =
@@ -63,5 +79,6 @@
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(sensorId);
         dest.writeBoolean(supportsFaceDetection);
+        dest.writeBoolean(supportsSelfIllumination);
     }
 }
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index a9097d4..437feb1 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -83,10 +83,10 @@
     boolean isHardwareDetected(String opPackageName);
 
     // Get a pre-enrollment authentication token
-    void generateChallenge(IBinder token, IFaceServiceReceiver receiver, String opPackageName);
+    void generateChallenge(IBinder token, int sensorId, IFaceServiceReceiver receiver, String opPackageName);
 
     // Finish an enrollment sequence and invalidate the authentication token
-    void revokeChallenge(IBinder token, String opPackageName);
+    void revokeChallenge(IBinder token, int sensorId, String opPackageName);
 
     // Determine if a user has at least one enrolled face
     boolean hasEnrolledFaces(int userId, String opPackageName);
@@ -98,7 +98,7 @@
     long getAuthenticatorId(int callingUserId);
 
     // Reset the lockout when user authenticates with strong auth (e.g. PIN, pattern or password)
-    void resetLockout(int userId, in byte [] hardwareAuthToken);
+    void resetLockout(IBinder token, int sensorId, int userId, in byte [] hardwareAuthToken, String opPackageName);
 
     // Add a callback which gets notified when the face lockout period expired.
     void addLockoutResetCallback(IBiometricServiceLockoutResetCallback callback, String opPackageName);
diff --git a/core/java/android/hardware/face/IFaceServiceReceiver.aidl b/core/java/android/hardware/face/IFaceServiceReceiver.aidl
index 2600b7de..bd4d3a0 100644
--- a/core/java/android/hardware/face/IFaceServiceReceiver.aidl
+++ b/core/java/android/hardware/face/IFaceServiceReceiver.aidl
@@ -31,5 +31,7 @@
     void onRemoved(in Face face, int remaining);
     void onFeatureSet(boolean success, int feature);
     void onFeatureGet(boolean success, int feature, boolean value);
-    void onChallengeGenerated(long challenge);
+    void onChallengeGenerated(int sensorId, long challenge);
+    void onChallengeInterrupted(int sensorId);
+    void onChallengeInterruptFinished(int sensorId);
 }
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 71598eb..c12bb39 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -18,6 +18,7 @@
 
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.Manifest.permission.MANAGE_FINGERPRINT;
+import static android.Manifest.permission.RESET_FINGERPRINT_LOCKOUT;
 import static android.Manifest.permission.USE_BIOMETRIC;
 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
 import static android.Manifest.permission.USE_FINGERPRINT;
@@ -378,12 +379,9 @@
      * @hide
      */
     public interface GenerateChallengeCallback {
-        void onChallengeGenerated(long challenge);
+        void onChallengeGenerated(int sensorId, long challenge);
     }
 
-    private abstract static class InternalGenerateChallengeCallback
-            implements GenerateChallengeCallback {}
-
     /**
      * Request authentication of a crypto object. This call warms up the fingerprint hardware
      * and starts scanning for a fingerprint. It terminates when
@@ -593,16 +591,34 @@
      * @hide
      */
     @RequiresPermission(MANAGE_FINGERPRINT)
-    public void generateChallenge(GenerateChallengeCallback callback) {
+    public void generateChallenge(int sensorId, GenerateChallengeCallback callback) {
         if (mService != null) try {
             mGenerateChallengeCallback = callback;
-            mService.generateChallenge(mToken, mServiceReceiver, mContext.getOpPackageName());
+            mService.generateChallenge(mToken, sensorId, mServiceReceiver,
+                    mContext.getOpPackageName());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
     /**
+     * Same as {@link #generateChallenge(int, GenerateChallengeCallback)}, but assumes the first
+     * enumerated sensor.
+     * @hide
+     */
+    @RequiresPermission(MANAGE_FINGERPRINT)
+    public void generateChallenge(GenerateChallengeCallback callback) {
+        final List<FingerprintSensorProperties> fingerprintSensorProperties = getSensorProperties();
+        if (fingerprintSensorProperties.isEmpty()) {
+            Slog.e(TAG, "No sensors");
+            return;
+        }
+
+        final int sensorId = fingerprintSensorProperties.get(0).sensorId;
+        generateChallenge(sensorId, callback);
+    }
+
+    /**
      * Finishes enrollment and cancels the current auth token.
      * @hide
      */
@@ -616,6 +632,26 @@
     }
 
     /**
+     * Reset the lockout when user authenticates with strong auth (e.g. PIN, pattern or password)
+     *
+     * @param sensorId Sensor ID that this operation takes effect for
+     * @param userId User ID that this operation takes effect for.
+     * @param hardwareAuthToken An opaque token returned by password confirmation.
+     * @hide
+     */
+    @RequiresPermission(RESET_FINGERPRINT_LOCKOUT)
+    public void resetLockout(int sensorId, int userId, @Nullable byte[] hardwareAuthToken) {
+        if (mService != null) {
+            try {
+                mService.resetLockout(mToken, sensorId, userId, hardwareAuthToken,
+                        mContext.getOpPackageName());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
      * Remove given fingerprint template from fingerprint hardware and/or protected storage.
      * @param fp the fingerprint item to remove
      * @param userId the user who this fingerprint belongs to
@@ -901,7 +937,7 @@
                     sendRemovedResult((Fingerprint) msg.obj, msg.arg1 /* remaining */);
                     break;
                 case MSG_CHALLENGE_GENERATED:
-                    sendChallengeGenerated((long) msg.obj /* challenge */);
+                    sendChallengeGenerated(msg.arg1 /* sensorId */, (long) msg.obj /* challenge */);
                     break;
                 case MSG_FINGERPRINT_DETECTED:
                     sendFingerprintDetected(msg.arg1 /* sensorId */, msg.arg2 /* userId */,
@@ -989,12 +1025,12 @@
         }
     }
 
-    private void sendChallengeGenerated(long challenge) {
+    private void sendChallengeGenerated(int sensorId, long challenge) {
         if (mGenerateChallengeCallback == null) {
             Slog.e(TAG, "sendChallengeGenerated, callback null");
             return;
         }
-        mGenerateChallengeCallback.onChallengeGenerated(challenge);
+        mGenerateChallengeCallback.onChallengeGenerated(sensorId, challenge);
     }
 
     private void sendFingerprintDetected(int sensorId, int userId, boolean isStrongBiometric) {
@@ -1178,14 +1214,9 @@
         }
 
         @Override // binder call
-        public void onChallengeGenerated(long challenge) {
-            if (mGenerateChallengeCallback instanceof InternalGenerateChallengeCallback) {
-                // Perform this on system_server thread, since the application's thread is
-                // blocked waiting for the result
-                mGenerateChallengeCallback.onChallengeGenerated(challenge);
-            } else {
-                mHandler.obtainMessage(MSG_CHALLENGE_GENERATED, challenge).sendToTarget();
-            }
+        public void onChallengeGenerated(int sensorId, long challenge) {
+            mHandler.obtainMessage(MSG_CHALLENGE_GENERATED, sensorId, 0, challenge)
+                    .sendToTarget();
         }
     };
 
diff --git a/core/java/android/hardware/fingerprint/FingerprintSensorProperties.java b/core/java/android/hardware/fingerprint/FingerprintSensorProperties.java
index a774121..b28551c 100644
--- a/core/java/android/hardware/fingerprint/FingerprintSensorProperties.java
+++ b/core/java/android/hardware/fingerprint/FingerprintSensorProperties.java
@@ -45,18 +45,24 @@
 
     public final int sensorId;
     public final @SensorType int sensorType;
+    // IBiometricsFingerprint@2.1 does not manage timeout below the HAL, so the Gatekeeper HAT
+    // cannot be checked
+    public final boolean resetLockoutRequiresHardwareAuthToken;
 
     /**
      * Initializes SensorProperties with specified values
      */
-    public FingerprintSensorProperties(int sensorId, @SensorType int sensorType) {
+    public FingerprintSensorProperties(int sensorId, @SensorType int sensorType,
+            boolean resetLockoutRequiresHardwareAuthToken) {
         this.sensorId = sensorId;
         this.sensorType = sensorType;
+        this.resetLockoutRequiresHardwareAuthToken = resetLockoutRequiresHardwareAuthToken;
     }
 
     protected FingerprintSensorProperties(Parcel in) {
         sensorId = in.readInt();
         sensorType = in.readInt();
+        resetLockoutRequiresHardwareAuthToken = in.readBoolean();
     }
 
     public static final Creator<FingerprintSensorProperties> CREATOR =
@@ -81,5 +87,6 @@
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(sensorId);
         dest.writeInt(sensorType);
+        dest.writeBoolean(resetLockoutRequiresHardwareAuthToken);
     }
 }
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index f6069d8..0fae156 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -88,7 +88,7 @@
     boolean isHardwareDetected(String opPackageName);
 
     // Get a pre-enrollment authentication token
-    void generateChallenge(IBinder token, IFingerprintServiceReceiver receiver, String opPackageName);
+    void generateChallenge(IBinder token, int sensorId, IFingerprintServiceReceiver receiver, String opPackageName);
 
     // Finish an enrollment sequence and invalidate the authentication token
     void revokeChallenge(IBinder token, String opPackageName);
@@ -103,7 +103,7 @@
     long getAuthenticatorId(int callingUserId);
 
     // Reset the timeout when user authenticates with strong auth (e.g. PIN, pattern or password)
-    void resetLockout(int userId, in byte [] hardwareAuthToken);
+    void resetLockout(IBinder token, int sensorId, int userId, in byte[] hardwareAuthToken, String opPackageNAame);
 
     // Add a callback which gets notified when the fingerprint lockout period expired.
     void addLockoutResetCallback(IBiometricServiceLockoutResetCallback callback, String opPackageName);
diff --git a/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
index ad8fbc0..095b8e9 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
@@ -29,5 +29,5 @@
     void onAuthenticationFailed();
     void onError(int error, int vendorCode);
     void onRemoved(in Fingerprint fp, int remaining);
-    void onChallengeGenerated(long challenge);
+    void onChallengeGenerated(int sensorId, long challenge);
 }
diff --git a/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl b/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl
index 32530da..a57726c 100644
--- a/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl
+++ b/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl
@@ -25,4 +25,7 @@
 
     // Hides the overlay.
     void hideUdfpsOverlay();
+
+    // Shows debug messages on the UDFPS overlay.
+    void setDebugMessage(String message);
 }
diff --git a/core/java/android/net/NetworkTemplate.java b/core/java/android/net/NetworkTemplate.java
index 7234eb1..cd26079 100644
--- a/core/java/android/net/NetworkTemplate.java
+++ b/core/java/android/net/NetworkTemplate.java
@@ -503,6 +503,10 @@
         for (final int ratType : ratTypes) {
             collapsedRatTypes.add(NetworkTemplate.getCollapsedRatType(ratType));
         }
+        // Add NETWORK_TYPE_5G_NSA to the returned list since 5G NSA is a virtual RAT type and
+        // it is not in TelephonyManager#NETWORK_TYPE_* constants.
+        // See {@link NetworkTemplate#NETWORK_TYPE_5G_NSA}.
+        collapsedRatTypes.add(NetworkTemplate.getCollapsedRatType(NETWORK_TYPE_5G_NSA));
         // Ensure that unknown type is returned.
         collapsedRatTypes.add(TelephonyManager.NETWORK_TYPE_UNKNOWN);
         return toIntArray(collapsedRatTypes);
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 1050c7f..8e865e7 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -1012,10 +1012,16 @@
 
         /**
          * Q.
-         * <p>
-         * <em>Why? Why, to give you a taste of your future, a preview of things
-         * to come. Con permiso, Capitan. The hall is rented, the orchestra
-         * engaged. It's now time to see if you can dance.</em>
+         *
+         * <p>Applications targeting this or a later release will get these new changes in behavior.
+         * For more information about this release, see the
+         * <a href="/about/versions/10">Android 10 overview</a>.</p>
+         * <ul>
+         * <li><a href="/about/versions/10/behavior-changes-all">Behavior changes: all apps</a></li>
+         * <li><a href="/about/versions/10/behavior-changes-10">Behavior changes: apps targeting API
+         * 29+</a></li>
+         * </ul>
+         *
          */
         public static final int Q = 29;
 
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index a6b869d..1eb3fc1 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -64,10 +64,11 @@
     private static final String SYSTEM_DRIVER_NAME = "system";
     private static final String SYSTEM_DRIVER_VERSION_NAME = "";
     private static final long SYSTEM_DRIVER_VERSION_CODE = 0;
-    private static final String PROPERTY_GFX_DRIVER = "ro.gfx.driver.0";
+    private static final String PROPERTY_GFX_DRIVER_PRODUCTION = "ro.gfx.driver.0";
     private static final String PROPERTY_GFX_DRIVER_PRERELEASE = "ro.gfx.driver.1";
     private static final String PROPERTY_GFX_DRIVER_BUILD_TIME = "ro.gfx.driver_build_time";
-    private static final String METADATA_DRIVER_BUILD_TIME = "com.android.gamedriver.build_time";
+    private static final String METADATA_DRIVER_BUILD_TIME =
+            "com.android.graphics.updatabledriver.build_time";
     private static final String METADATA_DEVELOPER_DRIVER_ENABLE =
             "com.android.graphics.developerdriver.enable";
     private static final String METADATA_INJECT_LAYERS_ENABLE =
@@ -78,20 +79,20 @@
     private static final String ACTION_ANGLE_FOR_ANDROID_TOAST_MESSAGE =
             "android.app.action.ANGLE_FOR_ANDROID_TOAST_MESSAGE";
     private static final String INTENT_KEY_A4A_TOAST_MESSAGE = "A4A Toast Message";
-    private static final String GAME_DRIVER_ALLOWLIST_ALL = "*";
-    private static final String GAME_DRIVER_SPHAL_LIBRARIES_FILENAME = "sphal_libraries.txt";
+    private static final String UPDATABLE_DRIVER_ALLOWLIST_ALL = "*";
+    private static final String UPDATABLE_DRIVER_SPHAL_LIBRARIES_FILENAME = "sphal_libraries.txt";
     private static final int VULKAN_1_0 = 0x00400000;
     private static final int VULKAN_1_1 = 0x00401000;
 
-    // GAME_DRIVER_ALL_APPS
+    // UPDATABLE_DRIVER_ALL_APPS
     // 0: Default (Invalid values fallback to default as well)
-    // 1: All apps use Game Driver
-    // 2: All apps use Prerelease Driver
+    // 1: All apps use updatable production driver
+    // 2: All apps use updatable prerelease driver
     // 3: All apps use system graphics driver
-    private static final int GAME_DRIVER_GLOBAL_OPT_IN_DEFAULT = 0;
-    private static final int GAME_DRIVER_GLOBAL_OPT_IN_GAME_DRIVER = 1;
-    private static final int GAME_DRIVER_GLOBAL_OPT_IN_PRERELEASE_DRIVER = 2;
-    private static final int GAME_DRIVER_GLOBAL_OPT_IN_OFF = 3;
+    private static final int UPDATABLE_DRIVER_GLOBAL_OPT_IN_DEFAULT = 0;
+    private static final int UPDATABLE_DRIVER_GLOBAL_OPT_IN_PRODUCTION_DRIVER = 1;
+    private static final int UPDATABLE_DRIVER_GLOBAL_OPT_IN_PRERELEASE_DRIVER = 2;
+    private static final int UPDATABLE_DRIVER_GLOBAL_OPT_IN_OFF = 3;
 
     private ClassLoader mClassLoader;
     private String mLibrarySearchPaths;
@@ -722,14 +723,17 @@
      * Return the driver package name to use. Return null for system driver.
      */
     private static String chooseDriverInternal(Bundle coreSettings, ApplicationInfo ai) {
-        final String gameDriver = SystemProperties.get(PROPERTY_GFX_DRIVER);
-        final boolean hasGameDriver = gameDriver != null && !gameDriver.isEmpty();
+        final String productionDriver = SystemProperties.get(PROPERTY_GFX_DRIVER_PRODUCTION);
+        final boolean hasProductionDriver = productionDriver != null && !productionDriver.isEmpty();
 
         final String prereleaseDriver = SystemProperties.get(PROPERTY_GFX_DRIVER_PRERELEASE);
         final boolean hasPrereleaseDriver = prereleaseDriver != null && !prereleaseDriver.isEmpty();
 
-        if (!hasGameDriver && !hasPrereleaseDriver) {
-            if (DEBUG) Log.v(TAG, "Neither Game Driver nor prerelease driver is supported.");
+        if (!hasProductionDriver && !hasPrereleaseDriver) {
+            if (DEBUG) {
+                Log.v(TAG,
+                        "Neither updatable production driver nor prerelease driver is supported.");
+            }
             return null;
         }
 
@@ -745,56 +749,59 @@
                 (ai.metaData != null && ai.metaData.getBoolean(METADATA_DEVELOPER_DRIVER_ENABLE))
                 || isDebuggable();
 
-        // Priority for Game Driver settings global on confliction (Higher priority comes first):
-        // 1. GAME_DRIVER_ALL_APPS
-        // 2. GAME_DRIVER_OPT_OUT_APPS
-        // 3. GAME_DRIVER_PRERELEASE_OPT_IN_APPS
-        // 4. GAME_DRIVER_OPT_IN_APPS
-        // 5. GAME_DRIVER_DENYLIST
-        // 6. GAME_DRIVER_ALLOWLIST
-        switch (coreSettings.getInt(Settings.Global.GAME_DRIVER_ALL_APPS, 0)) {
-            case GAME_DRIVER_GLOBAL_OPT_IN_OFF:
-                if (DEBUG) Log.v(TAG, "Game Driver is turned off on this device.");
+        // Priority of updatable driver settings on confliction (Higher priority comes first):
+        // 1. UPDATABLE_DRIVER_ALL_APPS
+        // 2. UPDATABLE_DRIVER_PRODUCTION_OPT_OUT_APPS
+        // 3. UPDATABLE_DRIVER_PRERELEASE_OPT_IN_APPS
+        // 4. UPDATABLE_DRIVER_PRODUCTION_OPT_IN_APPS
+        // 5. UPDATABLE_DRIVER_PRODUCTION_DENYLIST
+        // 6. UPDATABLE_DRIVER_PRODUCTION_ALLOWLIST
+        switch (coreSettings.getInt(Settings.Global.UPDATABLE_DRIVER_ALL_APPS, 0)) {
+            case UPDATABLE_DRIVER_GLOBAL_OPT_IN_OFF:
+                if (DEBUG) Log.v(TAG, "updatable driver is turned off on this device.");
                 return null;
-            case GAME_DRIVER_GLOBAL_OPT_IN_GAME_DRIVER:
-                if (DEBUG) Log.v(TAG, "All apps opt in to use Game Driver.");
-                return hasGameDriver ? gameDriver : null;
-            case GAME_DRIVER_GLOBAL_OPT_IN_PRERELEASE_DRIVER:
-                if (DEBUG) Log.v(TAG, "All apps opt in to use prerelease driver.");
+            case UPDATABLE_DRIVER_GLOBAL_OPT_IN_PRODUCTION_DRIVER:
+                if (DEBUG) Log.v(TAG, "All apps opt in to use updatable production driver.");
+                return hasProductionDriver ? productionDriver : null;
+            case UPDATABLE_DRIVER_GLOBAL_OPT_IN_PRERELEASE_DRIVER:
+                if (DEBUG) Log.v(TAG, "All apps opt in to use updatable prerelease driver.");
                 return hasPrereleaseDriver && enablePrereleaseDriver ? prereleaseDriver : null;
-            case GAME_DRIVER_GLOBAL_OPT_IN_DEFAULT:
+            case UPDATABLE_DRIVER_GLOBAL_OPT_IN_DEFAULT:
             default:
                 break;
         }
 
         final String appPackageName = ai.packageName;
-        if (getGlobalSettingsString(null, coreSettings, Settings.Global.GAME_DRIVER_OPT_OUT_APPS)
+        if (getGlobalSettingsString(null, coreSettings,
+                                    Settings.Global.UPDATABLE_DRIVER_PRODUCTION_OPT_OUT_APPS)
                         .contains(appPackageName)) {
-            if (DEBUG) Log.v(TAG, "App opts out for Game Driver.");
+            if (DEBUG) Log.v(TAG, "App opts out for updatable production driver.");
             return null;
         }
 
         if (getGlobalSettingsString(
-                    null, coreSettings, Settings.Global.GAME_DRIVER_PRERELEASE_OPT_IN_APPS)
+                    null, coreSettings, Settings.Global.UPDATABLE_DRIVER_PRERELEASE_OPT_IN_APPS)
                         .contains(appPackageName)) {
-            if (DEBUG) Log.v(TAG, "App opts in for prerelease Game Driver.");
+            if (DEBUG) Log.v(TAG, "App opts in for updatable prerelease driver.");
             return hasPrereleaseDriver && enablePrereleaseDriver ? prereleaseDriver : null;
         }
 
-        // Early return here since the rest logic is only for Game Driver.
-        if (!hasGameDriver) {
-            if (DEBUG) Log.v(TAG, "Game Driver is not supported on the device.");
+        // Early return here since the rest logic is only for updatable production Driver.
+        if (!hasProductionDriver) {
+            if (DEBUG) Log.v(TAG, "Updatable production driver is not supported on the device.");
             return null;
         }
 
         final boolean isOptIn =
-                getGlobalSettingsString(null, coreSettings, Settings.Global.GAME_DRIVER_OPT_IN_APPS)
+                getGlobalSettingsString(null, coreSettings,
+                                        Settings.Global.UPDATABLE_DRIVER_PRODUCTION_OPT_IN_APPS)
                         .contains(appPackageName);
         final List<String> allowlist =
-                getGlobalSettingsString(null, coreSettings, Settings.Global.GAME_DRIVER_ALLOWLIST);
-        if (!isOptIn && allowlist.indexOf(GAME_DRIVER_ALLOWLIST_ALL) != 0
+                getGlobalSettingsString(null, coreSettings,
+                                        Settings.Global.UPDATABLE_DRIVER_PRODUCTION_ALLOWLIST);
+        if (!isOptIn && allowlist.indexOf(UPDATABLE_DRIVER_ALLOWLIST_ALL) != 0
                 && !allowlist.contains(appPackageName)) {
-            if (DEBUG) Log.v(TAG, "App is not on the allowlist for Game Driver.");
+            if (DEBUG) Log.v(TAG, "App is not on the allowlist for updatable production driver.");
             return null;
         }
 
@@ -802,13 +809,13 @@
         // terminate early if it's on the denylist and fallback to system driver.
         if (!isOptIn
                 && getGlobalSettingsString(
-                        null, coreSettings, Settings.Global.GAME_DRIVER_DENYLIST)
+                        null, coreSettings, Settings.Global.UPDATABLE_DRIVER_PRODUCTION_DENYLIST)
                            .contains(appPackageName)) {
-            if (DEBUG) Log.v(TAG, "App is on the denylist for Game Driver.");
+            if (DEBUG) Log.v(TAG, "App is on the denylist for updatable production driver.");
             return null;
         }
 
-        return gameDriver;
+        return productionDriver;
     }
 
     /**
@@ -873,7 +880,7 @@
 
         final String driverBuildTime = driverAppInfo.metaData.getString(METADATA_DRIVER_BUILD_TIME);
         if (driverBuildTime == null || driverBuildTime.isEmpty()) {
-            throw new IllegalArgumentException("com.android.gamedriver.build_time is not set");
+            Log.v(TAG, "com.android.graphics.updatabledriver.build_time is not set");
         }
         // driver_build_time in the meta-data is in "L<Unix epoch timestamp>" format. e.g. L123456.
         // Long.parseLong will throw if the meta-data "driver_build_time" is not set properly.
@@ -901,7 +908,7 @@
             final Context driverContext =
                     context.createPackageContext(driverPackageName, Context.CONTEXT_RESTRICTED);
             final BufferedReader reader = new BufferedReader(new InputStreamReader(
-                    driverContext.getAssets().open(GAME_DRIVER_SPHAL_LIBRARIES_FILENAME)));
+                    driverContext.getAssets().open(UPDATABLE_DRIVER_SPHAL_LIBRARIES_FILENAME)));
             final ArrayList<String> assetStrings = new ArrayList<>();
             for (String assetString; (assetString = reader.readLine()) != null;) {
                 assetStrings.add(assetString);
@@ -913,7 +920,7 @@
             }
         } catch (IOException e) {
             if (DEBUG) {
-                Log.w(TAG, "Failed to load '" + GAME_DRIVER_SPHAL_LIBRARIES_FILENAME + "'");
+                Log.w(TAG, "Failed to load '" + UPDATABLE_DRIVER_SPHAL_LIBRARIES_FILENAME + "'");
             }
         }
         return "";
diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java
index 8ad35e7..e2e6140 100644
--- a/core/java/android/permission/PermissionControllerService.java
+++ b/core/java/android/permission/PermissionControllerService.java
@@ -35,6 +35,8 @@
 import android.annotation.SystemApi;
 import android.app.Service;
 import android.app.admin.DevicePolicyManager.PermissionGrantState;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
 import android.content.Intent;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
@@ -83,6 +85,15 @@
     public static final String SERVICE_INTERFACE = "android.permission.PermissionControllerService";
 
     /**
+     * A ChangeId indicating that this device supports camera and mic indicators. Will be "false"
+     * if present, because the CompatChanges#isChangeEnabled method returns true if the change id
+     * is not present.
+     */
+    @ChangeId
+    @Disabled
+    private static final long CAMERA_MIC_INDICATORS_NOT_PRESENT = 162547999L;
+
+    /**
      * Revoke a set of runtime permissions for various apps.
      *
      * @param requests The permissions to revoke as {@code Map<packageName, List<permission>>}
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 0c19071..b9d27e9 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -25,6 +25,7 @@
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
+import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.ActivityThread;
 import android.app.IActivityManager;
@@ -637,24 +638,25 @@
     private static final class PackageNamePermissionQuery {
         final String permName;
         final String pkgName;
-        final int uid;
+        final int userId;
 
-        PackageNamePermissionQuery(@Nullable String permName, @Nullable String pkgName, int uid) {
+        PackageNamePermissionQuery(@Nullable String permName, @Nullable String pkgName,
+                @UserIdInt int userId) {
             this.permName = permName;
             this.pkgName = pkgName;
-            this.uid = uid;
+            this.userId = userId;
         }
 
         @Override
         public String toString() {
             return String.format(
-                    "PackageNamePermissionQuery(pkgName=\"%s\", permName=\"%s, uid=%s\")",
-                    pkgName, permName, uid);
+                    "PackageNamePermissionQuery(pkgName=\"%s\", permName=\"%s, userId=%s\")",
+                    pkgName, permName, userId);
         }
 
         @Override
         public int hashCode() {
-            return Objects.hash(permName, pkgName, uid);
+            return Objects.hash(permName, pkgName, userId);
         }
 
         @Override
@@ -670,16 +672,16 @@
             }
             return Objects.equals(permName, other.permName)
                     && Objects.equals(pkgName, other.pkgName)
-                    && uid == other.uid;
+                    && userId == other.userId;
         }
     }
 
     /* @hide */
     private static int checkPackageNamePermissionUncached(
-            String permName, String pkgName, int uid) {
+            String permName, String pkgName, @UserIdInt int userId) {
         try {
             return ActivityThread.getPermissionManager().checkPermission(
-                    permName, pkgName, uid);
+                    permName, pkgName, userId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -693,7 +695,7 @@
                 @Override
                 protected Integer recompute(PackageNamePermissionQuery query) {
                     return checkPackageNamePermissionUncached(
-                            query.permName, query.pkgName, query.uid);
+                            query.permName, query.pkgName, query.userId);
                 }
             };
 
@@ -702,9 +704,10 @@
      *
      * @hide
      */
-    public static int checkPackageNamePermission(String permName, String pkgName, int uid) {
+    public static int checkPackageNamePermission(String permName, String pkgName,
+            @UserIdInt int userId) {
         return sPackageNamePermissionCache.query(
-                new PackageNamePermissionQuery(permName, pkgName, uid));
+                new PackageNamePermissionQuery(permName, pkgName, userId));
     }
 
     /**
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 660455e..6903a99 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8316,6 +8316,13 @@
         public static final String PANIC_GESTURE_ENABLED = "panic_gesture_enabled";
 
         /**
+         * Whether the panic button (emergency sos) sound should be enabled.
+         *
+         * @hide
+         */
+        public static final String PANIC_SOUND_ENABLED = "panic_sound_enabled";
+
+        /**
          * Whether the camera launch gesture to double tap the power button when the screen is off
          * should be disabled.
          *
@@ -8974,6 +8981,14 @@
         public static final String MEDIA_CONTROLS_RESUME = "qs_media_resumption";
 
         /**
+         * Controls which packages are blocked from persisting in media controls when resumption is
+         * enabled. The list of packages is set by the user in the Settings app.
+         * @see Settings.Secure#MEDIA_CONTROLS_RESUME
+         * @hide
+         */
+        public static final String MEDIA_CONTROLS_RESUME_BLOCKED = "qs_media_resumption_blocked";
+
+        /**
          * Controls if window magnification is enabled.
          * @hide
          */
@@ -9022,6 +9037,13 @@
                 "accessibility_magnification_capability";
 
         /**
+         * Whether the Adaptive connectivity option is enabled.
+         *
+         * @hide
+         */
+        public static final String ADAPTIVE_CONNECTIVITY_ENABLED = "adaptive_connectivity_enabled";
+
+        /**
          * Keys we no longer back up under the current schema, but want to continue to
          * process when restoring historical backup datasets.
          *
@@ -12347,63 +12369,71 @@
                 "show_angle_in_use_dialog_box";
 
         /**
-         * Game Driver global preference for all Apps.
+         * Updatable driver global preference for all Apps.
          * 0 = Default
-         * 1 = All Apps use Game Driver
-         * 2 = All Apps use system graphics driver
+         * 1 = All Apps use updatable production driver
+         * 2 = All apps use updatable prerelease driver
+         * 3 = All Apps use system graphics driver
          * @hide
          */
-        public static final String GAME_DRIVER_ALL_APPS = "game_driver_all_apps";
+        public static final String UPDATABLE_DRIVER_ALL_APPS = "updatable_driver_all_apps";
 
         /**
-         * List of Apps selected to use Game Driver.
+         * List of Apps selected to use updatable production driver.
          * i.e. <pkg1>,<pkg2>,...,<pkgN>
          * @hide
          */
-        public static final String GAME_DRIVER_OPT_IN_APPS = "game_driver_opt_in_apps";
+        public static final String UPDATABLE_DRIVER_PRODUCTION_OPT_IN_APPS =
+                "updatable_driver_production_opt_in_apps";
 
         /**
-         * List of Apps selected to use prerelease Game Driver.
+         * List of Apps selected to use updatable prerelease driver.
          * i.e. <pkg1>,<pkg2>,...,<pkgN>
          * @hide
          */
-        public static final String GAME_DRIVER_PRERELEASE_OPT_IN_APPS =
-                "game_driver_prerelease_opt_in_apps";
+        public static final String UPDATABLE_DRIVER_PRERELEASE_OPT_IN_APPS =
+                "updatable_driver_prerelease_opt_in_apps";
 
         /**
-         * List of Apps selected not to use Game Driver.
+         * List of Apps selected not to use updatable production driver.
          * i.e. <pkg1>,<pkg2>,...,<pkgN>
          * @hide
          */
-        public static final String GAME_DRIVER_OPT_OUT_APPS = "game_driver_opt_out_apps";
+        public static final String UPDATABLE_DRIVER_PRODUCTION_OPT_OUT_APPS =
+                "updatable_driver_production_opt_out_apps";
 
         /**
-         * Apps on the denylist that are forbidden to use Game Driver.
+         * Apps on the denylist that are forbidden to use updatable production driver.
          * @hide
          */
-        public static final String GAME_DRIVER_DENYLIST = "game_driver_denylist";
+        public static final String UPDATABLE_DRIVER_PRODUCTION_DENYLIST =
+                "updatable_driver_production_denylist";
 
         /**
-         * List of denylists, each denylist is a denylist for a specific version of Game Driver.
+         * List of denylists, each denylist is a denylist for a specific version of
+         * updatable production driver.
          * @hide
          */
-        public static final String GAME_DRIVER_DENYLISTS = "game_driver_denylists";
+        public static final String UPDATABLE_DRIVER_PRODUCTION_DENYLISTS =
+                "updatable_driver_production_denylists";
 
         /**
-         * Apps on the allowlist that are allowed to use Game Driver.
+         * Apps on the allowlist that are allowed to use updatable production driver.
          * The string is a list of application package names, seperated by comma.
          * i.e. <apk1>,<apk2>,...,<apkN>
          * @hide
          */
-        public static final String GAME_DRIVER_ALLOWLIST = "game_driver_allowlist";
+        public static final String UPDATABLE_DRIVER_PRODUCTION_ALLOWLIST =
+                "updatable_driver_production_allowlist";
 
         /**
-         * List of libraries in sphal accessible by Game Driver
+         * List of libraries in sphal accessible by updatable driver
          * The string is a list of library names, separated by colon.
          * i.e. <lib1>:<lib2>:...:<libN>
          * @hide
          */
-        public static final String GAME_DRIVER_SPHAL_LIBRARIES = "game_driver_sphal_libraries";
+        public static final String UPDATABLE_DRIVER_SPHAL_LIBRARIES =
+                "updatable_driver_sphal_libraries";
 
         /**
          * Ordered GPU debug layer list for Vulkan
diff --git a/core/java/android/service/autofill/InlinePresentation.java b/core/java/android/service/autofill/InlinePresentation.java
index 91416948..6eb2a15 100644
--- a/core/java/android/service/autofill/InlinePresentation.java
+++ b/core/java/android/service/autofill/InlinePresentation.java
@@ -40,6 +40,11 @@
 
     /**
      * Represents the UI content and the action for the inline suggestion.
+     *
+     * <p>The Slice should be constructed using the Content builder provided in the androidx
+     * autofill library e.g. {@code androidx.autofill.inline.v1.InlineSuggestionUi.Content.Builder}
+     * and then converted to a Slice with
+     * {@code androidx.autofill.inline.UiVersions.Content#getSlice()}.</p>
      */
     private final @NonNull Slice mSlice;
 
@@ -90,6 +95,11 @@
      *
      * @param slice
      *   Represents the UI content and the action for the inline suggestion.
+     *
+     *   <p>The Slice should be constructed using the Content builder provided in the androidx
+     *   autofill library e.g. {@code androidx.autofill.inline.v1.InlineSuggestionUi.Content.Builder}
+     *   and then converted to a Slice with
+     *   {@code androidx.autofill.inline.UiVersions.Content#getSlice()}.</p>
      * @param inlinePresentationSpec
      *   Specifies the UI specification for the inline suggestion.
      * @param pinned
@@ -118,6 +128,11 @@
 
     /**
      * Represents the UI content and the action for the inline suggestion.
+     *
+     * <p>The Slice should be constructed using the Content builder provided in the androidx
+     * autofill library e.g. {@code androidx.autofill.inline.v1.InlineSuggestionUi.Content.Builder}
+     * and then converted to a Slice with
+     * {@code androidx.autofill.inline.UiVersions.Content#getSlice()}.</p>
      */
     @DataClass.Generated.Member
     public @NonNull Slice getSlice() {
@@ -244,7 +259,7 @@
     };
 
     @DataClass.Generated(
-            time = 1593131904745L,
+            time = 1596484869201L,
             codegenVersion = "1.0.15",
             sourceFile = "frameworks/base/core/java/android/service/autofill/InlinePresentation.java",
             inputSignatures = "private final @android.annotation.NonNull android.app.slice.Slice mSlice\nprivate final @android.annotation.NonNull android.widget.inline.InlinePresentationSpec mInlinePresentationSpec\nprivate final  boolean mPinned\npublic @android.annotation.NonNull @android.annotation.Size(min=0L) java.lang.String[] getAutofillHints()\nclass InlinePresentation extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstDefs=true, genEqualsHashCode=true)")
diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java
index c1998c6..82f6036 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -69,7 +69,7 @@
     }
 
     public void onServedEditorChanged(EditorInfo info) {
-        if (isDummyOrEmptyEditor(info)) {
+        if (isFallbackOrEmptyEditor(info)) {
             mShowOnNextImeRender = false;
         }
         mFocusedEditor = info;
@@ -167,15 +167,15 @@
         }
     }
 
-    private boolean isDummyOrEmptyEditor(EditorInfo info) {
-        // TODO(b/123044812): Handle dummy input gracefully in IME Insets API
+    private boolean isFallbackOrEmptyEditor(EditorInfo info) {
+        // TODO(b/123044812): Handle fallback input gracefully in IME Insets API
         return info == null || (info.fieldId <= 0 && info.inputType <= 0);
     }
 
     private boolean isServedEditorRendered() {
         if (mFocusedEditor == null || mPreRenderedEditor == null
-                || isDummyOrEmptyEditor(mFocusedEditor)
-                || isDummyOrEmptyEditor(mPreRenderedEditor)) {
+                || isFallbackOrEmptyEditor(mFocusedEditor)
+                || isFallbackOrEmptyEditor(mPreRenderedEditor)) {
             // No view is focused or ready.
             return false;
         }
diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java
index bf94670..6136a80 100644
--- a/core/java/android/view/NotificationHeaderView.java
+++ b/core/java/android/view/NotificationHeaderView.java
@@ -52,14 +52,12 @@
     private View mHeaderText;
     private View mSecondaryHeaderText;
     private OnClickListener mExpandClickListener;
-    private OnClickListener mAppOpsListener;
     private OnClickListener mFeedbackListener;
     private HeaderTouchListener mTouchListener = new HeaderTouchListener();
     private LinearLayout mTransferChip;
     private NotificationExpandButton mExpandButton;
     private CachingIconView mIcon;
     private View mProfileBadge;
-    private View mAppOps;
     private View mFeedbackIcon;
     private boolean mExpanded;
     private boolean mShowExpandButtonAtEnd;
@@ -117,7 +115,6 @@
         mExpandButton = findViewById(com.android.internal.R.id.expand_button);
         mIcon = findViewById(com.android.internal.R.id.icon);
         mProfileBadge = findViewById(com.android.internal.R.id.profile_badge);
-        mAppOps = findViewById(com.android.internal.R.id.app_ops);
         mFeedbackIcon = findViewById(com.android.internal.R.id.feedback);
     }
 
@@ -146,7 +143,6 @@
             // Icons that should go at the end
             if ((child == mExpandButton && mShowExpandButtonAtEnd)
                     || child == mProfileBadge
-                    || child == mAppOps
                     || child == mFeedbackIcon
                     || child == mTransferChip) {
                 iconWidth += lp.leftMargin + lp.rightMargin + child.getMeasuredWidth();
@@ -212,7 +208,6 @@
             // Icons that should go at the end
             if ((child == mExpandButton && mShowExpandButtonAtEnd)
                     || child == mProfileBadge
-                    || child == mAppOps
                     || child == mFeedbackIcon
                     || child == mTransferChip) {
                 if (end == getMeasuredWidth()) {
@@ -282,7 +277,7 @@
     }
 
     private void updateTouchListener() {
-        if (mExpandClickListener == null && mAppOpsListener == null && mFeedbackListener == null) {
+        if (mExpandClickListener == null && mFeedbackListener == null) {
             setOnTouchListener(null);
             return;
         }
@@ -291,14 +286,6 @@
     }
 
     /**
-     * Sets onclick listener for app ops icons.
-     */
-    public void setAppOpsOnClickListener(OnClickListener l) {
-        mAppOpsListener = l;
-        updateTouchListener();
-    }
-
-    /**
      * Sets onclick listener for feedback icon.
      */
     public void setFeedbackOnClickListener(OnClickListener l) {
@@ -394,7 +381,6 @@
 
         private final ArrayList<Rect> mTouchRects = new ArrayList<>();
         private Rect mExpandButtonRect;
-        private Rect mAppOpsRect;
         private Rect mFeedbackRect;
         private int mTouchSlop;
         private boolean mTrackGesture;
@@ -408,9 +394,7 @@
             mTouchRects.clear();
             addRectAroundView(mIcon);
             mExpandButtonRect = addRectAroundView(mExpandButton);
-            mAppOpsRect = addRectAroundView(mAppOps);
             mFeedbackRect = addRectAroundView(mFeedbackIcon);
-            setTouchDelegate(new TouchDelegate(mAppOpsRect, mAppOps));
             addWidthRect();
             mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
         }
@@ -471,11 +455,7 @@
                     break;
                 case MotionEvent.ACTION_UP:
                     if (mTrackGesture) {
-                        if (mAppOps.isVisibleToUser() && (mAppOpsRect.contains((int) x, (int) y)
-                                || mAppOpsRect.contains((int) mDownX, (int) mDownY))) {
-                            mAppOps.performClick();
-                            return true;
-                        } else if (mFeedbackIcon.isVisibleToUser()
+                        if (mFeedbackIcon.isVisibleToUser()
                                 && (mFeedbackRect.contains((int) x, (int) y))
                                 || mFeedbackRect.contains((int) mDownX, (int) mDownY)) {
                             mFeedbackIcon.performClick();
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 7923ff0..32ee290 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -534,7 +534,8 @@
             ScreenshotSource.SCREENSHOT_KEY_OTHER,
             ScreenshotSource.SCREENSHOT_OVERVIEW,
             ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS,
-            ScreenshotSource.SCREENSHOT_OTHER})
+            ScreenshotSource.SCREENSHOT_OTHER,
+            ScreenshotSource.SCREENSHOT_VENDOR_GESTURE})
     @interface ScreenshotSource {
         int SCREENSHOT_GLOBAL_ACTIONS = 0;
         int SCREENSHOT_KEY_CHORD = 1;
@@ -542,6 +543,7 @@
         int SCREENSHOT_OVERVIEW = 3;
         int SCREENSHOT_ACCESSIBILITY_ACTIONS = 4;
         int SCREENSHOT_OTHER = 5;
+        int SCREENSHOT_VENDOR_GESTURE = 6;
     }
 
     /**
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
index d5d631a..eef2726 100644
--- a/core/java/android/view/inputmethod/BaseInputConnection.java
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -54,7 +54,7 @@
     /** @hide */
     protected final InputMethodManager mIMM;
     final View mTargetView;
-    final boolean mDummyMode;
+    final boolean mFallbackMode;
 
     private Object[] mDefaultComposingSpans;
 
@@ -64,14 +64,14 @@
     BaseInputConnection(InputMethodManager mgr, boolean fullEditor) {
         mIMM = mgr;
         mTargetView = null;
-        mDummyMode = !fullEditor;
+        mFallbackMode = !fullEditor;
     }
 
     public BaseInputConnection(View targetView, boolean fullEditor) {
         mIMM = (InputMethodManager)targetView.getContext().getSystemService(
                 Context.INPUT_METHOD_SERVICE);
         mTargetView = targetView;
-        mDummyMode = !fullEditor;
+        mFallbackMode = !fullEditor;
     }
 
     public static final void removeComposingSpans(Spannable text) {
@@ -189,7 +189,7 @@
 
     /**
      * Default implementation replaces any existing composing text with
-     * the given text.  In addition, only if dummy mode, a key event is
+     * the given text.  In addition, only if fallback mode, a key event is
      * sent for the new text and the current editable buffer cleared.
      */
     public boolean commitText(CharSequence text, int newCursorPosition) {
@@ -445,7 +445,7 @@
 
     /**
      * The default implementation removes the composing state from the
-     * current editable text.  In addition, only if dummy mode, a key event is
+     * current editable text.  In addition, only if fallback mode, a key event is
      * sent for the new text and the current editable buffer cleared.
      */
     public boolean finishComposingText() {
@@ -454,7 +454,7 @@
         if (content != null) {
             beginBatchEdit();
             removeComposingSpans(content);
-            // Note: sendCurrentText does nothing unless mDummyMode is set
+            // Note: sendCurrentText does nothing unless mFallbackMode is set
             sendCurrentText();
             endBatchEdit();
         }
@@ -464,10 +464,10 @@
     /**
      * The default implementation uses TextUtils.getCapsMode to get the
      * cursor caps mode for the current selection position in the editable
-     * text, unless in dummy mode in which case 0 is always returned.
+     * text, unless in fallback mode in which case 0 is always returned.
      */
     public int getCursorCapsMode(int reqModes) {
-        if (mDummyMode) return 0;
+        if (mFallbackMode) return 0;
 
         final Editable content = getEditable();
         if (content == null) return 0;
@@ -664,7 +664,7 @@
             content.setSpan(COMPOSING, a, b,
                     Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
 
-            // Note: sendCurrentText does nothing unless mDummyMode is set
+            // Note: sendCurrentText does nothing unless mFallbackMode is set
             sendCurrentText();
             endBatchEdit();
         }
@@ -715,7 +715,7 @@
     }
 
     private void sendCurrentText() {
-        if (!mDummyMode) {
+        if (!mFallbackMode) {
             return;
         }
 
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index 2864485..7cc347d 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -294,7 +294,7 @@
      */
     public InputMethodInfo(String packageName, String className,
             CharSequence label, String settingsActivity) {
-        this(buildDummyResolveInfo(packageName, className, label), false /* isAuxIme */,
+        this(buildFakeResolveInfo(packageName, className, label), false /* isAuxIme */,
                 settingsActivity, null /* subtypes */, 0 /* isDefaultResId */,
                 false /* forceDefault */, true /* supportsSwitchingToNextInputMethod */,
                 false /* inlineSuggestionsEnabled */, false /* isVrOnly */);
@@ -344,7 +344,7 @@
         mIsVrOnly = isVrOnly;
     }
 
-    private static ResolveInfo buildDummyResolveInfo(String packageName, String className,
+    private static ResolveInfo buildFakeResolveInfo(String packageName, String className,
             CharSequence label) {
         ResolveInfo ri = new ResolveInfo();
         ServiceInfo si = new ServiceInfo();
diff --git a/core/java/android/webkit/UserPackage.java b/core/java/android/webkit/UserPackage.java
index 556b24c..2e5ee04 100644
--- a/core/java/android/webkit/UserPackage.java
+++ b/core/java/android/webkit/UserPackage.java
@@ -99,7 +99,7 @@
 
     private static List<UserInfo> getAllUsers(Context context) {
         UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
-        return userManager.getUsers(false);
+        return userManager.getUsers();
     }
 
 }
diff --git a/core/java/android/widget/inline/InlinePresentationSpec.java b/core/java/android/widget/inline/InlinePresentationSpec.java
index 5f924c6..e7727fd 100644
--- a/core/java/android/widget/inline/InlinePresentationSpec.java
+++ b/core/java/android/widget/inline/InlinePresentationSpec.java
@@ -42,8 +42,13 @@
     private final Size mMaxSize;
 
     /**
-     * The extras encoding the UI style information. Defaults to {@code Bundle.Empty} in which case
-     * the default system UI style will be used.
+     * The extras encoding the UI style information.
+     *
+     * <p>The style bundles can be created using the relevant Style classes and their builders in
+     * the androidx autofill library e.g. {@code androidx.autofill.inline.UiVersions.StylesBuilder}.
+     * </p>
+     *
+     * <p>The style must be set for the suggestion to render properly.</p>
      *
      * <p>Note: There should be no remote objects in the bundle, all included remote objects will
      * be removed from the bundle before transmission.</p>
@@ -123,8 +128,13 @@
     }
 
     /**
-     * The extras encoding the UI style information. Defaults to {@code Bundle.Empty} in which case
-     * the default system UI style will be used.
+     * The extras encoding the UI style information.
+     *
+     * <p>The style bundles can be created using the relevant Style classes and their builders in
+     * the androidx autofill library e.g. {@code androidx.autofill.inline.UiVersions.StylesBuilder}.
+     * </p>
+     *
+     * <p>The style must be set for the suggestion to render properly.</p>
      *
      * <p>Note: There should be no remote objects in the bundle, all included remote objects will
      * be removed from the bundle before transmission.</p>
@@ -264,8 +274,13 @@
         }
 
         /**
-         * The extras encoding the UI style information. Defaults to {@code Bundle.Empty} in which case
-         * the default system UI style will be used.
+         * The extras encoding the UI style information.
+         *
+         * <p>The style bundles can be created using the relevant Style classes and their builders in
+         * the androidx autofill library e.g. {@code androidx.autofill.inline.UiVersions.StylesBuilder}.
+         * </p>
+         *
+         * <p>The style must be set for the suggestion to render properly.</p>
          *
          * <p>Note: There should be no remote objects in the bundle, all included remote objects will
          * be removed from the bundle before transmission.</p>
@@ -302,7 +317,7 @@
     }
 
     @DataClass.Generated(
-            time = 1588109681295L,
+            time = 1596485189661L,
             codegenVersion = "1.0.15",
             sourceFile = "frameworks/base/core/java/android/widget/inline/InlinePresentationSpec.java",
             inputSignatures = "private final @android.annotation.NonNull android.util.Size mMinSize\nprivate final @android.annotation.NonNull android.util.Size mMaxSize\nprivate final @android.annotation.NonNull android.os.Bundle mStyle\nprivate static @android.annotation.NonNull android.os.Bundle defaultStyle()\nprivate  boolean styleEquals(android.os.Bundle)\npublic  void filterContentTypes()\nclass InlinePresentationSpec extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nclass BaseBuilder extends java.lang.Object implements []")
diff --git a/core/java/com/android/internal/util/Preconditions.java b/core/java/com/android/internal/util/Preconditions.java
index 330c15c..c82a656 100644
--- a/core/java/com/android/internal/util/Preconditions.java
+++ b/core/java/com/android/internal/util/Preconditions.java
@@ -187,6 +187,32 @@
     }
 
     /**
+     * Ensures the truth of an expression involving whether the calling identity is authorized to
+     * call the calling method.
+     *
+     * @param expression a boolean expression
+     * @throws SecurityException if {@code expression} is false
+     */
+    public static void checkCallAuthorization(final boolean expression) {
+        if (!expression) {
+            throw new SecurityException("Calling identity is not authorized");
+        }
+    }
+
+    /**
+     * Ensures the truth of an expression involving whether the calling user is authorized to
+     * call the calling method.
+     *
+     * @param expression a boolean expression
+     * @throws SecurityException if {@code expression} is false
+     */
+    public static void checkCallingUser(final boolean expression) {
+        if (!expression) {
+            throw new SecurityException("Calling user is not authorized");
+        }
+    }
+
+    /**
      * Check the requested flags, throwing if any requested flags are outside
      * the allowed set.
      *
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 3332143..289a36f 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -168,8 +168,6 @@
     private int mFacePileProtectionWidthExpanded;
     private boolean mImportantConversation;
     private TextView mUnreadBadge;
-    private ViewGroup mAppOps;
-    private Rect mAppOpsTouchRect = new Rect();
     private View mFeedbackIcon;
     private float mMinTouchSize;
     private Icon mConversationIcon;
@@ -214,7 +212,6 @@
         mConversationIconView = findViewById(R.id.conversation_icon);
         mConversationIconContainer = findViewById(R.id.conversation_icon_container);
         mIcon = findViewById(R.id.icon);
-        mAppOps = findViewById(com.android.internal.R.id.app_ops);
         mFeedbackIcon = findViewById(com.android.internal.R.id.feedback);
         mMinTouchSize = 48 * getResources().getDisplayMetrics().density;
         mImportanceRingView = findViewById(R.id.conversation_icon_badge_ring);
@@ -1174,43 +1171,6 @@
             });
         }
         mTouchDelegate.clear();
-        if (mAppOps.getWidth() > 0) {
-
-            // Let's increase the touch size of the app ops view if it's here
-            mAppOpsTouchRect.set(
-                    mAppOps.getLeft(),
-                    mAppOps.getTop(),
-                    mAppOps.getRight(),
-                    mAppOps.getBottom());
-            for (int i = 0; i < mAppOps.getChildCount(); i++) {
-                View child = mAppOps.getChildAt(i);
-                if (child.getVisibility() == GONE) {
-                    continue;
-                }
-                // Make sure each child has at least a minTouchSize touch target around it
-                float childTouchLeft = child.getLeft() + child.getWidth() / 2.0f
-                        - mMinTouchSize / 2.0f;
-                float childTouchRight = childTouchLeft + mMinTouchSize;
-                mAppOpsTouchRect.left = (int) Math.min(mAppOpsTouchRect.left,
-                        mAppOps.getLeft() + childTouchLeft);
-                mAppOpsTouchRect.right = (int) Math.max(mAppOpsTouchRect.right,
-                        mAppOps.getLeft() + childTouchRight);
-            }
-
-            // Increase the height
-            int heightIncrease = 0;
-            if (mAppOpsTouchRect.height() < mMinTouchSize) {
-                heightIncrease = (int) Math.ceil((mMinTouchSize - mAppOpsTouchRect.height())
-                        / 2.0f);
-            }
-            mAppOpsTouchRect.inset(0, -heightIncrease);
-
-            getRelativeTouchRect(mAppOpsTouchRect, mAppOps);
-
-            // Extend the size of the app opps to be at least 48dp
-            mTouchDelegate.add(new TouchDelegate(mAppOpsTouchRect, mAppOps));
-
-        }
         if (mFeedbackIcon.getVisibility() == VISIBLE) {
             updateFeedbackIconMargins();
             float width = Math.max(mMinTouchSize, mFeedbackIcon.getWidth());
@@ -1240,13 +1200,7 @@
 
     private void updateFeedbackIconMargins() {
         MarginLayoutParams lp = (MarginLayoutParams) mFeedbackIcon.getLayoutParams();
-        if (mAppOps.getWidth() == 0) {
-            lp.setMarginStart(mNotificationHeaderSeparatingMargin);
-        } else {
-            float width = Math.max(mMinTouchSize, mFeedbackIcon.getWidth());
-            int horizontalMargin = (int) ((width - mFeedbackIcon.getWidth()) / 2);
-            lp.setMarginStart(horizontalMargin);
-        }
+        lp.setMarginStart(mNotificationHeaderSeparatingMargin);
         mFeedbackIcon.setLayoutParams(lp);
     }
 
diff --git a/core/jni/android_media_AudioDeviceAttributes.cpp b/core/jni/android_media_AudioDeviceAttributes.cpp
index e79c95e..2a16dce 100644
--- a/core/jni/android_media_AudioDeviceAttributes.cpp
+++ b/core/jni/android_media_AudioDeviceAttributes.cpp
@@ -31,7 +31,7 @@
                                  const AudioDeviceTypeAddr *devTypeAddr) {
     jint jStatus = (jint)AUDIO_JAVA_SUCCESS;
     jint jNativeType = (jint)devTypeAddr->mType;
-    ScopedLocalRef<jstring> jAddress(env, env->NewStringUTF(devTypeAddr->mAddress.data()));
+    ScopedLocalRef<jstring> jAddress(env, env->NewStringUTF(devTypeAddr->getAddress()));
 
     *jAudioDeviceAttributes = env->NewObject(gAudioDeviceAttributesClass, gAudioDeviceAttributesCstor,
             jNativeType, jAddress.get());
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 1f62544..3f39478 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -172,6 +172,8 @@
     jmethodID postRecordConfigEventFromNative;
 } gAudioPolicyEventHandlerMethods;
 
+static struct { jmethodID add; } gListMethods;
+
 //
 // JNI Initialization for OpenSLES routing
 //
@@ -310,7 +312,7 @@
 
 static jint getVectorOfAudioDeviceTypeAddr(JNIEnv *env, jintArray deviceTypes,
                                            jobjectArray deviceAddresses,
-                                           Vector<AudioDeviceTypeAddr> &audioDeviceTypeAddrVector) {
+                                           AudioDeviceTypeAddrVector &audioDeviceTypeAddrVector) {
     if (deviceTypes == nullptr || deviceAddresses == nullptr) {
         return (jint)AUDIO_JAVA_BAD_VALUE;
     }
@@ -337,7 +339,7 @@
         }
         const char *address = env->GetStringUTFChars((jstring)addrJobj, NULL);
         AudioDeviceTypeAddr dev = AudioDeviceTypeAddr(typesPtr[i], address);
-        audioDeviceTypeAddrVector.add(dev);
+        audioDeviceTypeAddrVector.push_back(dev);
         env->ReleaseStringUTFChars((jstring)addrJobj, address);
     }
     env->ReleaseIntArrayElements(deviceTypes, typesPtr, 0);
@@ -2062,7 +2064,7 @@
 
 static jint android_media_AudioSystem_setUidDeviceAffinities(JNIEnv *env, jobject clazz,
         jint uid, jintArray deviceTypes, jobjectArray deviceAddresses) {
-    Vector<AudioDeviceTypeAddr> deviceVector;
+    AudioDeviceTypeAddrVector deviceVector;
     jint results = getVectorOfAudioDeviceTypeAddr(env, deviceTypes, deviceAddresses, deviceVector);
     if (results != NO_ERROR) {
         return results;
@@ -2080,7 +2082,7 @@
 static jint android_media_AudioSystem_setUserIdDeviceAffinities(JNIEnv *env, jobject clazz,
                                                                 jint userId, jintArray deviceTypes,
                                                                 jobjectArray deviceAddresses) {
-    Vector<AudioDeviceTypeAddr> deviceVector;
+    AudioDeviceTypeAddrVector deviceVector;
     jint results = getVectorOfAudioDeviceTypeAddr(env, deviceTypes, deviceAddresses, deviceVector);
     if (results != NO_ERROR) {
         return results;
@@ -2361,46 +2363,48 @@
     return AudioSystem::isCallScreenModeSupported();
 }
 
-static jint
-android_media_AudioSystem_setPreferredDeviceForStrategy(JNIEnv *env, jobject thiz,
-        jint strategy, jint deviceType, jstring deviceAddress) {
-
-    const char *c_address = env->GetStringUTFChars(deviceAddress, NULL);
+static jint android_media_AudioSystem_setDevicesRoleForStrategy(JNIEnv *env, jobject thiz,
+                                                                jint strategy, jint role,
+                                                                jintArray jDeviceTypes,
+                                                                jobjectArray jDeviceAddresses) {
+    AudioDeviceTypeAddrVector nDevices;
+    jint results = getVectorOfAudioDeviceTypeAddr(env, jDeviceTypes, jDeviceAddresses, nDevices);
+    if (results != NO_ERROR) {
+        return results;
+    }
     int status = check_AudioSystem_Command(
-            AudioSystem::setPreferredDeviceForStrategy((product_strategy_t) strategy,
-                    AudioDeviceTypeAddr(deviceType, c_address)));
-    env->ReleaseStringUTFChars(deviceAddress, c_address);
+            AudioSystem::setDevicesRoleForStrategy((product_strategy_t)strategy,
+                                                   (device_role_t)role, nDevices));
     return (jint) status;
 }
 
-static jint
-android_media_AudioSystem_removePreferredDeviceForStrategy(JNIEnv *env, jobject thiz, jint strategy)
-{
-    return (jint) check_AudioSystem_Command(
-            AudioSystem::removePreferredDeviceForStrategy((product_strategy_t) strategy));
+static jint android_media_AudioSystem_removeDevicesRoleForStrategy(JNIEnv *env, jobject thiz,
+                                                                   jint strategy, jint role) {
+    return (jint)check_AudioSystem_Command(
+            AudioSystem::removeDevicesRoleForStrategy((product_strategy_t)strategy,
+                                                      (device_role_t)role));
 }
 
-static jint
-android_media_AudioSystem_getPreferredDeviceForStrategy(JNIEnv *env, jobject thiz,
-        jint strategy, jobjectArray jDeviceArray)
-{
-    if (jDeviceArray == nullptr || env->GetArrayLength(jDeviceArray) != 1) {
-        ALOGE("%s invalid array to store AudioDeviceAttributes", __FUNCTION__);
-        return (jint)AUDIO_JAVA_BAD_VALUE;
-    }
-
-    AudioDeviceTypeAddr elDevice;
+static jint android_media_AudioSystem_getDevicesForRoleAndStrategy(JNIEnv *env, jobject thiz,
+                                                                   jint strategy, jint role,
+                                                                   jobject jDevices) {
+    AudioDeviceTypeAddrVector nDevices;
     status_t status = check_AudioSystem_Command(
-            AudioSystem::getPreferredDeviceForStrategy((product_strategy_t) strategy, elDevice));
+            AudioSystem::getDevicesForRoleAndStrategy((product_strategy_t)strategy,
+                                                      (device_role_t)role, nDevices));
     if (status != NO_ERROR) {
         return (jint) status;
     }
-    jobject jAudioDeviceAttributes = NULL;
-    jint jStatus = createAudioDeviceAttributesFromNative(env, &jAudioDeviceAttributes, &elDevice);
-    if (jStatus == AUDIO_JAVA_SUCCESS) {
-        env->SetObjectArrayElement(jDeviceArray, 0, jAudioDeviceAttributes);
+    for (const auto &device : nDevices) {
+        jobject jAudioDeviceAttributes = NULL;
+        jint jStatus = createAudioDeviceAttributesFromNative(env, &jAudioDeviceAttributes, &device);
+        if (jStatus != AUDIO_JAVA_SUCCESS) {
+            return jStatus;
+        }
+        env->CallBooleanMethod(jDevices, gListMethods.add, jAudioDeviceAttributes);
+        env->DeleteLocalRef(jAudioDeviceAttributes);
     }
-    return jStatus;
+    return AUDIO_JAVA_SUCCESS;
 }
 
 static jint
@@ -2548,12 +2552,12 @@
          {"setAudioHalPids", "([I)I", (void *)android_media_AudioSystem_setAudioHalPids},
          {"isCallScreeningModeSupported", "()Z",
           (void *)android_media_AudioSystem_isCallScreeningModeSupported},
-         {"setPreferredDeviceForStrategy", "(IILjava/lang/String;)I",
-          (void *)android_media_AudioSystem_setPreferredDeviceForStrategy},
-         {"removePreferredDeviceForStrategy", "(I)I",
-          (void *)android_media_AudioSystem_removePreferredDeviceForStrategy},
-         {"getPreferredDeviceForStrategy", "(I[Landroid/media/AudioDeviceAttributes;)I",
-          (void *)android_media_AudioSystem_getPreferredDeviceForStrategy},
+         {"setDevicesRoleForStrategy", "(II[I[Ljava/lang/String;)I",
+          (void *)android_media_AudioSystem_setDevicesRoleForStrategy},
+         {"removeDevicesRoleForStrategy", "(II)I",
+          (void *)android_media_AudioSystem_removeDevicesRoleForStrategy},
+         {"getDevicesForRoleAndStrategy", "(IILjava/util/List;)I",
+          (void *)android_media_AudioSystem_getDevicesForRoleAndStrategy},
          {"getDevicesForAttributes",
           "(Landroid/media/AudioAttributes;[Landroid/media/AudioDeviceAttributes;)I",
           (void *)android_media_AudioSystem_getDevicesForAttributes},
@@ -2755,6 +2759,9 @@
     gMidAudioRecordRoutingProxy_release =
             android::GetMethodIDOrDie(env, gClsAudioRecordRoutingProxy, "native_release", "()V");
 
+    jclass listClass = FindClassOrDie(env, "java/util/List");
+    gListMethods.add = GetMethodIDOrDie(env, listClass, "add", "(Ljava/lang/Object;)Z");
+
     AudioSystem::addErrorCallback(android_media_AudioSystem_error_callback);
 
     RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index c73441c..f96ed36 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -1790,8 +1790,7 @@
 #ifdef ANDROID_EXPERIMENTAL_MTE
       SetTagCheckingLevel(PR_MTE_TCF_SYNC);
 #endif
-      // TODO(pcc): Use SYNC here once the allocator supports it.
-      heap_tagging_level = M_HEAP_TAGGING_LEVEL_ASYNC;
+      heap_tagging_level = M_HEAP_TAGGING_LEVEL_SYNC;
       break;
     default:
 #ifdef ANDROID_EXPERIMENTAL_MTE
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index 1014fbb..6eb8904 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -744,6 +744,41 @@
     // CATEGORY: SETTINGS
     // OS: R
     ACTION_CONFIRM_SIM_DELETION_OFF = 1739;
+
+    // ACTION: Settings > System > Gestures > Double tap > Toggle on Double tap
+    // CATEGORY: SETTINGS
+    // OS: S
+    ACTION_COLUMBUS_ENABLED = 1740;
+
+    // ACTION: Settings > System > Gestures > Double tap > Toggle off Double tap
+    // CATEGORY: SETTINGS
+    // OS: S
+    ACTION_COLUMBUS_DISABLED = 1741;
+
+    // ACTION: Settings > System > Gestures > Double tap > Invoke Assistant
+    // CATEGORY: SETTINGS
+    // OS: S
+    ACTION_COLUMBUS_ACTION_ASSISTANT = 1742;
+
+    // ACTION: Settings > System > Gestures > Double tap > Take screenshot
+    // CATEGORY: SETTINGS
+    // OS: S
+    ACTION_COLUMBUS_ACTION_SCREENSHOT = 1743;
+
+    // ACTION: Settings > System > Gestures > Double tap > Play and pause
+    // CATEGORY: SETTINGS
+    // OS: S
+    ACTION_COLUMBUS_ACTION_PLAY_PAUSE = 1744;
+
+    // ACTION: Settings > System > Gestures > Double tap > Open app overview
+    // CATEGORY: SETTINGS
+    // OS: S
+    ACTION_COLUMBUS_ACTION_OVERVIEW = 1745;
+
+    // ACTION: Settings > System > Gestures > Double tap > Open notification shade
+    // CATEGORY: SETTINGS
+    // OS: S
+    ACTION_COLUMBUS_ACTION_NOTIFICATION_SHADE = 1746;
 }
 
 /**
@@ -2310,10 +2345,10 @@
     // OS: Q
     ZEN_CUSTOM_SETTINGS_DIALOG = 1612;
 
-    // OPEN: Settings > Developer Options > Game Driver Preferences
+    // OPEN: Settings > Developer Options > Graphics Driver Preferences
     // CATEGORY: SETTINGS
     // OS: Q
-    SETTINGS_GAME_DRIVER_DASHBOARD = 1613;
+    SETTINGS_GRAPHICS_DRIVER_DASHBOARD = 1613;
 
     // OPEN: Settings > Accessibility > Vibration > Ring vibration
     // CATEGORY: SETTINGS
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index e4a142b..d5619ca 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -433,35 +433,36 @@
         // Ordered GPU debug layer list for GLES
         // i.e. <layer1>:<layer2>:...:<layerN>
         optional SettingProto debug_layers_gles = 7;
-        // Game Driver - global preference for all Apps
+        // Updatable Driver - global preference for all Apps
         // 0 = Default
-        // 1 = All Apps use Game Driver
-        // 2 = All Apps use system graphics driver
-        optional SettingProto game_driver_all_apps = 8;
-        // Game Driver - List of Apps selected to use Game Driver
+        // 1 = All Apps use updatable production driver
+        // 2 = All apps use updatable prerelease driver
+        // 3 = All Apps use system graphics driver
+        optional SettingProto updatable_driver_all_apps = 8;
+        // Updatable Driver - List of Apps selected to use updatable production driver
         // i.e. <pkg1>,<pkg2>,...,<pkgN>
-        optional SettingProto game_driver_opt_in_apps = 9;
-        // Game Driver - List of Apps selected not to use Game Driver
+        optional SettingProto updatable_driver_production_opt_in_apps = 9;
+        // Updatable Driver - List of Apps selected not to use updatable production driver
         // i.e. <pkg1>,<pkg2>,...,<pkgN>
-        optional SettingProto game_driver_opt_out_apps = 10;
-        // Game Driver - List of Apps that are forbidden to use Game Driver
-        optional SettingProto game_driver_denylist = 11;
-        // Game Driver - List of Apps that are allowed to use Game Driver
-        optional SettingProto game_driver_allowlist = 12;
+        optional SettingProto updatable_driver_production_opt_out_apps = 10;
+        // Updatable Driver - List of Apps that are forbidden to use updatable production driver
+        optional SettingProto updatable_driver_production_denylist = 11;
+        // Updatable Driver - List of Apps that are allowed to use updatable production driver
+        optional SettingProto updatable_driver_production_allowlist = 12;
         // ANGLE - List of Apps that can check ANGLE rules
         optional SettingProto angle_allowlist = 13;
-        // Game Driver - List of denylists, each denylist is a denylist for
-        // a specific Game Driver version
-        optional SettingProto game_driver_denylists = 14;
+        // Updatable Driver - List of denylists, each denylist is a denylist for
+        // a specific updatable production driver version
+        optional SettingProto updatable_driver_production_denylists = 14;
         // ANGLE - Show a dialog box when ANGLE is selected for the currently running PKG
         optional SettingProto show_angle_in_use_dialog = 15;
-        // Game Driver - List of libraries in sphal accessible by Game Driver
-        optional SettingProto game_driver_sphal_libraries = 16;
+        // Updatable Driver - List of libraries in sphal accessible by updatable driver
+        optional SettingProto updatable_driver_sphal_libraries = 16;
         // ANGLE - External package containing ANGLE libraries
         optional SettingProto angle_debug_package = 17;
-        // Game Driver - List of Apps selected to use prerelease Game Driver
+        // Updatable Driver - List of Apps selected to use updatable prerelease driver
         // i.e. <pkg1>,<pkg2>,...,<pkgN>
-        optional SettingProto game_driver_prerelease_opt_in_apps = 18;
+        optional SettingProto updatable_driver_prerelease_opt_in_apps = 18;
     }
     optional Gpu gpu = 59;
 
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index ca4dc18..3d12d07 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -181,6 +181,7 @@
     optional SettingProto cmas_additional_broadcast_pkg = 14 [ (android.privacy).dest = DEST_AUTOMATIC ];
     repeated SettingProto completed_categories = 15;
     optional SettingProto connectivity_release_pending_intent_delay_ms = 16 [ (android.privacy).dest = DEST_AUTOMATIC ];
+    optional SettingProto adaptive_connectivity_enabled = 84 [ (android.privacy).dest = DEST_AUTOMATIC ];
 
     message Controls {
         option (android.msg_privacy).dest = DEST_EXPLICIT;
@@ -211,6 +212,7 @@
 
     message EmergencyResponse {
         optional SettingProto panic_gesture_enabled = 1  [ (android.privacy).dest = DEST_AUTOMATIC ];
+        optional SettingProto panic_sound_enabled = 2  [ (android.privacy).dest = DEST_AUTOMATIC ];
     }
 
     optional EmergencyResponse emergency_response = 83;
@@ -613,5 +615,5 @@
 
     // Please insert fields in alphabetical order and group them into messages
     // if possible (to avoid reaching the method limit).
-    // Next tag = 84;
+    // Next tag = 85;
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index edb9727..32c1e4a 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -558,6 +558,7 @@
     <protected-broadcast android:name="android.os.action.ACTION_EFFECTS_SUPPRESSOR_CHANGED" />
     <protected-broadcast android:name="android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED" />
     <protected-broadcast android:name="android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED" />
+    <protected-broadcast android:name="android.app.action.NOTIFICATION_LISTENER_ENABLED_CHANGED" />
     <protected-broadcast android:name="android.app.action.APP_BLOCK_STATE_CHANGED" />
 
     <protected-broadcast android:name="android.permission.GET_APP_GRANTED_URI_PERMISSIONS" />
diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml
index d22a19f..9a1b592 100644
--- a/core/res/res/layout/notification_template_header.xml
+++ b/core/res/res/layout/notification_template_header.xml
@@ -160,43 +160,6 @@
         android:visibility="gone"
         android:contentDescription="@string/notification_work_profile_content_description"
         />
-    <LinearLayout
-        android:id="@+id/app_ops"
-        android:layout_height="match_parent"
-        android:layout_width="wrap_content"
-        android:layout_marginStart="6dp"
-        android:background="?android:selectableItemBackgroundBorderless"
-        android:orientation="horizontal">
-        <ImageView
-            android:id="@+id/camera"
-            android:layout_width="?attr/notificationHeaderIconSize"
-            android:layout_height="?attr/notificationHeaderIconSize"
-            android:src="@drawable/ic_camera"
-            android:visibility="gone"
-            android:focusable="false"
-            android:contentDescription="@string/notification_appops_camera_active"
-            />
-        <ImageView
-            android:id="@+id/mic"
-            android:layout_width="?attr/notificationHeaderIconSize"
-            android:layout_height="?attr/notificationHeaderIconSize"
-            android:src="@drawable/ic_mic"
-            android:layout_marginStart="4dp"
-            android:visibility="gone"
-            android:focusable="false"
-            android:contentDescription="@string/notification_appops_microphone_active"
-            />
-        <ImageView
-            android:id="@+id/overlay"
-            android:layout_width="?attr/notificationHeaderIconSize"
-            android:layout_height="?attr/notificationHeaderIconSize"
-            android:src="@drawable/ic_alert_window_layer"
-            android:layout_marginStart="4dp"
-            android:visibility="gone"
-            android:focusable="false"
-            android:contentDescription="@string/notification_appops_overlay_active"
-            />
-    </LinearLayout>
     <include
         layout="@layout/notification_material_media_transfer_action"
         android:id="@+id/media_seamless"
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index d0ed635..bad330e 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -983,9 +983,9 @@
     <string name="menu_space_shortcut_label" msgid="5949311515646872071">"স্পেচ"</string>
     <string name="menu_enter_shortcut_label" msgid="6709499510082897320">"লিখক"</string>
     <string name="menu_delete_shortcut_label" msgid="4365787714477739080">"মচক"</string>
-    <string name="search_go" msgid="2141477624421347086">"অনুসন্ধান কৰক"</string>
+    <string name="search_go" msgid="2141477624421347086">"Search"</string>
     <string name="search_hint" msgid="455364685740251925">"অনুসন্ধান কৰক…"</string>
-    <string name="searchview_description_search" msgid="1045552007537359343">"অনুসন্ধান কৰক"</string>
+    <string name="searchview_description_search" msgid="1045552007537359343">"Search"</string>
     <string name="searchview_description_query" msgid="7430242366971716338">"প্ৰশ্নৰ সন্ধান কৰক"</string>
     <string name="searchview_description_clear" msgid="1989371719192982900">"প্ৰশ্ন মচক"</string>
     <string name="searchview_description_submit" msgid="6771060386117334686">"প্ৰশ্ন দাখিল কৰক"</string>
@@ -1401,7 +1401,7 @@
     <string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"জুম নিয়ন্ত্ৰণ কৰিবলৈ দুবাৰ টিপক"</string>
     <string name="gadget_host_error_inflating" msgid="2449961590495198720">"ৱিজেট যোগ কৰিব পৰা নগ\'ল।"</string>
     <string name="ime_action_go" msgid="5536744546326495436">"যাওক"</string>
-    <string name="ime_action_search" msgid="4501435960587287668">"অনুসন্ধান কৰক"</string>
+    <string name="ime_action_search" msgid="4501435960587287668">"Search"</string>
     <string name="ime_action_send" msgid="8456843745664334138">"পঠিয়াওক"</string>
     <string name="ime_action_next" msgid="4169702997635728543">"পৰৱৰ্তী"</string>
     <string name="ime_action_done" msgid="6299921014822891569">"সম্পন্ন হ’ল"</string>
@@ -1881,7 +1881,7 @@
     <string name="language_picker_section_suggested" msgid="6556199184638990447">"প্ৰস্তাৱিত"</string>
     <string name="language_picker_section_all" msgid="1985809075777564284">"সকলো ভাষা"</string>
     <string name="region_picker_section_all" msgid="756441309928774155">"সকলো অঞ্চল"</string>
-    <string name="locale_search_menu" msgid="6258090710176422934">"অনুসন্ধান কৰক"</string>
+    <string name="locale_search_menu" msgid="6258090710176422934">"Search"</string>
     <string name="app_suspended_title" msgid="888873445010322650">"এপটো নাই"</string>
     <string name="app_suspended_default_message" msgid="6451215678552004172">"এই মুহূৰ্তত <xliff:g id="APP_NAME_0">%1$s</xliff:g> উপলব্ধ নহয়। ইয়াক <xliff:g id="APP_NAME_1">%2$s</xliff:g>এ পৰিচালনা কৰে।"</string>
     <string name="app_suspended_more_details" msgid="211260942831587014">"অধিক জানক"</string>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index ffc9ecf..22589cd 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -983,9 +983,9 @@
     <string name="menu_space_shortcut_label" msgid="5949311515646872071">"স্পেস"</string>
     <string name="menu_enter_shortcut_label" msgid="6709499510082897320">"enter"</string>
     <string name="menu_delete_shortcut_label" msgid="4365787714477739080">"মুছুন"</string>
-    <string name="search_go" msgid="2141477624421347086">"খুঁজুন"</string>
+    <string name="search_go" msgid="2141477624421347086">"সার্চ"</string>
     <string name="search_hint" msgid="455364685740251925">"সার্চ করুন..."</string>
-    <string name="searchview_description_search" msgid="1045552007537359343">"খুঁজুন"</string>
+    <string name="searchview_description_search" msgid="1045552007537359343">"সার্চ"</string>
     <string name="searchview_description_query" msgid="7430242366971716338">"সার্চ ক্যোয়ারী"</string>
     <string name="searchview_description_clear" msgid="1989371719192982900">"ক্যোয়ারী সাফ করুন"</string>
     <string name="searchview_description_submit" msgid="6771060386117334686">"ক্যোয়ারী জমা দিন"</string>
@@ -1401,7 +1401,7 @@
     <string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"জুম নিয়ন্ত্রণের জন্য দুবার ট্যাপ করুন"</string>
     <string name="gadget_host_error_inflating" msgid="2449961590495198720">"উইজেট যোগ করা যায়নি৷"</string>
     <string name="ime_action_go" msgid="5536744546326495436">"যান"</string>
-    <string name="ime_action_search" msgid="4501435960587287668">"খুঁজুন"</string>
+    <string name="ime_action_search" msgid="4501435960587287668">"সার্চ"</string>
     <string name="ime_action_send" msgid="8456843745664334138">"পাঠান"</string>
     <string name="ime_action_next" msgid="4169702997635728543">"পরবর্তী"</string>
     <string name="ime_action_done" msgid="6299921014822891569">"সম্পন্ন হয়েছে"</string>
@@ -1881,7 +1881,7 @@
     <string name="language_picker_section_suggested" msgid="6556199184638990447">"প্রস্তাবিত"</string>
     <string name="language_picker_section_all" msgid="1985809075777564284">"সকল ভাষা"</string>
     <string name="region_picker_section_all" msgid="756441309928774155">"সমস্ত অঞ্চল"</string>
-    <string name="locale_search_menu" msgid="6258090710176422934">"খুঁজুন"</string>
+    <string name="locale_search_menu" msgid="6258090710176422934">"সার্চ"</string>
     <string name="app_suspended_title" msgid="888873445010322650">"অ্যাপটি উপলভ্য নয়"</string>
     <string name="app_suspended_default_message" msgid="6451215678552004172">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> এখন উপলভ্য নয়। এই অ্যাপটিকে <xliff:g id="APP_NAME_1">%2$s</xliff:g> অ্যাপ ম্যানেজ করে।"</string>
     <string name="app_suspended_more_details" msgid="211260942831587014">"আরও জানুন"</string>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index ca35a68..5ffc420 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -985,7 +985,7 @@
     <string name="menu_delete_shortcut_label" msgid="4365787714477739080">"ડિલીટ કરો"</string>
     <string name="search_go" msgid="2141477624421347086">"શોધો"</string>
     <string name="search_hint" msgid="455364685740251925">"શોધો…"</string>
-    <string name="searchview_description_search" msgid="1045552007537359343">"શોધ"</string>
+    <string name="searchview_description_search" msgid="1045552007537359343">"શોધો"</string>
     <string name="searchview_description_query" msgid="7430242366971716338">"શોધ ક્વેરી"</string>
     <string name="searchview_description_clear" msgid="1989371719192982900">"ક્વેરી સાફ કરો"</string>
     <string name="searchview_description_submit" msgid="6771060386117334686">"ક્વેરી સબમિટ કરો"</string>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index cbb9ca2..c7786ae 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -983,9 +983,9 @@
     <string name="menu_space_shortcut_label" msgid="5949311515646872071">"space"</string>
     <string name="menu_enter_shortcut_label" msgid="6709499510082897320">"enter"</string>
     <string name="menu_delete_shortcut_label" msgid="4365787714477739080">"ಅಳಿಸಿ"</string>
-    <string name="search_go" msgid="2141477624421347086">"ಹುಡುಕಿ"</string>
+    <string name="search_go" msgid="2141477624421347086">"Search"</string>
     <string name="search_hint" msgid="455364685740251925">"ಹುಡುಕಿ…"</string>
-    <string name="searchview_description_search" msgid="1045552007537359343">"ಹುಡುಕಿ"</string>
+    <string name="searchview_description_search" msgid="1045552007537359343">"Search"</string>
     <string name="searchview_description_query" msgid="7430242366971716338">"ಪ್ರಶ್ನೆಯನ್ನು ಹುಡುಕಿ"</string>
     <string name="searchview_description_clear" msgid="1989371719192982900">"ಪ್ರಶ್ನೆಯನ್ನು ತೆರವುಗೊಳಿಸು"</string>
     <string name="searchview_description_submit" msgid="6771060386117334686">"ಪ್ರಶ್ನೆಯನ್ನು ಸಲ್ಲಿಸು"</string>
@@ -1401,7 +1401,7 @@
     <string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"ಝೂಮ್‌ ನಿಯಂತ್ರಿಸಲು ಎರಡು ಬಾರಿ ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
     <string name="gadget_host_error_inflating" msgid="2449961590495198720">"ವಿಜೆಟ್ ಸೇರಿಸಲು ಸಾಧ್ಯವಾಗುತ್ತಿಲ್ಲ."</string>
     <string name="ime_action_go" msgid="5536744546326495436">"ಹೋಗು"</string>
-    <string name="ime_action_search" msgid="4501435960587287668">"ಹುಡುಕಿ"</string>
+    <string name="ime_action_search" msgid="4501435960587287668">"Search"</string>
     <string name="ime_action_send" msgid="8456843745664334138">"ಕಳುಹಿಸು"</string>
     <string name="ime_action_next" msgid="4169702997635728543">"ಮುಂದೆ"</string>
     <string name="ime_action_done" msgid="6299921014822891569">"ಮುಗಿದಿದೆ"</string>
@@ -1881,7 +1881,7 @@
     <string name="language_picker_section_suggested" msgid="6556199184638990447">"ಸೂಚಿತ ಭಾಷೆ"</string>
     <string name="language_picker_section_all" msgid="1985809075777564284">"ಎಲ್ಲಾ ಭಾಷೆಗಳು"</string>
     <string name="region_picker_section_all" msgid="756441309928774155">"ಎಲ್ಲಾ ಪ್ರದೇಶಗಳು"</string>
-    <string name="locale_search_menu" msgid="6258090710176422934">"ಹುಡುಕಿ"</string>
+    <string name="locale_search_menu" msgid="6258090710176422934">"Search"</string>
     <string name="app_suspended_title" msgid="888873445010322650">"ಅಪ್ಲಿಕೇಶನ್ ಲಭ್ಯವಿಲ್ಲ"</string>
     <string name="app_suspended_default_message" msgid="6451215678552004172">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> ಅಪ್ಲಿಕೇಶನ್‌ ಸದ್ಯಕ್ಕೆ ಲಭ್ಯವಿಲ್ಲ. ಇದನ್ನು <xliff:g id="APP_NAME_1">%2$s</xliff:g> ನಲ್ಲಿ ನಿರ್ವಹಿಸಲಾಗುತ್ತಿದೆ."</string>
     <string name="app_suspended_more_details" msgid="211260942831587014">"ಇನ್ನಷ್ಟು ತಿಳಿಯಿರಿ"</string>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index af75969..12c0cb4 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -983,9 +983,9 @@
     <string name="menu_space_shortcut_label" msgid="5949311515646872071">"space"</string>
     <string name="menu_enter_shortcut_label" msgid="6709499510082897320">"enter"</string>
     <string name="menu_delete_shortcut_label" msgid="4365787714477739080">"delete"</string>
-    <string name="search_go" msgid="2141477624421347086">"തിരയൽ"</string>
+    <string name="search_go" msgid="2141477624421347086">"Search"</string>
     <string name="search_hint" msgid="455364685740251925">"തിരയുക…"</string>
-    <string name="searchview_description_search" msgid="1045552007537359343">"തിരയൽ"</string>
+    <string name="searchview_description_search" msgid="1045552007537359343">"Search"</string>
     <string name="searchview_description_query" msgid="7430242366971716338">"തിരയൽ അന്വേഷണം"</string>
     <string name="searchview_description_clear" msgid="1989371719192982900">"അന്വേഷണം മായ്‌ക്കുക"</string>
     <string name="searchview_description_submit" msgid="6771060386117334686">"ചോദ്യം സമർപ്പിക്കുക"</string>
@@ -1401,7 +1401,7 @@
     <string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"സൂം നിയന്ത്രണം ലഭിക്കാൻ രണ്ടുതവണ ടാപ്പുചെയ്യുക"</string>
     <string name="gadget_host_error_inflating" msgid="2449961590495198720">"വിജറ്റ് ചേർക്കാനായില്ല."</string>
     <string name="ime_action_go" msgid="5536744546326495436">"പോവുക"</string>
-    <string name="ime_action_search" msgid="4501435960587287668">"തിരയൽ"</string>
+    <string name="ime_action_search" msgid="4501435960587287668">"Search"</string>
     <string name="ime_action_send" msgid="8456843745664334138">"അയയ്‌ക്കുക"</string>
     <string name="ime_action_next" msgid="4169702997635728543">"അടുത്തത്"</string>
     <string name="ime_action_done" msgid="6299921014822891569">"പൂർത്തിയായി"</string>
@@ -1881,7 +1881,7 @@
     <string name="language_picker_section_suggested" msgid="6556199184638990447">"നിര്‍‌ദ്ദേശിച്ചത്"</string>
     <string name="language_picker_section_all" msgid="1985809075777564284">"എല്ലാ ഭാഷകളും"</string>
     <string name="region_picker_section_all" msgid="756441309928774155">"എല്ലാ പ്രദേശങ്ങളും"</string>
-    <string name="locale_search_menu" msgid="6258090710176422934">"തിരയുക"</string>
+    <string name="locale_search_menu" msgid="6258090710176422934">"Search"</string>
     <string name="app_suspended_title" msgid="888873445010322650">"ആപ്പ് ലഭ്യമല്ല"</string>
     <string name="app_suspended_default_message" msgid="6451215678552004172">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> ഇപ്പോൾ ലഭ്യമല്ല. <xliff:g id="APP_NAME_1">%2$s</xliff:g> ആണ് ഇത് മാനേജ് ചെയ്യുന്നത്."</string>
     <string name="app_suspended_more_details" msgid="211260942831587014">"കൂടുതലറിയുക"</string>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index ec240c1..da6f2b7 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -983,9 +983,9 @@
     <string name="menu_space_shortcut_label" msgid="5949311515646872071">"स्पेस"</string>
     <string name="menu_enter_shortcut_label" msgid="6709499510082897320">"एंटर"</string>
     <string name="menu_delete_shortcut_label" msgid="4365787714477739080">"हटवा"</string>
-    <string name="search_go" msgid="2141477624421347086">"शोध"</string>
+    <string name="search_go" msgid="2141477624421347086">"Search"</string>
     <string name="search_hint" msgid="455364685740251925">"शोधा…"</string>
-    <string name="searchview_description_search" msgid="1045552007537359343">"शोध"</string>
+    <string name="searchview_description_search" msgid="1045552007537359343">"Search"</string>
     <string name="searchview_description_query" msgid="7430242366971716338">"शोध क्वेरी"</string>
     <string name="searchview_description_clear" msgid="1989371719192982900">"क्‍वेरी साफ करा"</string>
     <string name="searchview_description_submit" msgid="6771060386117334686">"क्वेरी सबमिट करा"</string>
@@ -1401,7 +1401,7 @@
     <string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"झूम नियंत्रणासाठी दोनदा टॅप करा"</string>
     <string name="gadget_host_error_inflating" msgid="2449961590495198720">"विजेट जोडू शकलो नाही."</string>
     <string name="ime_action_go" msgid="5536744546326495436">"जा"</string>
-    <string name="ime_action_search" msgid="4501435960587287668">"शोधा"</string>
+    <string name="ime_action_search" msgid="4501435960587287668">"Search"</string>
     <string name="ime_action_send" msgid="8456843745664334138">"पाठवा"</string>
     <string name="ime_action_next" msgid="4169702997635728543">"पुढे"</string>
     <string name="ime_action_done" msgid="6299921014822891569">"पूर्ण झाले"</string>
@@ -1881,7 +1881,7 @@
     <string name="language_picker_section_suggested" msgid="6556199184638990447">"सुचवलेल्या भाषा"</string>
     <string name="language_picker_section_all" msgid="1985809075777564284">"सर्व भाषा"</string>
     <string name="region_picker_section_all" msgid="756441309928774155">"सर्व प्रदेश"</string>
-    <string name="locale_search_menu" msgid="6258090710176422934">"शोध"</string>
+    <string name="locale_search_menu" msgid="6258090710176422934">"Search"</string>
     <string name="app_suspended_title" msgid="888873445010322650">"अ‍ॅप उपलब्ध नाही"</string>
     <string name="app_suspended_default_message" msgid="6451215678552004172">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> आत्ता उपलब्ध नाही. हे <xliff:g id="APP_NAME_1">%2$s</xliff:g> कडून व्यवस्थापित केले जाते."</string>
     <string name="app_suspended_more_details" msgid="211260942831587014">"अधिक जाणून घ्या"</string>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index a1fd3f0..1cb01ea 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -983,9 +983,9 @@
     <string name="menu_space_shortcut_label" msgid="5949311515646872071">"ସ୍ପେସ୍‍"</string>
     <string name="menu_enter_shortcut_label" msgid="6709499510082897320">"ଏଣ୍ଟର୍"</string>
     <string name="menu_delete_shortcut_label" msgid="4365787714477739080">"ଡିଲିଟ୍‍"</string>
-    <string name="search_go" msgid="2141477624421347086">"ସର୍ଚ୍ଚ କରନ୍ତୁ"</string>
+    <string name="search_go" msgid="2141477624421347086">"Search"</string>
     <string name="search_hint" msgid="455364685740251925">"ସର୍ଚ୍ଚ…"</string>
-    <string name="searchview_description_search" msgid="1045552007537359343">"ସର୍ଚ୍ଚ କରନ୍ତୁ"</string>
+    <string name="searchview_description_search" msgid="1045552007537359343">"Search"</string>
     <string name="searchview_description_query" msgid="7430242366971716338">"କ୍ୱେରୀ ସର୍ଚ୍ଚ କରନ୍ତୁ"</string>
     <string name="searchview_description_clear" msgid="1989371719192982900">"କ୍ୱେରୀ ଖାଲି କରନ୍ତୁ"</string>
     <string name="searchview_description_submit" msgid="6771060386117334686">"କ୍ୱେରୀ ଦାଖଲ କରନ୍ତୁ"</string>
@@ -1401,7 +1401,7 @@
     <string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"ଜୁମ୍ ନିୟନ୍ତ୍ରଣ ପାଇଁ ଦୁଇଥର ଟାପ୍‌ କରନ୍ତୁ"</string>
     <string name="gadget_host_error_inflating" msgid="2449961590495198720">"ୱିଜେଟ୍‍ ଯୋଡ଼ିପାରିବ ନାହିଁ।"</string>
     <string name="ime_action_go" msgid="5536744546326495436">"ଯାଆନ୍ତୁ"</string>
-    <string name="ime_action_search" msgid="4501435960587287668">"ସର୍ଚ୍ଚ କରନ୍ତୁ"</string>
+    <string name="ime_action_search" msgid="4501435960587287668">"Search"</string>
     <string name="ime_action_send" msgid="8456843745664334138">"ପଠାନ୍ତୁ"</string>
     <string name="ime_action_next" msgid="4169702997635728543">"ପରବର୍ତ୍ତୀ"</string>
     <string name="ime_action_done" msgid="6299921014822891569">"ହୋଇଗଲା"</string>
@@ -1881,7 +1881,7 @@
     <string name="language_picker_section_suggested" msgid="6556199184638990447">"ପ୍ରସ୍ତାବିତ"</string>
     <string name="language_picker_section_all" msgid="1985809075777564284">"ସମସ୍ତ ଭାଷା"</string>
     <string name="region_picker_section_all" msgid="756441309928774155">"ସମସ୍ତ ଅଞ୍ଚଳ"</string>
-    <string name="locale_search_menu" msgid="6258090710176422934">"ସର୍ଚ୍ଚ କରନ୍ତୁ"</string>
+    <string name="locale_search_menu" msgid="6258090710176422934">"Search"</string>
     <string name="app_suspended_title" msgid="888873445010322650">"ଆପ୍‌ ଉପଲବ୍ଧ ନାହିଁ"</string>
     <string name="app_suspended_default_message" msgid="6451215678552004172">"ବର୍ତ୍ତମାନ <xliff:g id="APP_NAME_0">%1$s</xliff:g> ଉପଲବ୍ଧ ନାହିଁ। ଏହା <xliff:g id="APP_NAME_1">%2$s</xliff:g> ଦ୍ଵାରା ପରିଚାଳିତ ହେଉଛି।"</string>
     <string name="app_suspended_more_details" msgid="211260942831587014">"ଅଧିକ ଜାଣନ୍ତୁ"</string>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index 3e7ca92..37a995a5 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -754,7 +754,7 @@
     <string name="phoneTypeFaxHome" msgid="6678559953115904345">"Faks shtëpie"</string>
     <string name="phoneTypePager" msgid="576402072263522767">"Biper"</string>
     <string name="phoneTypeOther" msgid="6918196243648754715">"Tjetër"</string>
-    <string name="phoneTypeCallback" msgid="3455781500844157767">"Ri-telefono"</string>
+    <string name="phoneTypeCallback" msgid="3455781500844157767">"Kthim telefonate"</string>
     <string name="phoneTypeCar" msgid="4604775148963129195">"Numri i telefonit të makinës"</string>
     <string name="phoneTypeCompanyMain" msgid="4482773154536455441">"Numri kryesor i telefonit të kompanisë"</string>
     <string name="phoneTypeIsdn" msgid="2496238954533998512">"ISDN"</string>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index 854427b..6b9caf1 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -983,9 +983,9 @@
     <string name="menu_space_shortcut_label" msgid="5949311515646872071">"space"</string>
     <string name="menu_enter_shortcut_label" msgid="6709499510082897320">"enter"</string>
     <string name="menu_delete_shortcut_label" msgid="4365787714477739080">"delete"</string>
-    <string name="search_go" msgid="2141477624421347086">"వెతుకు"</string>
+    <string name="search_go" msgid="2141477624421347086">"సెర్చ్"</string>
     <string name="search_hint" msgid="455364685740251925">"వెతుకు..."</string>
-    <string name="searchview_description_search" msgid="1045552007537359343">"శోధించండి"</string>
+    <string name="searchview_description_search" msgid="1045552007537359343">"సెర్చ్"</string>
     <string name="searchview_description_query" msgid="7430242366971716338">"ప్రశ్నను శోధించండి"</string>
     <string name="searchview_description_clear" msgid="1989371719192982900">"ప్రశ్నను క్లియర్ చేయి"</string>
     <string name="searchview_description_submit" msgid="6771060386117334686">"ప్రశ్నని సమర్పించండి"</string>
@@ -1401,7 +1401,7 @@
     <string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"జూమ్ నియంత్రణ కోసం రెండుసార్లు నొక్కండి"</string>
     <string name="gadget_host_error_inflating" msgid="2449961590495198720">"విడ్జెట్‌ను జోడించడం సాధ్యపడలేదు."</string>
     <string name="ime_action_go" msgid="5536744546326495436">"వెళ్లు"</string>
-    <string name="ime_action_search" msgid="4501435960587287668">"వెతుకు"</string>
+    <string name="ime_action_search" msgid="4501435960587287668">"సెర్చ్"</string>
     <string name="ime_action_send" msgid="8456843745664334138">"పంపు"</string>
     <string name="ime_action_next" msgid="4169702997635728543">"తర్వాత"</string>
     <string name="ime_action_done" msgid="6299921014822891569">"పూర్తయింది"</string>
@@ -1881,7 +1881,7 @@
     <string name="language_picker_section_suggested" msgid="6556199184638990447">"సూచించినవి"</string>
     <string name="language_picker_section_all" msgid="1985809075777564284">"అన్ని భాషలు"</string>
     <string name="region_picker_section_all" msgid="756441309928774155">"అన్ని ప్రాంతాలు"</string>
-    <string name="locale_search_menu" msgid="6258090710176422934">"వెతుకు"</string>
+    <string name="locale_search_menu" msgid="6258090710176422934">"సెర్చ్"</string>
     <string name="app_suspended_title" msgid="888873445010322650">"యాప్ అందుబాటులో లేదు"</string>
     <string name="app_suspended_default_message" msgid="6451215678552004172">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> ప్రస్తుతం అందుబాటులో లేదు. ఇది <xliff:g id="APP_NAME_1">%2$s</xliff:g> ద్వారా నిర్వహించబడుతుంది."</string>
     <string name="app_suspended_more_details" msgid="211260942831587014">"మరింత తెలుసుకోండి"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 3cb264d..1899f8f 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -1131,10 +1131,10 @@
     <string name="whichViewApplication" msgid="5733194231473132945">"Mở bằng"</string>
     <string name="whichViewApplicationNamed" msgid="415164730629690105">"Mở bằng %1$s"</string>
     <string name="whichViewApplicationLabel" msgid="7367556735684742409">"Mở"</string>
-    <string name="whichOpenHostLinksWith" msgid="7645631470199397485">"Mở đường dẫn liên kết <xliff:g id="HOST">%1$s</xliff:g> bằng"</string>
-    <string name="whichOpenLinksWith" msgid="1120936181362907258">"Mở đường dẫn liên kết bằng"</string>
-    <string name="whichOpenLinksWithApp" msgid="6917864367861910086">"Mở đường dẫn liên kết bằng <xliff:g id="APPLICATION">%1$s</xliff:g>"</string>
-    <string name="whichOpenHostLinksWithApp" msgid="2401668560768463004">"Mở đường dẫn liên kết <xliff:g id="HOST">%1$s</xliff:g> bằng <xliff:g id="APPLICATION">%2$s</xliff:g>"</string>
+    <string name="whichOpenHostLinksWith" msgid="7645631470199397485">"Mở đường liên kết <xliff:g id="HOST">%1$s</xliff:g> bằng"</string>
+    <string name="whichOpenLinksWith" msgid="1120936181362907258">"Mở đường liên kết bằng"</string>
+    <string name="whichOpenLinksWithApp" msgid="6917864367861910086">"Mở đường liên kết bằng <xliff:g id="APPLICATION">%1$s</xliff:g>"</string>
+    <string name="whichOpenHostLinksWithApp" msgid="2401668560768463004">"Mở đường liên kết <xliff:g id="HOST">%1$s</xliff:g> bằng <xliff:g id="APPLICATION">%2$s</xliff:g>"</string>
     <string name="whichGiveAccessToApplicationLabel" msgid="7805857277166106236">"Cấp quyền truy cập"</string>
     <string name="whichEditApplication" msgid="6191568491456092812">"Chỉnh sửa bằng"</string>
     <string name="whichEditApplicationNamed" msgid="8096494987978521514">"Chỉnh sửa bằng %1$s"</string>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index f99be88..5f2e4f9 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4189,6 +4189,8 @@
     <string-array name="config_biometric_sensors" translatable="false" >
         <!-- <item>0:2:15</item>  ID0:Fingerprint:Strong -->
     </string-array>
+    <!--If true, allows the device to load udfps components on older HIDL implementations -->
+    <bool name="allow_test_udfps" translatable="false" >false</bool>
 
     <!-- Messages that should not be shown to the user during face auth enrollment. This should be
          used to hide messages that may be too chatty or messages that the user can't do much about.
@@ -4210,15 +4212,20 @@
     </integer-array>
 
     <!-- Messages that should not be shown to the user during face authentication, on
-     BiometricPrompt. This should be used to hide messages that may be too chatty or messages that
-     the user can't do much about. Entries are defined in
-     android.hardware.biometrics.face@1.0 types.hal -->
+         BiometricPrompt. This should be used to hide messages that may be too chatty or messages
+         that the user can't do much about. Entries are defined in
+         android.hardware.biometrics.face@1.0 types.hal -->
     <integer-array name="config_face_acquire_biometricprompt_ignorelist" translatable="false" >
     </integer-array>
     <!-- Same as the above, but are defined by vendorCodes -->
     <integer-array name="config_face_acquire_vendor_biometricprompt_ignorelist" translatable="false" >
     </integer-array>
 
+    <!-- True if the sensor is able to provide self illumination in dark secnarios, without  support
+         from above the HAL. This configuration is only applicable to IBiometricsFace@1.0 and its
+         minor revisions. -->
+    <bool name="config_faceAuthSupportsSelfIllumination">true</bool>
+
     <!-- If face auth sends the user directly to home/last open app, or stays on keyguard -->
     <bool name="config_faceAuthDismissesKeyguard">true</bool>
 
diff --git a/core/res/res/values/cross_profile_apps.xml b/core/res/res/values/cross_profile_apps.xml
index ab6f20d..3688c3e 100644
--- a/core/res/res/values/cross_profile_apps.xml
+++ b/core/res/res/values/cross_profile_apps.xml
@@ -17,7 +17,7 @@
 <resources>
     <!--
     A collection of apps that have been pre-approved for cross-profile communication.
-    These will not require admin consent, but will still require user consent during provisioning.
+    These will not require admin or user consent.
     -->
     <string-array translatable="false" name="cross_profile_apps">
     </string-array>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 040fad5..35ce780 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2506,6 +2506,7 @@
   <java-symbol type="string" name="face_error_security_update_required" />
 
   <java-symbol type="array" name="config_biometric_sensors" />
+  <java-symbol type="bool" name="allow_test_udfps" />
 
   <java-symbol type="array" name="config_face_acquire_enroll_ignorelist" />
   <java-symbol type="array" name="config_face_acquire_vendor_enroll_ignorelist" />
@@ -2513,6 +2514,7 @@
   <java-symbol type="array" name="config_face_acquire_vendor_keyguard_ignorelist" />
   <java-symbol type="array" name="config_face_acquire_biometricprompt_ignorelist" />
   <java-symbol type="array" name="config_face_acquire_vendor_biometricprompt_ignorelist" />
+  <java-symbol type="bool" name="config_faceAuthSupportsSelfIllumination" />
   <java-symbol type="bool" name="config_faceAuthDismissesKeyguard" />
 
   <!-- Face config -->
diff --git a/core/res/res/xml/power_profile.xml b/core/res/res/xml/power_profile.xml
index d8ec72f..166edca 100644
--- a/core/res/res/xml/power_profile.xml
+++ b/core/res/res/xml/power_profile.xml
@@ -20,7 +20,7 @@
 <device name="Android">
   <!-- Most values are the incremental current used by a feature,
        in mA (measured at nominal voltage).
-       The default values are deliberately incorrect dummy values.
+       The default values are deliberately incorrect values.
        OEM's must measure and provide actual values before
        shipping a device.
        Example real-world values are given in comments, but they
diff --git a/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java b/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
index 01515bd..a80f5a0 100644
--- a/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
@@ -52,7 +52,9 @@
 import android.provider.Settings;
 import android.support.test.uiautomator.UiDevice;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.DebugUtils;
+import android.util.KeyValueListParser;
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
@@ -104,8 +106,6 @@
 
     private static final int WORK_DURATION_MS = 2000;
 
-    private static final String DESIRED_PROC_STATE_CPU_TIMES_DELAY = "0";
-
     private static boolean sBatteryStatsConstsUpdated;
     private static String sOriginalBatteryStatsConsts;
 
@@ -124,10 +124,12 @@
         sContext.getPackageManager().setApplicationEnabledSetting(TEST_PKG,
                 PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
         sTestPkgUid = sContext.getPackageManager().getPackageUid(TEST_PKG, 0);
+
+        final ArrayMap<String, String> desiredConstants = new ArrayMap<>();
+        desiredConstants.put(KEY_TRACK_CPU_TIMES_BY_PROC_STATE, Boolean.toString(true));
+        desiredConstants.put(KEY_PROC_STATE_CPU_TIMES_READ_DELAY_MS, Integer.toString(0));
+        updateBatteryStatsConstants(desiredConstants);
         checkCpuTimesAvailability();
-        if (sPerProcStateTimesAvailable && sCpuFreqTimesAvailable) {
-            setDesiredReadyDelay();
-        }
     }
 
     @AfterClass
@@ -139,26 +141,33 @@
         batteryReset();
     }
 
-    private static void setDesiredReadyDelay() {
+    private static void updateBatteryStatsConstants(ArrayMap<String, String> desiredConstants) {
         sOriginalBatteryStatsConsts = Settings.Global.getString(sContext.getContentResolver(),
                 Settings.Global.BATTERY_STATS_CONSTANTS);
-        String newBatteryStatsConstants;
-        final String newConstant = KEY_PROC_STATE_CPU_TIMES_READ_DELAY_MS
-                + "=" + DESIRED_PROC_STATE_CPU_TIMES_DELAY;
-        if (sOriginalBatteryStatsConsts == null || "null".equals(sOriginalBatteryStatsConsts)) {
-            // battery_stats_constants is initially empty, so just assign the desired value.
-            newBatteryStatsConstants = newConstant;
-        } else if (sOriginalBatteryStatsConsts.contains(KEY_PROC_STATE_CPU_TIMES_READ_DELAY_MS)) {
-            // battery_stats_constants contains delay duration, so replace it
-            // with the desired value.
-            newBatteryStatsConstants = sOriginalBatteryStatsConsts.replaceAll(
-                    KEY_PROC_STATE_CPU_TIMES_READ_DELAY_MS + "=\\d+", newConstant);
-        } else {
-            // battery_stats_constants didn't contain any delay, so append the desired value.
-            newBatteryStatsConstants = sOriginalBatteryStatsConsts + "," + newConstant;
+        final char delimiter = ',';
+        final KeyValueListParser parser = new KeyValueListParser(delimiter);
+        parser.setString(sOriginalBatteryStatsConsts);
+        final StringBuilder sb = new StringBuilder();
+        for (int i = 0, size = parser.size(); i < size; ++i) {
+            final String key = parser.keyAt(i);
+            final String value = desiredConstants.getOrDefault(key,
+                    parser.getString(key, null));
+            if (sb.length() > 0) {
+                sb.append(delimiter);
+            }
+            sb.append(key + "=" + value);
+            desiredConstants.remove(key);
         }
+        desiredConstants.forEach((key, value) -> {
+            if (sb.length() > 0) {
+                sb.append(delimiter);
+            }
+            sb.append(key + '=' + value);
+        });
         Settings.Global.putString(sContext.getContentResolver(),
-                Settings.Global.BATTERY_STATS_CONSTANTS, newBatteryStatsConstants);
+                Settings.Global.BATTERY_STATS_CONSTANTS, sb.toString());
+        Log.d(TAG, "Updated value of '" + Settings.Global.BATTERY_STATS_CONSTANTS + "': "
+                + sb.toString());
         sBatteryStatsConstsUpdated = true;
     }
 
diff --git a/data/etc/car/Android.bp b/data/etc/car/Android.bp
index 04007c9..d0e688d 100644
--- a/data/etc/car/Android.bp
+++ b/data/etc/car/Android.bp
@@ -17,126 +17,126 @@
 // Privapp permission whitelist files
 
 prebuilt_etc {
-    name: "privapp_whitelist_android.car.cluster.loggingrenderer",
+    name: "allowed_privapp_android.car.cluster.loggingrenderer",
     sub_dir: "permissions",
     src: "android.car.cluster.loggingrenderer.xml",
     filename_from_src: true,
 }
 
 prebuilt_etc {
-    name: "privapp_whitelist_android.car.cluster.sample",
+    name: "allowed_privapp_android.car.cluster.sample",
     sub_dir: "permissions",
     src: "android.car.cluster.sample.xml",
     filename_from_src: true,
 }
 
 prebuilt_etc {
-    name: "privapp_whitelist_android.car.cluster",
+    name: "allowed_privapp_android.car.cluster",
     sub_dir: "permissions",
     src: "android.car.cluster.xml",
     filename_from_src: true,
 }
 
 prebuilt_etc {
-    name: "privapp_whitelist_android.car.usb.handler",
+    name: "allowed_privapp_android.car.usb.handler",
     sub_dir: "permissions",
     src: "android.car.usb.handler.xml",
     filename_from_src: true,
 }
 
 prebuilt_etc {
-    name: "privapp_whitelist_com.android.car.carlauncher",
+    name: "allowed_privapp_com.android.car.carlauncher",
     sub_dir: "permissions",
     src: "com.android.car.carlauncher.xml",
     filename_from_src: true,
 }
 
 prebuilt_etc {
-    name: "privapp_whitelist_com.android.car.dialer",
+    name: "allowed_privapp_com.android.car.dialer",
     sub_dir: "permissions",
     src: "com.android.car.dialer.xml",
     filename_from_src: true,
 }
 
 prebuilt_etc {
-    name: "privapp_whitelist_com.android.car.hvac",
+    name: "allowed_privapp_com.android.car.hvac",
     sub_dir: "permissions",
     src: "com.android.car.hvac.xml",
     filename_from_src: true,
 }
 
 prebuilt_etc {
-    name: "privapp_whitelist_com.android.car.media",
+    name: "allowed_privapp_com.android.car.media",
     sub_dir: "permissions",
     src: "com.android.car.media.xml",
     filename_from_src: true,
 }
 
 prebuilt_etc {
-    name: "privapp_whitelist_com.android.car.notification",
+    name: "allowed_privapp_com.android.car.notification",
     sub_dir: "permissions",
     src: "com.android.car.notification.xml",
     filename_from_src: true,
 }
 
 prebuilt_etc {
-    name: "privapp_whitelist_com.android.car.radio",
+    name: "allowed_privapp_com.android.car.radio",
     sub_dir: "permissions",
     src: "com.android.car.radio.xml",
     filename_from_src: true,
 }
 
 prebuilt_etc {
-    name: "privapp_whitelist_com.android.car.secondaryhome",
+    name: "allowed_privapp_com.android.car.secondaryhome",
     sub_dir: "permissions",
     src: "com.android.car.secondaryhome.xml",
     filename_from_src: true,
 }
 
 prebuilt_etc {
-    name: "privapp_whitelist_com.android.car.settings",
+    name: "allowed_privapp_com.android.car.settings",
     sub_dir: "permissions",
     src: "com.android.car.settings.xml",
     filename_from_src: true,
 }
 
 prebuilt_etc {
-    name: "privapp_whitelist_com.android.car.themeplayground",
+    name: "allowed_privapp_com.android.car.themeplayground",
     sub_dir: "permissions",
     src: "com.android.car.themeplayground.xml",
     filename_from_src: true,
 }
 
 prebuilt_etc {
-    name: "privapp_whitelist_com.android.car",
+    name: "allowed_privapp_com.android.car",
     sub_dir: "permissions",
     src: "com.android.car.xml",
     filename_from_src: true,
 }
 
 prebuilt_etc {
-    name: "privapp_whitelist_com.android.car.bugreport",
+    name: "allowed_privapp_com.android.car.bugreport",
     sub_dir: "permissions",
     src: "com.android.car.bugreport.xml",
     filename_from_src: true,
 }
 
 prebuilt_etc {
-    name: "privapp_whitelist_com.android.car.companiondevicesupport",
+    name: "allowed_privapp_com.android.car.companiondevicesupport",
     sub_dir: "permissions",
     src: "com.android.car.companiondevicesupport.xml",
     filename_from_src: true,
 }
 
 prebuilt_etc {
-    name: "privapp_whitelist_com.google.android.car.kitchensink",
+    name: "allowed_privapp_com.google.android.car.kitchensink",
     sub_dir: "permissions",
     src: "com.google.android.car.kitchensink.xml",
     filename_from_src: true,
 }
 
 prebuilt_etc {
-    name: "privapp_whitelist_com.android.car.developeroptions",
+    name: "allowed_privapp_com.android.car.developeroptions",
     sub_dir: "permissions",
     src: "com.android.car.developeroptions.xml",
     filename_from_src: true,
@@ -144,14 +144,14 @@
 }
 
 prebuilt_etc {
-    name: "privapp_whitelist_com.android.car.floatingcardslauncher",
+    name: "allowed_privapp_com.android.car.floatingcardslauncher",
     sub_dir: "permissions",
     src: "com.android.car.floatingcardslauncher.xml",
     filename_from_src: true,
 }
 
 prebuilt_etc {
-    name: "privapp_whitelist_com.android.car.ui.paintbooth",
+    name: "allowed_privapp_com.android.car.ui.paintbooth",
     sub_dir: "permissions",
     src: "com.android.car.ui.paintbooth.xml",
     filename_from_src: true,
diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml
index ada8b00..06f1dae 100644
--- a/data/etc/com.android.systemui.xml
+++ b/data/etc/com.android.systemui.xml
@@ -35,6 +35,7 @@
         <permission name="android.permission.MANAGE_USERS"/>
         <permission name="android.permission.MASTER_CLEAR"/>
         <permission name="android.permission.MEDIA_CONTENT_CONTROL"/>
+        <permission name="android.permission.MODIFY_AUDIO_ROUTING" />
         <permission name="android.permission.MODIFY_DAY_NIGHT_MODE"/>
         <permission name="android.permission.MODIFY_PHONE_STATE"/>
         <permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
diff --git a/drm/java/android/drm/DrmManagerClient.java b/drm/java/android/drm/DrmManagerClient.java
index ba3ebdd..4ec752a 100644
--- a/drm/java/android/drm/DrmManagerClient.java
+++ b/drm/java/android/drm/DrmManagerClient.java
@@ -751,7 +751,7 @@
 
     /**
      * Removes all the rights information of every DRM plug-in (agent) associated with
-     * the DRM framework. Will be used during a master reset.
+     * the DRM framework.
      *
      * @return ERROR_NONE for success; ERROR_UNKNOWN for failure.
      */
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 28d7911..964640b 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -3111,7 +3111,7 @@
     @CriticalNative
     private static native boolean nGetFillPath(long paintPtr, long src, long dst);
     @CriticalNative
-    private static native long nSetShader(long paintPtr, long shader);
+    private static native void nSetShader(long paintPtr, long shader);
     @CriticalNative
     private static native long nSetColorFilter(long paintPtr, long filter);
     @CriticalNative
diff --git a/libs/hostgraphics/Android.bp b/libs/hostgraphics/Android.bp
index e713b98..9d83e49 100644
--- a/libs/hostgraphics/Android.bp
+++ b/libs/hostgraphics/Android.bp
@@ -5,6 +5,10 @@
         "-Wno-unused-parameter",
     ],
 
+    static_libs: [
+        "libbase",
+    ],
+
     srcs: [
         ":libui_host_common",
         "Fence.cpp",
@@ -28,4 +32,4 @@
             enabled: true,
         }
     },
-}
\ No newline at end of file
+}
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 9ae5ad9..0b13754 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -462,6 +462,13 @@
         "RenderNode.cpp",
         "RenderProperties.cpp",
         "RootRenderNode.cpp",
+        "shader/Shader.cpp",
+        "shader/BitmapShader.cpp",
+        "shader/ComposeShader.cpp",
+        "shader/LinearGradientShader.cpp",
+        "shader/RadialGradientShader.cpp",
+        "shader/RuntimeShader.cpp",
+        "shader/SweepGradientShader.cpp",
         "SkiaCanvas.cpp",
         "VectorDrawable.cpp",
     ],
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 242b8b0..cfba5d4 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -42,6 +42,8 @@
 #include <SkTextBlob.h>
 #include <SkVertices.h>
 
+#include <shader/BitmapShader.h>
+
 #include <memory>
 #include <optional>
 #include <utility>
@@ -49,6 +51,7 @@
 namespace android {
 
 using uirenderer::PaintUtils;
+using uirenderer::BitmapShader;
 
 Canvas* Canvas::create_canvas(const SkBitmap& bitmap) {
     return new SkiaCanvas(bitmap);
@@ -681,7 +684,9 @@
     if (paint) {
         pnt = *paint;
     }
-    pnt.setShader(bitmap.makeImage()->makeShader());
+
+    pnt.setShader(sk_ref_sp(new BitmapShader(
+            bitmap.makeImage(), SkTileMode::kClamp, SkTileMode::kClamp, nullptr)));
     auto v = builder.detach();
     apply_looper(&pnt, [&](const SkPaint& p) {
         mCanvas->drawVertices(v, SkBlendMode::kModulate, p);
diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp
index cd908354..0f566e4 100644
--- a/libs/hwui/VectorDrawable.cpp
+++ b/libs/hwui/VectorDrawable.cpp
@@ -23,7 +23,6 @@
 #include "PathParser.h"
 #include "SkColorFilter.h"
 #include "SkImageInfo.h"
-#include "SkShader.h"
 #include "hwui/Paint.h"
 
 #ifdef __ANDROID__
@@ -159,10 +158,10 @@
 
     // Draw path's fill, if fill color or gradient is valid
     bool needsFill = false;
-    SkPaint paint;
+    Paint paint;
     if (properties.getFillGradient() != nullptr) {
         paint.setColor(applyAlpha(SK_ColorBLACK, properties.getFillAlpha()));
-        paint.setShader(sk_sp<SkShader>(SkSafeRef(properties.getFillGradient())));
+        paint.setShader(sk_sp<Shader>(SkSafeRef(properties.getFillGradient())));
         needsFill = true;
     } else if (properties.getFillColor() != SK_ColorTRANSPARENT) {
         paint.setColor(applyAlpha(properties.getFillColor(), properties.getFillAlpha()));
@@ -179,7 +178,7 @@
     bool needsStroke = false;
     if (properties.getStrokeGradient() != nullptr) {
         paint.setColor(applyAlpha(SK_ColorBLACK, properties.getStrokeAlpha()));
-        paint.setShader(sk_sp<SkShader>(SkSafeRef(properties.getStrokeGradient())));
+        paint.setShader(sk_sp<Shader>(SkSafeRef(properties.getStrokeGradient())));
         needsStroke = true;
     } else if (properties.getStrokeColor() != SK_ColorTRANSPARENT) {
         paint.setColor(applyAlpha(properties.getStrokeColor(), properties.getStrokeAlpha()));
diff --git a/libs/hwui/VectorDrawable.h b/libs/hwui/VectorDrawable.h
index ac7d41e..d4086f1 100644
--- a/libs/hwui/VectorDrawable.h
+++ b/libs/hwui/VectorDrawable.h
@@ -31,8 +31,8 @@
 #include <SkPath.h>
 #include <SkPathMeasure.h>
 #include <SkRect.h>
-#include <SkShader.h>
 #include <SkSurface.h>
+#include <shader/Shader.h>
 
 #include <cutils/compiler.h>
 #include <stddef.h>
@@ -227,20 +227,20 @@
             strokeGradient = prop.strokeGradient;
             onPropertyChanged();
         }
-        void setFillGradient(SkShader* gradient) {
+        void setFillGradient(Shader* gradient) {
             if (fillGradient.get() != gradient) {
                 fillGradient = sk_ref_sp(gradient);
                 onPropertyChanged();
             }
         }
-        void setStrokeGradient(SkShader* gradient) {
+        void setStrokeGradient(Shader* gradient) {
             if (strokeGradient.get() != gradient) {
                 strokeGradient = sk_ref_sp(gradient);
                 onPropertyChanged();
             }
         }
-        SkShader* getFillGradient() const { return fillGradient.get(); }
-        SkShader* getStrokeGradient() const { return strokeGradient.get(); }
+        Shader* getFillGradient() const { return fillGradient.get(); }
+        Shader* getStrokeGradient() const { return strokeGradient.get(); }
         float getStrokeWidth() const { return mPrimitiveFields.strokeWidth; }
         void setStrokeWidth(float strokeWidth) {
             VD_SET_PRIMITIVE_FIELD_AND_NOTIFY(strokeWidth, strokeWidth);
@@ -320,8 +320,8 @@
             count,
         };
         PrimitiveFields mPrimitiveFields;
-        sk_sp<SkShader> fillGradient;
-        sk_sp<SkShader> strokeGradient;
+        sk_sp<Shader> fillGradient;
+        sk_sp<Shader> strokeGradient;
     };
 
     // Called from UI thread
diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp
index c138a32..2a377bb 100644
--- a/libs/hwui/hwui/Canvas.cpp
+++ b/libs/hwui/hwui/Canvas.cpp
@@ -73,7 +73,7 @@
 
 static void simplifyPaint(int color, Paint* paint) {
     paint->setColor(color);
-    paint->setShader(nullptr);
+    paint->setShader((sk_sp<uirenderer::Shader>)nullptr);
     paint->setColorFilter(nullptr);
     paint->setLooper(nullptr);
     paint->setStrokeWidth(4 + 0.04 * paint->getSkFont().getSize());
diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h
index e75e9e7..0bb689c 100644
--- a/libs/hwui/hwui/Paint.h
+++ b/libs/hwui/hwui/Paint.h
@@ -30,6 +30,8 @@
 #include <minikin/FamilyVariant.h>
 #include <minikin/Hyphenator.h>
 
+#include <shader/Shader.h>
+
 namespace android {
 
 class Paint : public SkPaint {
@@ -149,8 +151,14 @@
     // The only respected flags are : [ antialias, dither, filterBitmap ]
     static uint32_t GetSkPaintJavaFlags(const SkPaint&);
     static void SetSkPaintJavaFlags(SkPaint*, uint32_t flags);
+
+    void setShader(sk_sp<uirenderer::Shader> shader);
  
 private:
+
+    using SkPaint::setShader;
+    using SkPaint::setImageFilter;
+
     SkFont mFont;
     sk_sp<SkDrawLooper> mLooper;
 
@@ -169,6 +177,7 @@
     bool mStrikeThru = false;
     bool mUnderline = false;
     bool mDevKern = false;
+    sk_sp<uirenderer::Shader> mShader;
 };
 
 }  // namespace android
diff --git a/libs/hwui/hwui/PaintImpl.cpp b/libs/hwui/hwui/PaintImpl.cpp
index fa2674f..21f60fd 100644
--- a/libs/hwui/hwui/PaintImpl.cpp
+++ b/libs/hwui/hwui/PaintImpl.cpp
@@ -24,7 +24,8 @@
         , mWordSpacing(0)
         , mFontFeatureSettings()
         , mMinikinLocaleListId(0)
-        , mFamilyVariant(minikin::FamilyVariant::DEFAULT) {
+        , mFamilyVariant(minikin::FamilyVariant::DEFAULT)
+        , mShader(nullptr) {
     // SkPaint::antialiasing defaults to false, but
     // SkFont::edging defaults to kAntiAlias. To keep them
     // insync, we manually set the font to kAilas.
@@ -45,7 +46,8 @@
         , mAlign(paint.mAlign)
         , mStrikeThru(paint.mStrikeThru)
         , mUnderline(paint.mUnderline)
-        , mDevKern(paint.mDevKern) {}
+        , mDevKern(paint.mDevKern)
+        , mShader(paint.mShader){}
 
 
 Paint::~Paint() {}
@@ -65,9 +67,30 @@
     mStrikeThru = other.mStrikeThru;
     mUnderline = other.mUnderline;
     mDevKern = other.mDevKern;
+    mShader = other.mShader;
     return *this;
 }
 
+void Paint::setShader(sk_sp<uirenderer::Shader> shader) {
+    if (shader) {
+        // If there is an SkShader compatible shader, apply it
+        sk_sp<SkShader> skShader = shader->asSkShader();
+        if (skShader.get()) {
+            SkPaint::setShader(skShader);
+            SkPaint::setImageFilter(nullptr);
+        } else {
+            // ... otherwise the specified shader can only be represented as an ImageFilter
+            SkPaint::setShader(nullptr);
+            SkPaint::setImageFilter(shader->asSkImageFilter());
+        }
+    } else {
+        // No shader is provided at all, clear out both the SkShader and SkImageFilter slots
+        SkPaint::setShader(nullptr);
+        SkPaint::setImageFilter(nullptr);
+    }
+    mShader = shader;
+}
+
 bool operator==(const Paint& a, const Paint& b) {
     return static_cast<const SkPaint&>(a) == static_cast<const SkPaint&>(b) &&
            a.mFont == b.mFont &&
diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp
index df8635a..554674a 100644
--- a/libs/hwui/jni/Paint.cpp
+++ b/libs/hwui/jni/Paint.cpp
@@ -47,6 +47,7 @@
 #include <minikin/LocaleList.h>
 #include <minikin/Measurement.h>
 #include <minikin/MinikinPaint.h>
+#include <shader/Shader.h>
 #include <unicode/utf16.h>
 
 #include <cassert>
@@ -54,6 +55,8 @@
 #include <memory>
 #include <vector>
 
+using namespace android::uirenderer;
+
 namespace android {
 
 struct JMetricsID {
@@ -782,11 +785,10 @@
         return obj->getFillPath(*src, dst) ? JNI_TRUE : JNI_FALSE;
     }
 
-    static jlong setShader(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jlong shaderHandle) {
-        Paint* obj = reinterpret_cast<Paint*>(objHandle);
-        SkShader* shader = reinterpret_cast<SkShader*>(shaderHandle);
-        obj->setShader(sk_ref_sp(shader));
-        return reinterpret_cast<jlong>(obj->getShader());
+    static void setShader(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jlong shaderHandle) {
+        auto* paint = reinterpret_cast<Paint*>(objHandle);
+        auto* shader = reinterpret_cast<Shader*>(shaderHandle);
+        paint->setShader(sk_ref_sp(shader));
     }
 
     static jlong setColorFilter(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jlong filterHandle) {
@@ -1097,7 +1099,7 @@
     {"nGetStrokeJoin","(J)I", (void*) PaintGlue::getStrokeJoin},
     {"nSetStrokeJoin","(JI)V", (void*) PaintGlue::setStrokeJoin},
     {"nGetFillPath","(JJJ)Z", (void*) PaintGlue::getFillPath},
-    {"nSetShader","(JJ)J", (void*) PaintGlue::setShader},
+    {"nSetShader","(JJ)V", (void*) PaintGlue::setShader},
     {"nSetColorFilter","(JJ)J", (void*) PaintGlue::setColorFilter},
     {"nSetXfermode","(JI)V", (void*) PaintGlue::setXfermode},
     {"nSetPathEffect","(JJ)J", (void*) PaintGlue::setPathEffect},
diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp
index e76aace..9b1972e 100644
--- a/libs/hwui/jni/Shader.cpp
+++ b/libs/hwui/jni/Shader.cpp
@@ -5,6 +5,13 @@
 #include "SkShader.h"
 #include "SkBlendMode.h"
 #include "include/effects/SkRuntimeEffect.h"
+#include "shader/Shader.h"
+#include "shader/BitmapShader.h"
+#include "shader/ComposeShader.h"
+#include "shader/LinearGradientShader.h"
+#include "shader/RadialGradientShader.h"
+#include "shader/RuntimeShader.h"
+#include "shader/SweepGradientShader.h"
 
 #include <vector>
 
@@ -50,7 +57,7 @@
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
 
-static void Shader_safeUnref(SkShader* shader) {
+static void Shader_safeUnref(Shader* shader) {
     SkSafeUnref(shader);
 }
 
@@ -74,15 +81,15 @@
         SkBitmap bitmap;
         image = SkMakeImageFromRasterBitmap(bitmap, kNever_SkCopyPixelsMode);
     }
-    sk_sp<SkShader> shader = image->makeShader(
-            (SkTileMode)tileModeX, (SkTileMode)tileModeY);
-    ThrowIAE_IfNull(env, shader.get());
 
-    if (matrix) {
-        shader = shader->makeWithLocalMatrix(*matrix);
-    }
+    auto* shader = new BitmapShader(
+            image,
+            static_cast<SkTileMode>(tileModeX),
+            static_cast<SkTileMode>(tileModeY),
+            matrix
+        );
 
-    return reinterpret_cast<jlong>(shader.release());
+    return reinterpret_cast<jlong>(shader);
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
@@ -118,17 +125,18 @@
     #error Need to convert float array to SkScalar array before calling the following function.
 #endif
 
-    sk_sp<SkShader> shader(SkGradientShader::MakeLinear(pts, &colors[0],
-                GraphicsJNI::getNativeColorSpace(colorSpaceHandle), pos, colors.size(),
-                static_cast<SkTileMode>(tileMode), sGradientShaderFlags, nullptr));
-    ThrowIAE_IfNull(env, shader);
+    auto* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
+    auto* shader = new LinearGradientShader(
+                pts,
+                colors,
+                GraphicsJNI::getNativeColorSpace(colorSpaceHandle),
+                pos,
+                static_cast<SkTileMode>(tileMode),
+                sGradientShaderFlags,
+                matrix
+            );
 
-    const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
-    if (matrix) {
-        shader = shader->makeWithLocalMatrix(*matrix);
-    }
-
-    return reinterpret_cast<jlong>(shader.release());
+    return reinterpret_cast<jlong>(shader);
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
@@ -148,17 +156,20 @@
     #error Need to convert float array to SkScalar array before calling the following function.
 #endif
 
-    sk_sp<SkShader> shader = SkGradientShader::MakeRadial(center, radius, &colors[0],
-            GraphicsJNI::getNativeColorSpace(colorSpaceHandle), pos, colors.size(),
-            static_cast<SkTileMode>(tileMode), sGradientShaderFlags, nullptr);
-    ThrowIAE_IfNull(env, shader);
+    auto* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
 
-    const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
-    if (matrix) {
-        shader = shader->makeWithLocalMatrix(*matrix);
-    }
+    auto* shader = new RadialGradientShader(
+                center,
+                radius,
+                colors,
+                GraphicsJNI::getNativeColorSpace(colorSpaceHandle),
+                pos,
+                static_cast<SkTileMode>(tileMode),
+                sGradientShaderFlags,
+                matrix
+            );
 
-    return reinterpret_cast<jlong>(shader.release());
+    return reinterpret_cast<jlong>(shader);
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -174,54 +185,58 @@
     #error Need to convert float array to SkScalar array before calling the following function.
 #endif
 
-    sk_sp<SkShader> shader = SkGradientShader::MakeSweep(x, y, &colors[0],
-            GraphicsJNI::getNativeColorSpace(colorSpaceHandle), pos, colors.size(),
-            sGradientShaderFlags, nullptr);
-    ThrowIAE_IfNull(env, shader);
+    auto* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
 
-    const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
-    if (matrix) {
-        shader = shader->makeWithLocalMatrix(*matrix);
-    }
+    auto* shader = new SweepGradientShader(
+                x,
+                y,
+                colors,
+                GraphicsJNI::getNativeColorSpace(colorSpaceHandle),
+                pos,
+                sGradientShaderFlags,
+                matrix
+            );
 
-    return reinterpret_cast<jlong>(shader.release());
+    return reinterpret_cast<jlong>(shader);
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
 
 static jlong ComposeShader_create(JNIEnv* env, jobject o, jlong matrixPtr,
         jlong shaderAHandle, jlong shaderBHandle, jint xfermodeHandle) {
-    const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
-    SkShader* shaderA = reinterpret_cast<SkShader *>(shaderAHandle);
-    SkShader* shaderB = reinterpret_cast<SkShader *>(shaderBHandle);
-    SkBlendMode mode = static_cast<SkBlendMode>(xfermodeHandle);
-    sk_sp<SkShader> baseShader(SkShaders::Blend(mode,
-            sk_ref_sp(shaderA), sk_ref_sp(shaderB)));
+    auto* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
+    auto* shaderA = reinterpret_cast<Shader*>(shaderAHandle);
+    auto* shaderB = reinterpret_cast<Shader*>(shaderBHandle);
 
-    SkShader* shader;
+    auto mode = static_cast<SkBlendMode>(xfermodeHandle);
 
-    if (matrix) {
-        shader = baseShader->makeWithLocalMatrix(*matrix).release();
-    } else {
-        shader = baseShader.release();
-    }
-    return reinterpret_cast<jlong>(shader);
+    auto* composeShader = new ComposeShader(
+            *shaderA,
+            *shaderB,
+            mode,
+            matrix
+        );
+
+    return reinterpret_cast<jlong>(composeShader);
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
 
 static jlong RuntimeShader_create(JNIEnv* env, jobject, jlong shaderFactory, jlong matrixPtr,
         jbyteArray inputs, jlong colorSpaceHandle, jboolean isOpaque) {
-    SkRuntimeEffect* effect = reinterpret_cast<SkRuntimeEffect*>(shaderFactory);
+    auto* effect = reinterpret_cast<SkRuntimeEffect*>(shaderFactory);
     AutoJavaByteArray arInputs(env, inputs);
 
-    sk_sp<SkData> fData;
-    fData = SkData::MakeWithCopy(arInputs.ptr(), arInputs.length());
-    const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
-    sk_sp<SkShader> shader = effect->makeShader(fData, nullptr, 0, matrix, isOpaque == JNI_TRUE);
-    ThrowIAE_IfNull(env, shader);
+    auto data = SkData::MakeWithCopy(arInputs.ptr(), arInputs.length());
+    auto* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
 
-    return reinterpret_cast<jlong>(shader.release());
+    auto* shader = new RuntimeShader(
+            *effect,
+            std::move(data),
+            isOpaque == JNI_TRUE,
+            matrix
+        );
+    return reinterpret_cast<jlong>(shader);
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
@@ -239,12 +254,8 @@
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
 
-static void Effect_safeUnref(SkRuntimeEffect* effect) {
-    SkSafeUnref(effect);
-}
-
 static jlong RuntimeShader_getNativeFinalizer(JNIEnv*, jobject) {
-    return static_cast<jlong>(reinterpret_cast<uintptr_t>(&Effect_safeUnref));
+    return static_cast<jlong>(reinterpret_cast<uintptr_t>(&Shader_safeUnref));
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp b/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp
index 9cffceb..a1adcb3 100644
--- a/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp
+++ b/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp
@@ -143,13 +143,13 @@
 
 static void updateFullPathFillGradient(JNIEnv*, jobject, jlong pathPtr, jlong fillGradientPtr) {
     VectorDrawable::FullPath* path = reinterpret_cast<VectorDrawable::FullPath*>(pathPtr);
-    SkShader* fillShader = reinterpret_cast<SkShader*>(fillGradientPtr);
+    auto* fillShader = reinterpret_cast<Shader*>(fillGradientPtr);
     path->mutateStagingProperties()->setFillGradient(fillShader);
 }
 
 static void updateFullPathStrokeGradient(JNIEnv*, jobject, jlong pathPtr, jlong strokeGradientPtr) {
     VectorDrawable::FullPath* path = reinterpret_cast<VectorDrawable::FullPath*>(pathPtr);
-    SkShader* strokeShader = reinterpret_cast<SkShader*>(strokeGradientPtr);
+    auto* strokeShader = reinterpret_cast<Shader*>(strokeGradientPtr);
     path->mutateStagingProperties()->setStrokeGradient(strokeShader);
 }
 
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index 2a8aa8c..a116781 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -238,12 +238,8 @@
     return config;
 }
 
-extern "C" EGLAPI const char* eglQueryStringImplementationANDROID(EGLDisplay dpy, EGLint name);
-
 void EglManager::initExtensions() {
     auto extensions = StringUtils::split(eglQueryString(mEglDisplay, EGL_EXTENSIONS));
-    auto extensionsAndroid =
-            StringUtils::split(eglQueryStringImplementationANDROID(mEglDisplay, EGL_EXTENSIONS));
 
     // For our purposes we don't care if EGL_BUFFER_AGE is a result of
     // EGL_EXT_buffer_age or EGL_KHR_partial_update as our usage is covered
@@ -265,10 +261,7 @@
     EglExtensions.surfacelessContext = extensions.has("EGL_KHR_surfaceless_context");
     EglExtensions.fenceSync = extensions.has("EGL_KHR_fence_sync");
     EglExtensions.waitSync = extensions.has("EGL_KHR_wait_sync");
-
-    // EGL_ANDROID_native_fence_sync is not exposed to applications, so access
-    // this through the private Android-specific query instead.
-    EglExtensions.nativeFenceSync = extensionsAndroid.has("EGL_ANDROID_native_fence_sync");
+    EglExtensions.nativeFenceSync = extensions.has("EGL_ANDROID_native_fence_sync");
 }
 
 bool EglManager::hasEglContext() {
diff --git a/libs/hwui/shader/BitmapShader.cpp b/libs/hwui/shader/BitmapShader.cpp
new file mode 100644
index 0000000..fe653e8
--- /dev/null
+++ b/libs/hwui/shader/BitmapShader.cpp
@@ -0,0 +1,31 @@
+/*
+ * 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 "BitmapShader.h"
+
+#include "SkImagePriv.h"
+
+namespace android::uirenderer {
+BitmapShader::BitmapShader(const sk_sp<SkImage>& image, const SkTileMode tileModeX,
+                           const SkTileMode tileModeY, const SkMatrix* matrix)
+        : Shader(matrix), skShader(image->makeShader(tileModeX, tileModeY)) {}
+
+sk_sp<SkShader> BitmapShader::makeSkShader() {
+    return skShader;
+}
+
+BitmapShader::~BitmapShader() {}
+}  // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/shader/BitmapShader.h b/libs/hwui/shader/BitmapShader.h
new file mode 100644
index 0000000..ed6a6e6
--- /dev/null
+++ b/libs/hwui/shader/BitmapShader.h
@@ -0,0 +1,39 @@
+/*
+ * 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 "Shader.h"
+#include "SkShader.h"
+
+namespace android::uirenderer {
+
+/**
+ * Shader implementation that renders a Bitmap as either a SkShader or SkImageFilter
+ */
+class BitmapShader : public Shader {
+public:
+    BitmapShader(const sk_sp<SkImage>& image, const SkTileMode tileModeX,
+                 const SkTileMode tileModeY, const SkMatrix* matrix);
+    ~BitmapShader() override;
+
+protected:
+    sk_sp<SkShader> makeSkShader() override;
+
+private:
+    sk_sp<SkShader> skShader;
+};
+}  // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/shader/ComposeShader.cpp b/libs/hwui/shader/ComposeShader.cpp
new file mode 100644
index 0000000..3765489
--- /dev/null
+++ b/libs/hwui/shader/ComposeShader.cpp
@@ -0,0 +1,51 @@
+/*
+ * 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 "ComposeShader.h"
+
+#include "SkImageFilters.h"
+#include "SkShader.h"
+
+namespace android::uirenderer {
+
+ComposeShader::ComposeShader(Shader& shaderA, Shader& shaderB, const SkBlendMode blendMode,
+                             const SkMatrix* matrix)
+        : Shader(matrix) {
+    // If both Shaders can be represented as SkShaders then use those, if not
+    // create an SkImageFilter from both Shaders and create the equivalent SkImageFilter
+    sk_sp<SkShader> skShaderA = shaderA.asSkShader();
+    sk_sp<SkShader> skShaderB = shaderB.asSkShader();
+    if (skShaderA.get() && skShaderB.get()) {
+        skShader = SkShaders::Blend(blendMode, skShaderA, skShaderB);
+        skImageFilter = nullptr;
+    } else {
+        sk_sp<SkImageFilter> skImageFilterA = shaderA.asSkImageFilter();
+        sk_sp<SkImageFilter> skImageFilterB = shaderB.asSkImageFilter();
+        skShader = nullptr;
+        skImageFilter = SkImageFilters::Xfermode(blendMode, skImageFilterA, skImageFilterB);
+    }
+}
+
+sk_sp<SkShader> ComposeShader::makeSkShader() {
+    return skShader;
+}
+
+sk_sp<SkImageFilter> ComposeShader::makeSkImageFilter() {
+    return skImageFilter;
+}
+
+ComposeShader::~ComposeShader() {}
+}  // namespace android::uirenderer
diff --git a/libs/hwui/shader/ComposeShader.h b/libs/hwui/shader/ComposeShader.h
new file mode 100644
index 0000000..a246b52
--- /dev/null
+++ b/libs/hwui/shader/ComposeShader.h
@@ -0,0 +1,43 @@
+/*
+ * 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 "Shader.h"
+#include "SkShader.h"
+
+namespace android::uirenderer {
+
+/**
+ * Shader implementation that can composite 2 Shaders together with the specified blend mode.
+ * This implementation can appropriately convert the composed result to either an SkShader or
+ * SkImageFilter depending on the inputs
+ */
+class ComposeShader : public Shader {
+public:
+    ComposeShader(Shader& shaderA, Shader& shaderB, const SkBlendMode blendMode,
+                  const SkMatrix* matrix);
+    ~ComposeShader() override;
+
+protected:
+    sk_sp<SkShader> makeSkShader() override;
+    sk_sp<SkImageFilter> makeSkImageFilter() override;
+
+private:
+    sk_sp<SkShader> skShader;
+    sk_sp<SkImageFilter> skImageFilter;
+};
+}  // namespace android::uirenderer
diff --git a/libs/hwui/shader/LinearGradientShader.cpp b/libs/hwui/shader/LinearGradientShader.cpp
new file mode 100644
index 0000000..868fa44
--- /dev/null
+++ b/libs/hwui/shader/LinearGradientShader.cpp
@@ -0,0 +1,39 @@
+/*
+ * 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 "LinearGradientShader.h"
+
+#include <vector>
+
+#include "SkGradientShader.h"
+
+namespace android::uirenderer {
+
+LinearGradientShader::LinearGradientShader(const SkPoint pts[2],
+                                           const std::vector<SkColor4f>& colors,
+                                           sk_sp<SkColorSpace> colorspace, const SkScalar pos[],
+                                           const SkTileMode tileMode, const uint32_t shaderFlags,
+                                           const SkMatrix* matrix)
+        : Shader(matrix)
+        , skShader(SkGradientShader::MakeLinear(pts, colors.data(), colorspace, pos, colors.size(),
+                                                tileMode, shaderFlags, nullptr)) {}
+
+sk_sp<SkShader> LinearGradientShader::makeSkShader() {
+    return skShader;
+}
+
+LinearGradientShader::~LinearGradientShader() {}
+}  // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/shader/LinearGradientShader.h b/libs/hwui/shader/LinearGradientShader.h
new file mode 100644
index 0000000..596f4e0
--- /dev/null
+++ b/libs/hwui/shader/LinearGradientShader.h
@@ -0,0 +1,42 @@
+/*
+ * 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 "Shader.h"
+#include "SkShader.h"
+
+namespace android::uirenderer {
+
+/**
+ * Shader implementation that renders a color ramp of colors to either as either SkShader or
+ * SkImageFilter
+ */
+class LinearGradientShader : public Shader {
+public:
+    LinearGradientShader(const SkPoint pts[2], const std::vector<SkColor4f>& colors,
+                         sk_sp<SkColorSpace> colorspace, const SkScalar pos[],
+                         const SkTileMode tileMode, const uint32_t shaderFlags,
+                         const SkMatrix* matrix);
+    ~LinearGradientShader() override;
+
+protected:
+    sk_sp<SkShader> makeSkShader() override;
+
+private:
+    sk_sp<SkShader> skShader;
+};
+}  // namespace android::uirenderer
diff --git a/libs/hwui/shader/RadialGradientShader.cpp b/libs/hwui/shader/RadialGradientShader.cpp
new file mode 100644
index 0000000..21ff56f
--- /dev/null
+++ b/libs/hwui/shader/RadialGradientShader.cpp
@@ -0,0 +1,38 @@
+/*
+ * 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 "RadialGradientShader.h"
+
+#include <vector>
+
+#include "SkGradientShader.h"
+
+namespace android::uirenderer {
+
+RadialGradientShader::RadialGradientShader(const SkPoint& center, const float radius,
+                                           const std::vector<SkColor4f>& colors,
+                                           sk_sp<SkColorSpace> colorspace, const SkScalar pos[],
+                                           const SkTileMode tileMode, const uint32_t shaderFlags,
+                                           const SkMatrix* matrix)
+        : Shader(matrix)
+        , skShader(SkGradientShader::MakeRadial(center, radius, colors.data(), colorspace, pos,
+                                                colors.size(), tileMode, shaderFlags, nullptr)) {}
+
+sk_sp<SkShader> RadialGradientShader::makeSkShader() {
+    return skShader;
+}
+
+RadialGradientShader::~RadialGradientShader() {}
+}  // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/shader/RadialGradientShader.h b/libs/hwui/shader/RadialGradientShader.h
new file mode 100644
index 0000000..9a2ff13
--- /dev/null
+++ b/libs/hwui/shader/RadialGradientShader.h
@@ -0,0 +1,42 @@
+/*
+ * 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 "Shader.h"
+#include "SkShader.h"
+
+namespace android::uirenderer {
+
+/**
+ * Shader implementation that renders a color ramp from the center outward to either as either
+ * a SkShader or SkImageFilter
+ */
+class RadialGradientShader : public Shader {
+public:
+    RadialGradientShader(const SkPoint& center, const float radius,
+                         const std::vector<SkColor4f>& colors, sk_sp<SkColorSpace> colorSpace,
+                         const SkScalar pos[], const SkTileMode tileMode, const uint32_t shaderFlags,
+                         const SkMatrix* matrix);
+    ~RadialGradientShader() override;
+
+protected:
+    sk_sp<SkShader> makeSkShader() override;
+
+private:
+    sk_sp<SkShader> skShader;
+};
+}  // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/shader/RuntimeShader.cpp b/libs/hwui/shader/RuntimeShader.cpp
new file mode 100644
index 0000000..dd0b698
--- /dev/null
+++ b/libs/hwui/shader/RuntimeShader.cpp
@@ -0,0 +1,36 @@
+/*
+ * 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 "RuntimeShader.h"
+
+#include "SkShader.h"
+#include "include/effects/SkRuntimeEffect.h"
+
+namespace android::uirenderer {
+
+RuntimeShader::RuntimeShader(SkRuntimeEffect& effect, sk_sp<SkData> data, bool isOpaque,
+                             const SkMatrix* matrix)
+        : Shader(nullptr)
+        ,  // Explicitly passing null as RuntimeShader is created with the
+           // matrix directly
+        skShader(effect.makeShader(std::move(data), nullptr, 0, matrix, isOpaque)) {}
+
+sk_sp<SkShader> RuntimeShader::makeSkShader() {
+    return skShader;
+}
+
+RuntimeShader::~RuntimeShader() {}
+}  // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/shader/RuntimeShader.h b/libs/hwui/shader/RuntimeShader.h
new file mode 100644
index 0000000..7fe0b02
--- /dev/null
+++ b/libs/hwui/shader/RuntimeShader.h
@@ -0,0 +1,40 @@
+/*
+ * 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 "Shader.h"
+#include "SkShader.h"
+#include "include/effects/SkRuntimeEffect.h"
+
+namespace android::uirenderer {
+
+/**
+ * RuntimeShader implementation that can map to either a SkShader or SkImageFilter
+ */
+class RuntimeShader : public Shader {
+public:
+    RuntimeShader(SkRuntimeEffect& effect, sk_sp<SkData> data, bool isOpaque,
+                  const SkMatrix* matrix);
+    ~RuntimeShader() override;
+
+protected:
+    sk_sp<SkShader> makeSkShader() override;
+
+private:
+    sk_sp<SkShader> skShader;
+};
+}  // namespace android::uirenderer
diff --git a/libs/hwui/shader/Shader.cpp b/libs/hwui/shader/Shader.cpp
new file mode 100644
index 0000000..45123dd
--- /dev/null
+++ b/libs/hwui/shader/Shader.cpp
@@ -0,0 +1,87 @@
+/*
+ * 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 "Shader.h"
+
+#include "SkImageFilters.h"
+#include "SkPaint.h"
+#include "SkRefCnt.h"
+
+namespace android::uirenderer {
+
+Shader::Shader(const SkMatrix* matrix)
+        : localMatrix(matrix ? *matrix : SkMatrix::I())
+        , skShader(nullptr)
+        , skImageFilter(nullptr) {}
+
+Shader::~Shader() {}
+
+sk_sp<SkShader> Shader::asSkShader() {
+    // If we already have created a shader with these parameters just return the existing
+    // shader we have already created
+    if (!this->skShader.get()) {
+        this->skShader = makeSkShader();
+        if (this->skShader.get()) {
+            if (!localMatrix.isIdentity()) {
+                this->skShader = this->skShader->makeWithLocalMatrix(localMatrix);
+            }
+        }
+    }
+    return this->skShader;
+}
+
+/**
+ * By default return null as we cannot convert all visual effects to SkShader instances
+ */
+sk_sp<SkShader> Shader::makeSkShader() {
+    return nullptr;
+}
+
+sk_sp<SkImageFilter> Shader::asSkImageFilter() {
+    // If we already have created an ImageFilter with these parameters just return the existing
+    // ImageFilter we have already created
+    if (!this->skImageFilter.get()) {
+        // Attempt to create an SkImageFilter from the current Shader implementation
+        this->skImageFilter = makeSkImageFilter();
+        if (this->skImageFilter) {
+            if (!localMatrix.isIdentity()) {
+                // If we have created an SkImageFilter and we have a transformation, wrap
+                // the created SkImageFilter to apply the given matrix
+                this->skImageFilter = SkImageFilters::MatrixTransform(
+                    localMatrix, kMedium_SkFilterQuality, this->skImageFilter);
+            }
+        } else {
+            // Otherwise if no SkImageFilter implementation is provided, create one from
+            // the result of asSkShader. Note the matrix is already applied to the shader in
+            // this case so just convert it to an SkImageFilter using SkImageFilters::Paint
+            SkPaint paint;
+            paint.setShader(asSkShader());
+            sk_sp<SkImageFilter> paintFilter = SkImageFilters::Paint(paint);
+            this->skImageFilter = SkImageFilters::Xfermode(SkBlendMode::kDstIn,
+                    std::move(paintFilter));
+        }
+    }
+    return this->skImageFilter;
+}
+
+/**
+ * By default return null for subclasses to implement. If there is not a direct SkImageFilter
+ * conversion
+ */
+sk_sp<SkImageFilter> Shader::makeSkImageFilter() {
+    return nullptr;
+}
+}  // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/shader/Shader.h b/libs/hwui/shader/Shader.h
new file mode 100644
index 0000000..3c0cdaa
--- /dev/null
+++ b/libs/hwui/shader/Shader.h
@@ -0,0 +1,82 @@
+/*
+ * 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 "SkImageFilter.h"
+#include "SkShader.h"
+#include "SkPaint.h"
+#include "SkRefCnt.h"
+
+class SkMatrix;
+
+namespace android::uirenderer {
+
+/**
+ * Shader class that can optionally wrap an SkShader or SkImageFilter depending
+ * on the implementation
+ */
+class Shader: public SkRefCnt {
+public:
+    /**
+     * Creates a Shader instance with an optional transformation matrix
+     * @param matrix Optional matrix to transform the underlying SkShader or SkImageFilter
+     */
+    Shader(const SkMatrix* matrix);
+    virtual ~Shader();
+
+    /**
+     * Create an SkShader from the current Shader instance or return a previously
+     * created instance. This can be null if no SkShader could be created from this
+     * Shader instance.
+     */
+    sk_sp<SkShader> asSkShader();
+
+    /**
+     * Create an SkImageFilter from the current Shader instance or return a previously
+     * created instance. Unlike asSkShader, this method cannot return null.
+     */
+    sk_sp<SkImageFilter> asSkImageFilter();
+
+protected:
+    /**
+     * Create a new SkShader instance based on this Shader instance
+     */
+    virtual sk_sp<SkShader> makeSkShader();
+
+    /**
+     * Create a new SkImageFilter instance based on this Shader instance. If no SkImageFilter
+     * can be created then return nullptr
+     */
+    virtual sk_sp<SkImageFilter> makeSkImageFilter();
+
+private:
+    /**
+     * Optional matrix transform
+     */
+    const SkMatrix localMatrix;
+
+    /**
+     * Cached SkShader instance to be returned on subsequent queries
+     */
+    sk_sp<SkShader> skShader;
+
+    /**
+     * Cached SkImageFilter instance to be returned on subsequent queries
+     */
+    sk_sp<SkImageFilter> skImageFilter;
+};
+}  // namespace android::uirenderer
diff --git a/libs/hwui/shader/SweepGradientShader.cpp b/libs/hwui/shader/SweepGradientShader.cpp
new file mode 100644
index 0000000..3b1f37f
--- /dev/null
+++ b/libs/hwui/shader/SweepGradientShader.cpp
@@ -0,0 +1,38 @@
+/*
+ * 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 "SweepGradientShader.h"
+
+#include <vector>
+
+#include "SkGradientShader.h"
+#include "SkImageFilters.h"
+
+namespace android::uirenderer {
+
+SweepGradientShader::SweepGradientShader(float x, float y, const std::vector<SkColor4f>& colors,
+                                         const sk_sp<SkColorSpace>& colorspace, const SkScalar pos[],
+                                         const uint32_t shaderFlags, const SkMatrix* matrix)
+        : Shader(matrix)
+        , skShader(SkGradientShader::MakeSweep(x, y, colors.data(), colorspace, pos, colors.size(),
+                                               shaderFlags, nullptr)) {}
+
+sk_sp<SkShader> SweepGradientShader::makeSkShader() {
+    return skShader;
+}
+
+SweepGradientShader::~SweepGradientShader() {}
+}  // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/shader/SweepGradientShader.h b/libs/hwui/shader/SweepGradientShader.h
new file mode 100644
index 0000000..dad3ef0
--- /dev/null
+++ b/libs/hwui/shader/SweepGradientShader.h
@@ -0,0 +1,41 @@
+/*
+ * 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 "Shader.h"
+#include "SkShader.h"
+
+namespace android::uirenderer {
+
+/**
+ * Shader implementation that renders a color ramp clockwise such that the start and end colors
+ * are visible at 3 o'clock. This handles converting to either an SkShader or SkImageFilter
+ */
+class SweepGradientShader : public Shader {
+public:
+    SweepGradientShader(float x, float y, const std::vector<SkColor4f>& colors,
+                        const sk_sp<SkColorSpace>& colorspace, const SkScalar pos[],
+                        const uint32_t shaderFlags, const SkMatrix* matrix);
+    virtual ~SweepGradientShader() override;
+
+protected:
+    virtual sk_sp<SkShader> makeSkShader() override;
+
+private:
+    sk_sp<SkShader> skShader;
+};
+}  // namespace android::uirenderer
diff --git a/libs/hwui/tests/common/scenes/BitmapShaders.cpp b/libs/hwui/tests/common/scenes/BitmapShaders.cpp
index c4067af..e2c1651 100644
--- a/libs/hwui/tests/common/scenes/BitmapShaders.cpp
+++ b/libs/hwui/tests/common/scenes/BitmapShaders.cpp
@@ -18,6 +18,7 @@
 #include "hwui/Paint.h"
 #include "TestSceneBase.h"
 #include "tests/common/BitmapAllocationTestUtils.h"
+#include <shader/BitmapShader.h>
 #include "utils/Color.h"
 
 class BitmapShaders;
@@ -45,15 +46,24 @@
                 });
 
         Paint paint;
+        sk_sp<BitmapShader> bitmapShader = sk_make_sp<BitmapShader>(
+                    hwuiBitmap->makeImage(),
+                    SkTileMode::kRepeat,
+                    SkTileMode::kRepeat,
+                    nullptr
+                );
+
         sk_sp<SkImage> image = hwuiBitmap->makeImage();
-        sk_sp<SkShader> repeatShader =
-                image->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat);
-        paint.setShader(std::move(repeatShader));
+        paint.setShader(std::move(bitmapShader));
         canvas.drawRoundRect(0, 0, 500, 500, 50.0f, 50.0f, paint);
 
-        sk_sp<SkShader> mirrorShader =
-                image->makeShader(SkTileMode::kMirror, SkTileMode::kMirror);
-        paint.setShader(std::move(mirrorShader));
+        sk_sp<BitmapShader> mirrorBitmapShader = sk_make_sp<BitmapShader>(
+                    image,
+                    SkTileMode::kMirror,
+                    SkTileMode::kMirror,
+                    nullptr
+                );
+        paint.setShader(std::move(mirrorBitmapShader));
         canvas.drawRoundRect(0, 600, 500, 1100, 50.0f, 50.0f, paint);
     }
 
diff --git a/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp b/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp
index 5886ea3..d37bc3c 100644
--- a/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp
+++ b/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp
@@ -20,6 +20,10 @@
 #include <SkGradientShader.h>
 #include <SkImagePriv.h>
 #include <ui/PixelFormat.h>
+#include <shader/BitmapShader.h>
+#include <shader/LinearGradientShader.h>
+#include <shader/RadialGradientShader.h>
+#include <shader/ComposeShader.h>
 
 class HwBitmapInCompositeShader;
 
@@ -50,20 +54,41 @@
             pixels[4000 + 4 * i + 3] = 255;
         }
         buffer->unlock();
-        sk_sp<Bitmap> hardwareBitmap(Bitmap::createFrom(buffer->toAHardwareBuffer(),
-                                                        SkColorSpace::MakeSRGB()));
-        sk_sp<SkShader> hardwareShader(createBitmapShader(*hardwareBitmap));
+
+        sk_sp<BitmapShader> bitmapShader = sk_make_sp<BitmapShader>(
+                Bitmap::createFrom(
+                        buffer->toAHardwareBuffer(),
+                        SkColorSpace::MakeSRGB()
+                )->makeImage(),
+                SkTileMode::kClamp,
+                SkTileMode::kClamp,
+                nullptr
+        );
 
         SkPoint center;
         center.set(50, 50);
-        SkColor colors[2];
-        colors[0] = Color::Black;
-        colors[1] = Color::White;
-        sk_sp<SkShader> gradientShader = SkGradientShader::MakeRadial(
-                center, 50, colors, nullptr, 2, SkTileMode::kRepeat);
 
-        sk_sp<SkShader> compositeShader(
-                SkShaders::Blend(SkBlendMode::kDstATop, hardwareShader, gradientShader));
+        std::vector<SkColor4f> vColors(2);
+        vColors[0] = SkColors::kBlack;
+        vColors[1] = SkColors::kWhite;
+
+        sk_sp<RadialGradientShader> radialShader = sk_make_sp<RadialGradientShader>(
+                center,
+                50,
+                vColors,
+                SkColorSpace::MakeSRGB(),
+                nullptr,
+                SkTileMode::kRepeat,
+                0,
+                nullptr
+            );
+
+        sk_sp<ComposeShader> compositeShader = sk_make_sp<ComposeShader>(
+                    *bitmapShader.get(),
+                    *radialShader.get(),
+                    SkBlendMode::kDstATop,
+                    nullptr
+                );
 
         Paint paint;
         paint.setShader(std::move(compositeShader));
diff --git a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp
index a9449b6..76e39de 100644
--- a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp
@@ -17,7 +17,8 @@
 #include "TestSceneBase.h"
 #include "tests/common/TestListViewSceneBase.h"
 #include "hwui/Paint.h"
-#include <SkGradientShader.h>
+#include "SkColor.h"
+#include <shader/LinearGradientShader.h>
 
 class ListOfFadedTextAnimation;
 
@@ -42,15 +43,26 @@
         pts[0].set(0, 0);
         pts[1].set(0, 1);
 
-        SkColor colors[2] = {Color::Black, Color::Transparent};
-        sk_sp<SkShader> s(
-                SkGradientShader::MakeLinear(pts, colors, NULL, 2, SkTileMode::kClamp));
-
         SkMatrix matrix;
         matrix.setScale(1, length);
         matrix.postRotate(-90);
+
+        std::vector<SkColor4f> vColors(2);
+        vColors[0] = SkColors::kBlack;
+        vColors[1] = SkColors::kTransparent;
+
+        sk_sp<LinearGradientShader> linearGradientShader = sk_make_sp<LinearGradientShader>(
+                    pts,
+                    vColors,
+                    SkColorSpace::MakeSRGB(),
+                    nullptr,
+                    SkTileMode::kClamp,
+                    0,
+                    &matrix
+                );
+
         Paint fadingPaint;
-        fadingPaint.setShader(s->makeWithLocalMatrix(matrix));
+        fadingPaint.setShader(linearGradientShader);
         fadingPaint.setBlendMode(SkBlendMode::kDstOut);
         canvas.drawRect(0, 0, length, itemHeight, fadingPaint);
         canvas.restore();
diff --git a/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp b/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp
index a0bc5aa..bdc157f 100644
--- a/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp
@@ -17,7 +17,7 @@
 #include "TestSceneBase.h"
 
 #include <SkColorMatrixFilter.h>
-#include <SkGradientShader.h>
+#include <shader/LinearGradientShader.h>
 
 class SimpleColorMatrixAnimation;
 
@@ -65,9 +65,12 @@
                     // enough renderer might apply it directly to the paint color)
                     float pos[] = {0, 1};
                     SkPoint pts[] = {SkPoint::Make(0, 0), SkPoint::Make(width, height)};
-                    SkColor colors[2] = {Color::DeepPurple_500, Color::DeepOrange_500};
-                    paint.setShader(SkGradientShader::MakeLinear(pts, colors, pos, 2,
-                                                                 SkTileMode::kClamp));
+                    std::vector<SkColor4f> colors(2);
+                    colors[0] = SkColor4f::FromColor(Color::DeepPurple_500);
+                    colors[1] = SkColor4f::FromColor(Color::DeepOrange_500);
+                    paint.setShader(sk_make_sp<LinearGradientShader>(
+                            pts, colors, SkColorSpace::MakeSRGB(), pos, SkTileMode::kClamp,
+                            0, nullptr));
 
                     // overdraw several times to emphasize shader cost
                     for (int i = 0; i < 10; i++) {
diff --git a/libs/hwui/tests/common/scenes/SimpleGradientAnimation.cpp b/libs/hwui/tests/common/scenes/SimpleGradientAnimation.cpp
index 57a260c..9a15c9d 100644
--- a/libs/hwui/tests/common/scenes/SimpleGradientAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/SimpleGradientAnimation.cpp
@@ -17,6 +17,7 @@
 #include "TestSceneBase.h"
 
 #include <SkGradientShader.h>
+#include <shader/LinearGradientShader.h>
 
 class SimpleGradientAnimation;
 
@@ -55,9 +56,24 @@
                     // overdraw several times to emphasize shader cost
                     for (int i = 0; i < 10; i++) {
                         // use i%2 start position to pick 2 color combo with black in it
-                        SkColor colors[3] = {Color::Transparent, Color::Black, Color::Cyan_500};
-                        paint.setShader(SkGradientShader::MakeLinear(pts, colors + (i % 2), pos, 2,
-                                                                     SkTileMode::kClamp));
+                        std::vector<SkColor4f> vColors(2);
+                        vColors[0] = ((i % 2) == 0) ?
+                                SkColor4f::FromColor(Color::Transparent) :
+                                SkColor4f::FromColor(Color::Black);
+                        vColors[1] = (((i + 1) % 2) == 0) ?
+                                SkColor4f::FromColor(Color::Black) :
+                                SkColor4f::FromColor(Color::Cyan_500);
+
+                        sk_sp<LinearGradientShader> gradient = sk_make_sp<LinearGradientShader>(
+                                    pts,
+                                    vColors,
+                                    SkColorSpace::MakeSRGB(),
+                                    pos,
+                                    SkTileMode::kClamp,
+                                    0,
+                                    nullptr
+                                );
+                        paint.setShader(gradient);
                         canvas.drawRect(i, i, width, height, paint);
                     }
                 });
diff --git a/libs/hwui/tests/unit/VectorDrawableTests.cpp b/libs/hwui/tests/unit/VectorDrawableTests.cpp
index 6d4c574..5e56b26 100644
--- a/libs/hwui/tests/unit/VectorDrawableTests.cpp
+++ b/libs/hwui/tests/unit/VectorDrawableTests.cpp
@@ -17,9 +17,14 @@
 #include <gtest/gtest.h>
 
 #include "PathParser.h"
+#include "GraphicsJNI.h"
+#include "SkGradientShader.h"
+#include "SkShader.h"
 #include "VectorDrawable.h"
 #include "utils/MathUtils.h"
 #include "utils/VectorDrawableUtils.h"
+#include <shader/Shader.h>
+#include <shader/LinearGradientShader.h>
 
 #include <functional>
 
@@ -395,7 +400,21 @@
     bitmap.allocN32Pixels(5, 5, false);
     SkCanvas canvas(bitmap);
 
-    sk_sp<SkShader> shader = SkShaders::Color(SK_ColorBLACK);
+    SkPoint pts[2];
+    pts[0].set(0, 0);
+    pts[1].set(0, 0);
+
+    std::vector<SkColor4f> colors(2);
+    colors[0] = SkColors::kBlack;
+    colors[1] = SkColors::kBlack;
+
+    sk_sp<LinearGradientShader> shader = sk_sp(new LinearGradientShader(pts,
+            colors,
+            SkColorSpace::MakeSRGB(),
+            nullptr,
+            SkTileMode::kClamp,
+            SkGradientShader::kInterpolateColorsInPremul_Flag,
+            nullptr));
     // Initial ref count is 1
     EXPECT_TRUE(shader->unique());
 
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index b2e0538..a16e063 100755
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -75,6 +75,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -1599,21 +1600,14 @@
     @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
     public boolean setPreferredDeviceForStrategy(@NonNull AudioProductStrategy strategy,
             @NonNull AudioDeviceAttributes device) {
-        Objects.requireNonNull(strategy);
-        Objects.requireNonNull(device);
-        try {
-            final int status =
-                    getService().setPreferredDeviceForStrategy(strategy.getId(), device);
-            return status == AudioSystem.SUCCESS;
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        return setPreferredDevicesForStrategy(strategy, Arrays.asList(device));
     }
 
     /**
      * @hide
-     * Removes the preferred audio device previously set with
-     * {@link #setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAttributes)}.
+     * Removes the preferred audio device(s) previously set with
+     * {@link #setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAttributes)} or
+     * {@link #setPreferredDevicesForStrategy(AudioProductStrategy, List<AudioDeviceAttributes>)}.
      * @param strategy the audio strategy whose routing will be affected
      * @return true if the operation was successful, false otherwise (invalid strategy, or no
      *     device set for example)
@@ -1624,7 +1618,7 @@
         Objects.requireNonNull(strategy);
         try {
             final int status =
-                    getService().removePreferredDeviceForStrategy(strategy.getId());
+                    getService().removePreferredDevicesForStrategy(strategy.getId());
             return status == AudioSystem.SUCCESS;
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -1634,18 +1628,74 @@
     /**
      * @hide
      * Return the preferred device for an audio strategy, previously set with
+     * {@link #setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAttributes)} or
+     * {@link #setPreferredDevicesForStrategy(AudioProductStrategy, List<AudioDeviceAttributes>)}
+     * @param strategy the strategy to query
+     * @return the preferred device for that strategy, if multiple devices are set as preferred
+     *    devices, the first one in the list will be returned. Null will be returned if none was
+     *    ever set or if the strategy is invalid
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    @Nullable
+    public AudioDeviceAttributes getPreferredDeviceForStrategy(
+            @NonNull AudioProductStrategy strategy) {
+        List<AudioDeviceAttributes> devices = getPreferredDevicesForStrategy(strategy);
+        return devices.isEmpty() ? null : devices.get(0);
+    }
+
+    /**
+     * @hide
+     * Set the preferred devices for a given strategy, i.e. the audio routing to be used by
+     * this audio strategy. Note that the devices may not be available at the time the preferred
+     * devices is set, but it will be used once made available.
+     * <p>Use {@link #removePreferredDeviceForStrategy(AudioProductStrategy)} to cancel setting
+     * this preference for this strategy.</p>
+     * Note that the list of devices is not a list ranked by preference, but a list of one or more
+     * devices used simultaneously to output the same audio signal.
+     * @param strategy the audio strategy whose routing will be affected
+     * @param devices a non-empty list of the audio devices to route to when available
+     * @return true if the operation was successful, false otherwise
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public boolean setPreferredDevicesForStrategy(@NonNull AudioProductStrategy strategy,
+                                                  @NonNull List<AudioDeviceAttributes> devices) {
+        Objects.requireNonNull(strategy);
+        Objects.requireNonNull(devices);
+        if (devices.isEmpty()) {
+            throw new IllegalArgumentException(
+                    "Tried to set preferred devices for strategy with a empty list");
+        }
+        for (AudioDeviceAttributes device : devices) {
+            Objects.requireNonNull(device);
+        }
+        try {
+            final int status =
+                    getService().setPreferredDevicesForStrategy(strategy.getId(), devices);
+            return status == AudioSystem.SUCCESS;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     * Return the preferred devices for an audio strategy, previously set with
      * {@link #setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAttributes)}
+     * {@link #setPreferredDevicesForStrategy(AudioProductStrategy, List<AudioDeviceAttributes>)}
      * @param strategy the strategy to query
      * @return the preferred device for that strategy, or null if none was ever set or if the
      *    strategy is invalid
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
-    public @Nullable AudioDeviceAttributes getPreferredDeviceForStrategy(
+    @NonNull
+    public List<AudioDeviceAttributes> getPreferredDevicesForStrategy(
             @NonNull AudioProductStrategy strategy) {
         Objects.requireNonNull(strategy);
         try {
-            return getService().getPreferredDeviceForStrategy(strategy.getId());
+            return getService().getPreferredDevicesForStrategy(strategy.getId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1657,6 +1707,7 @@
      * strategy.
      * <p>Note that this listener will only be invoked whenever
      * {@link #setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAttributes)} or
+     * {@link #setPreferredDevicesForStrategy(AudioProductStrategy, List<AudioDeviceAttributes>)}
      * {@link #removePreferredDeviceForStrategy(AudioProductStrategy)} causes a change in
      * preferred device. It will not be invoked directly after registration with
      * {@link #addOnPreferredDeviceForStrategyChangedListener(Executor, OnPreferredDeviceForStrategyChangedListener)}
@@ -1664,8 +1715,10 @@
      * @see #setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAttributes)
      * @see #removePreferredDeviceForStrategy(AudioProductStrategy)
      * @see #getPreferredDeviceForStrategy(AudioProductStrategy)
+     * @deprecated use #OnPreferredDevicesForStrategyChangedListener
      */
     @SystemApi
+    @Deprecated
     public interface OnPreferredDeviceForStrategyChangedListener {
         /**
          * Called on the listener to indicate that the preferred audio device for the given
@@ -1680,6 +1733,70 @@
 
     /**
      * @hide
+     * Interface to be notified of changes in the preferred audio devices set for a given audio
+     * strategy.
+     * <p>Note that this listener will only be invoked whenever
+     * {@link #setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAttributes)} or
+     * {@link #setPreferredDevicesForStrategy(AudioProductStrategy, List<AudioDeviceAttributes>)}
+     * {@link #removePreferredDeviceForStrategy(AudioProductStrategy)} causes a change in
+     * preferred device(s). It will not be invoked directly after registration with
+     * {@link #addOnPreferredDevicesForStrategyChangedListener(
+     * Executor, OnPreferredDevicesForStrategyChangedListener)}
+     * to indicate which strategies had preferred devices at the time of registration.</p>
+     * @see #setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAttributes)
+     * @see #setPreferredDevicesForStrategy(AudioProductStrategy, List)
+     * @see #removePreferredDeviceForStrategy(AudioProductStrategy)
+     * @see #getPreferredDeviceForStrategy(AudioProductStrategy)
+     * @see #getPreferredDevicesForStrategy(AudioProductStrategy)
+     */
+    @SystemApi
+    public interface OnPreferredDevicesForStrategyChangedListener {
+        /**
+         * Called on the listener to indicate that the preferred audio devices for the given
+         * strategy has changed.
+         * @param strategy the {@link AudioProductStrategy} whose preferred device changed
+         * @param devices a list of newly set preferred audio devices
+         */
+        void onPreferredDevicesForStrategyChanged(@NonNull AudioProductStrategy strategy,
+                                                  @NonNull List<AudioDeviceAttributes> devices);
+    }
+
+    /**
+     * @hide
+     * Adds a listener for being notified of changes to the strategy-preferred audio device.
+     * @param executor
+     * @param listener
+     * @throws SecurityException if the caller doesn't hold the required permission
+     * @deprecated use {@link #addOnPreferredDevicesForStrategyChangedListener(
+     *             Executor, AudioManager.OnPreferredDevicesForStrategyChangedListener)} instead
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    @Deprecated
+    public void addOnPreferredDeviceForStrategyChangedListener(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OnPreferredDeviceForStrategyChangedListener listener)
+            throws SecurityException {
+        // No-op, the method is deprecated.
+    }
+
+    /**
+     * @hide
+     * Removes a previously added listener of changes to the strategy-preferred audio device.
+     * @param listener
+     * @deprecated use {@link #removeOnPreferredDevicesForStrategyChangedListener(
+     *             AudioManager.OnPreferredDevicesForStrategyChangedListener)} instead
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    @Deprecated
+    public void removeOnPreferredDeviceForStrategyChangedListener(
+            @NonNull OnPreferredDeviceForStrategyChangedListener listener) {
+        // No-op, the method is deprecated.
+    }
+
+    /**
+     * @hide
      * Adds a listener for being notified of changes to the strategy-preferred audio device.
      * @param executor
      * @param listener
@@ -1687,16 +1804,16 @@
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
-    public void addOnPreferredDeviceForStrategyChangedListener(
+    public void addOnPreferredDevicesForStrategyChangedListener(
             @NonNull @CallbackExecutor Executor executor,
-            @NonNull OnPreferredDeviceForStrategyChangedListener listener)
+            @NonNull OnPreferredDevicesForStrategyChangedListener listener)
             throws SecurityException {
         Objects.requireNonNull(executor);
         Objects.requireNonNull(listener);
         synchronized (mPrefDevListenerLock) {
             if (hasPrefDevListener(listener)) {
                 throw new IllegalArgumentException(
-                        "attempt to call addOnPreferredDeviceForStrategyChangedListener() "
+                        "attempt to call addOnPreferredDevicesForStrategyChangedListener() "
                                 + "on a previously registered listener");
             }
             // lazy initialization of the list of strategy-preferred device listener
@@ -1708,10 +1825,10 @@
             if (oldCbCount == 0 && mPrefDevListeners.size() > 0) {
                 // register binder for callbacks
                 if (mPrefDevDispatcherStub == null) {
-                    mPrefDevDispatcherStub = new StrategyPreferredDeviceDispatcherStub();
+                    mPrefDevDispatcherStub = new StrategyPreferredDevicesDispatcherStub();
                 }
                 try {
-                    getService().registerStrategyPreferredDeviceDispatcher(mPrefDevDispatcherStub);
+                    getService().registerStrategyPreferredDevicesDispatcher(mPrefDevDispatcherStub);
                 } catch (RemoteException e) {
                     throw e.rethrowFromSystemServer();
                 }
@@ -1726,8 +1843,8 @@
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
-    public void removeOnPreferredDeviceForStrategyChangedListener(
-            @NonNull OnPreferredDeviceForStrategyChangedListener listener) {
+    public void removeOnPreferredDevicesForStrategyChangedListener(
+            @NonNull OnPreferredDevicesForStrategyChangedListener listener) {
         Objects.requireNonNull(listener);
         synchronized (mPrefDevListenerLock) {
             if (!removePrefDevListener(listener)) {
@@ -1738,7 +1855,7 @@
             if (mPrefDevListeners.size() == 0) {
                 // unregister binder for callbacks
                 try {
-                    getService().unregisterStrategyPreferredDeviceDispatcher(
+                    getService().unregisterStrategyPreferredDevicesDispatcher(
                             mPrefDevDispatcherStub);
                 } catch (RemoteException e) {
                     throw e.rethrowFromSystemServer();
@@ -1760,23 +1877,23 @@
     private @Nullable ArrayList<PrefDevListenerInfo> mPrefDevListeners;
 
     private static class PrefDevListenerInfo {
-        final @NonNull OnPreferredDeviceForStrategyChangedListener mListener;
+        final @NonNull OnPreferredDevicesForStrategyChangedListener mListener;
         final @NonNull Executor mExecutor;
-        PrefDevListenerInfo(OnPreferredDeviceForStrategyChangedListener listener, Executor exe) {
+        PrefDevListenerInfo(OnPreferredDevicesForStrategyChangedListener listener, Executor exe) {
             mListener = listener;
             mExecutor = exe;
         }
     }
 
     @GuardedBy("mPrefDevListenerLock")
-    private StrategyPreferredDeviceDispatcherStub mPrefDevDispatcherStub;
+    private StrategyPreferredDevicesDispatcherStub mPrefDevDispatcherStub;
 
-    private final class StrategyPreferredDeviceDispatcherStub
-            extends IStrategyPreferredDeviceDispatcher.Stub {
+    private final class StrategyPreferredDevicesDispatcherStub
+            extends IStrategyPreferredDevicesDispatcher.Stub {
 
         @Override
-        public void dispatchPrefDeviceChanged(int strategyId,
-                                              @Nullable AudioDeviceAttributes device) {
+        public void dispatchPrefDevicesChanged(int strategyId,
+                                               @NonNull List<AudioDeviceAttributes> devices) {
             // make a shallow copy of listeners so callback is not executed under lock
             final ArrayList<PrefDevListenerInfo> prefDevListeners;
             synchronized (mPrefDevListenerLock) {
@@ -1791,7 +1908,7 @@
             try {
                 for (PrefDevListenerInfo info : prefDevListeners) {
                     info.mExecutor.execute(() ->
-                            info.mListener.onPreferredDeviceForStrategyChanged(strategy, device));
+                            info.mListener.onPreferredDevicesForStrategyChanged(strategy, devices));
                 }
             } finally {
                 Binder.restoreCallingIdentity(ident);
@@ -1801,7 +1918,7 @@
 
     @GuardedBy("mPrefDevListenerLock")
     private @Nullable PrefDevListenerInfo getPrefDevListenerInfo(
-            OnPreferredDeviceForStrategyChangedListener listener) {
+            OnPreferredDevicesForStrategyChangedListener listener) {
         if (mPrefDevListeners == null) {
             return null;
         }
@@ -1814,7 +1931,7 @@
     }
 
     @GuardedBy("mPrefDevListenerLock")
-    private boolean hasPrefDevListener(OnPreferredDeviceForStrategyChangedListener listener) {
+    private boolean hasPrefDevListener(OnPreferredDevicesForStrategyChangedListener listener) {
         return getPrefDevListenerInfo(listener) != null;
     }
 
@@ -1822,7 +1939,7 @@
     /**
      * @return true if the listener was removed from the list
      */
-    private boolean removePrefDevListener(OnPreferredDeviceForStrategyChangedListener listener) {
+    private boolean removePrefDevListener(OnPreferredDevicesForStrategyChangedListener listener) {
         final PrefDevListenerInfo infoToRemove = getPrefDevListenerInfo(listener);
         if (infoToRemove != null) {
             mPrefDevListeners.remove(infoToRemove);
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 5fe5c05..243ec1f 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -32,6 +32,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
@@ -1377,6 +1378,11 @@
     /** @hide */ public static final int FOR_VIBRATE_RINGING = 7;
     private static final int NUM_FORCE_USE = 8;
 
+    // Device role in audio policy
+    public static final int DEVICE_ROLE_NONE = 0;
+    public static final int DEVICE_ROLE_PREFERRED = 1;
+    public static final int DEVICE_ROLE_DISABLED = 2;
+
     /** @hide */
     public static String forceUseUsageToString(int usage) {
         switch (usage) {
@@ -1697,47 +1703,58 @@
 
     /**
      * @hide
-     * Sets the preferred device to use for a given audio strategy in the audio policy engine
+     * Set device as role for product strategy.
      * @param strategy the id of the strategy to configure
-     * @param device the device type and address to route to when available
+     * @param role the role of the devices
+     * @param devices the list of devices to be set as role for the given strategy
      * @return {@link #SUCCESS} if successfully set
      */
-    public static int setPreferredDeviceForStrategy(
-            int strategy, @NonNull AudioDeviceAttributes device) {
-        return setPreferredDeviceForStrategy(strategy,
-                AudioDeviceInfo.convertDeviceTypeToInternalDevice(device.getType()),
-                device.getAddress());
+    public static int setDevicesRoleForStrategy(
+            int strategy, int role, @NonNull List<AudioDeviceAttributes> devices) {
+        if (devices.isEmpty()) {
+            return BAD_VALUE;
+        }
+        int[] types = new int[devices.size()];
+        String[] addresses = new String[devices.size()];
+        for (int i = 0; i < devices.size(); ++i) {
+            types[i] = AudioDeviceInfo.convertDeviceTypeToInternalDevice(devices.get(i).getType());
+            addresses[i] = devices.get(i).getAddress();
+        }
+        return setDevicesRoleForStrategy(strategy, role, types, addresses);
     }
-    /**
-     * @hide
-     * Set device routing per product strategy.
-     * @param strategy the id of the strategy to configure
-     * @param deviceType the native device type, NOT AudioDeviceInfo types
-     * @param deviceAddress the address of the device
-     * @return {@link #SUCCESS} if successfully set
-     */
-    private static native int setPreferredDeviceForStrategy(
-            int strategy, int deviceType, String deviceAddress);
 
     /**
      * @hide
-     * Remove preferred routing for the strategy
+     * Set device as role for product strategy.
      * @param strategy the id of the strategy to configure
+     * @param role the role of the devices
+     * @param types all device types
+     * @param addresses all device addresses
+     * @return {@link #SUCCESS} if successfully set
+     */
+    private static native int setDevicesRoleForStrategy(
+            int strategy, int role, @NonNull int[] types, @NonNull String[] addresses);
+
+    /**
+     * @hide
+     * Remove devices as role for the strategy
+     * @param strategy the id of the strategy to configure
+     * @param role the role of the devices
      * @return {@link #SUCCESS} if successfully removed
      */
-    public static native int removePreferredDeviceForStrategy(int strategy);
+    public static native int removeDevicesRoleForStrategy(int strategy, int role);
 
     /**
      * @hide
-     * Query previously set preferred device for a strategy
+     * Query previously set devices as role for a strategy
      * @param strategy the id of the strategy to query for
-     * @param device an array of size 1 that will contain the preferred device, or null if
-     *               none was set
+     * @param role the role of the devices
+     * @param devices a list that will contain the devices of role
      * @return {@link #SUCCESS} if there is a preferred device and it was successfully retrieved
      *     and written to the array
      */
-    public static native int getPreferredDeviceForStrategy(int strategy,
-                                                           AudioDeviceAttributes[] device);
+    public static native int getDevicesForRoleAndStrategy(
+            int strategy, int role, @NonNull List<AudioDeviceAttributes> devices);
 
     // Items shared with audio service
 
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 4cf236a..ef8b0ed 100755
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -29,7 +29,7 @@
 import android.media.IPlaybackConfigDispatcher;
 import android.media.IRecordingConfigDispatcher;
 import android.media.IRingtonePlayer;
-import android.media.IStrategyPreferredDeviceDispatcher;
+import android.media.IStrategyPreferredDevicesDispatcher;
 import android.media.IVolumeController;
 import android.media.IVolumeController;
 import android.media.PlayerBase;
@@ -279,11 +279,11 @@
 
     boolean isCallScreeningModeSupported();
 
-    int setPreferredDeviceForStrategy(in int strategy, in AudioDeviceAttributes device);
+    int setPreferredDevicesForStrategy(in int strategy, in List<AudioDeviceAttributes> device);
 
-    int removePreferredDeviceForStrategy(in int strategy);
+    int removePreferredDevicesForStrategy(in int strategy);
 
-    AudioDeviceAttributes getPreferredDeviceForStrategy(in int strategy);
+    List<AudioDeviceAttributes> getPreferredDevicesForStrategy(in int strategy);
 
     List<AudioDeviceAttributes> getDevicesForAttributes(in AudioAttributes attributes);
 
@@ -291,10 +291,10 @@
 
     int getAllowedCapturePolicy();
 
-    void registerStrategyPreferredDeviceDispatcher(IStrategyPreferredDeviceDispatcher dispatcher);
+    void registerStrategyPreferredDevicesDispatcher(IStrategyPreferredDevicesDispatcher dispatcher);
 
-    oneway void unregisterStrategyPreferredDeviceDispatcher(
-            IStrategyPreferredDeviceDispatcher dispatcher);
+    oneway void unregisterStrategyPreferredDevicesDispatcher(
+            IStrategyPreferredDevicesDispatcher dispatcher);
 
     oneway void setRttEnabled(in boolean rttEnabled);
 
diff --git a/media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl b/media/java/android/media/IStrategyPreferredDevicesDispatcher.aidl
similarity index 82%
rename from media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl
rename to media/java/android/media/IStrategyPreferredDevicesDispatcher.aidl
index b1f99e6..db674c3 100644
--- a/media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl
+++ b/media/java/android/media/IStrategyPreferredDevicesDispatcher.aidl
@@ -19,12 +19,12 @@
 import android.media.AudioDeviceAttributes;
 
 /**
- * AIDL for AudioService to signal audio strategy-preferred device updates.
+ * AIDL for AudioService to signal audio strategy-preferred devices updates.
  *
  * {@hide}
  */
-oneway interface IStrategyPreferredDeviceDispatcher {
+oneway interface IStrategyPreferredDevicesDispatcher {
 
-    void dispatchPrefDeviceChanged(int strategyId, in AudioDeviceAttributes device);
+    void dispatchPrefDevicesChanged(int strategyId, in List<AudioDeviceAttributes> devices);
 
 }
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index 6976a35..2000693 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -523,7 +523,8 @@
      * @param keyEvent The KeyEvent to send.
      * @hide
      */
-    public void dispatchMediaKeyEventAsSystemService(KeyEvent keyEvent) {
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public void dispatchMediaKeyEventAsSystemService(@NonNull KeyEvent keyEvent) {
         dispatchMediaKeyEventInternal(true, keyEvent, false);
     }
 
@@ -548,6 +549,7 @@
      * @return {@code true} if the event was sent to the session, {@code false} otherwise
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public boolean dispatchMediaKeyEventAsSystemService(@NonNull MediaSession.Token sessionToken,
             @NonNull KeyEvent keyEvent) {
         if (sessionToken == null) {
@@ -586,10 +588,15 @@
      * Should be only called by the {@link com.android.internal.policy.PhoneWindow} or
      * {@link android.view.FallbackEventHandler} when the foreground activity didn't consume the key
      * from the hardware devices.
+     * <p>
+     * Valid stream types include {@link AudioManager.PublicStreamTypes} and
+     * {@link AudioManager#USE_DEFAULT_STREAM_TYPE}.
      *
-     * @param keyEvent The KeyEvent to send.
+     * @param keyEvent volume key event
+     * @param streamType type of stream
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public void dispatchVolumeKeyEventAsSystemService(@NonNull KeyEvent keyEvent, int streamType) {
         dispatchVolumeKeyEventInternal(true, keyEvent, streamType, false);
     }
@@ -614,6 +621,7 @@
      * @param keyEvent volume key event
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public void dispatchVolumeKeyEventAsSystemService(@NonNull MediaSession.Token sessionToken,
             @NonNull KeyEvent keyEvent) {
         if (sessionToken == null) {
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 8bf688d..e148d0e 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -362,6 +362,11 @@
      */
     @Override
     public void close() {
+        releaseAll();
+        TunerUtils.throwExceptionForResult(nativeClose(), "failed to close tuner");
+    }
+
+    private void releaseAll() {
         if (mFrontendHandle != null) {
             int res = nativeCloseFrontend(mFrontendHandle);
             if (res != Tuner.RESULT_SUCCESS) {
@@ -396,9 +401,9 @@
                 TunerUtils.throwExceptionForResult(res, "failed to close demux");
             }
             mTunerResourceManager.releaseDemux(mDemuxHandle, mClientId);
-            mFrontendHandle = null;
+            mDemuxHandle = null;
         }
-        TunerUtils.throwExceptionForResult(nativeClose(), "failed to close tuner");
+
     }
 
     /**
@@ -495,6 +500,7 @@
                     break;
                 }
                 case MSG_RESOURCE_LOST: {
+                    releaseAll();
                     if (mOnResourceLostListener != null
                                 && mOnResourceLostListenerExecutor != null) {
                         mOnResourceLostListenerExecutor.execute(
diff --git a/media/java/android/service/media/MediaBrowserService.java b/media/java/android/service/media/MediaBrowserService.java
index 39c7682..1386cba 100644
--- a/media/java/android/service/media/MediaBrowserService.java
+++ b/media/java/android/service/media/MediaBrowserService.java
@@ -684,8 +684,15 @@
                 List<MediaBrowser.MediaItem> filteredList =
                         (flag & RESULT_FLAG_OPTION_NOT_HANDLED) != 0
                                 ? applyOptions(list, options) : list;
-                final ParceledListSlice<MediaBrowser.MediaItem> pls =
-                        filteredList == null ? null : new ParceledListSlice<>(filteredList);
+                final ParceledListSlice<MediaBrowser.MediaItem> pls;
+                if (filteredList == null) {
+                    pls = null;
+                } else {
+                    pls = new ParceledListSlice<>(filteredList);
+                    // Limit the size of initial Parcel to prevent binder buffer overflow
+                    // as onLoadChildren is an async binder call.
+                    pls.setInlineCountLimit(1);
+                }
                 try {
                     connection.callbacks.onLoadChildren(parentId, pls, options);
                 } catch (RemoteException ex) {
diff --git a/media/jni/audioeffect/android_media_AudioEffect.cpp b/media/jni/audioeffect/android_media_AudioEffect.cpp
index 96961ac..45c49e5 100644
--- a/media/jni/audioeffect/android_media_AudioEffect.cpp
+++ b/media/jni/audioeffect/android_media_AudioEffect.cpp
@@ -333,7 +333,7 @@
     if (deviceType != AUDIO_DEVICE_NONE) {
         device.mType = deviceType;
         ScopedUtfChars address(env, deviceAddress);
-        device.mAddress = address.c_str();
+        device.setAddress(address.c_str());
     }
 
     // create the native AudioEffect object
diff --git a/non-updatable-api/current.txt b/non-updatable-api/current.txt
index 51e7287..e90fbd4 100644
--- a/non-updatable-api/current.txt
+++ b/non-updatable-api/current.txt
@@ -44126,7 +44126,9 @@
     method public final void addExistingConnection(android.telecom.PhoneAccountHandle, android.telecom.Connection);
     method public final void conferenceRemoteConnections(android.telecom.RemoteConnection, android.telecom.RemoteConnection);
     method public final void connectionServiceFocusReleased();
+    method @Nullable public final android.telecom.RemoteConference createRemoteIncomingConference(@Nullable android.telecom.PhoneAccountHandle, @Nullable android.telecom.ConnectionRequest);
     method public final android.telecom.RemoteConnection createRemoteIncomingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
+    method @Nullable public final android.telecom.RemoteConference createRemoteOutgoingConference(@Nullable android.telecom.PhoneAccountHandle, @Nullable android.telecom.ConnectionRequest);
     method public final android.telecom.RemoteConnection createRemoteOutgoingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
     method public final java.util.Collection<android.telecom.Conference> getAllConferences();
     method public final java.util.Collection<android.telecom.Connection> getAllConnections();
@@ -44357,6 +44359,7 @@
 
   public final class RemoteConnection {
     method public void abort();
+    method public void addConferenceParticipants(@NonNull java.util.List<android.net.Uri>);
     method public void answer();
     method public void disconnect();
     method public android.net.Uri getAddress();
@@ -45134,7 +45137,7 @@
     method public long getNci();
     method @IntRange(from=0, to=3279165) public int getNrarfcn();
     method @IntRange(from=0, to=1007) public int getPci();
-    method @IntRange(from=0, to=65535) public int getTac();
+    method @IntRange(from=0, to=16777215) public int getTac();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityNr> CREATOR;
   }
@@ -63867,7 +63870,7 @@
 
 package java.math {
 
-  public class BigDecimal extends java.lang.Number implements java.lang.Comparable<java.math.BigDecimal> java.io.Serializable {
+  public class BigDecimal extends java.lang.Number implements java.lang.Comparable<java.math.BigDecimal> {
     ctor public BigDecimal(char[], int, int);
     ctor public BigDecimal(char[], int, int, java.math.MathContext);
     ctor public BigDecimal(char[]);
@@ -63954,19 +63957,20 @@
     field public static final java.math.BigDecimal ZERO;
   }
 
-  public class BigInteger extends java.lang.Number implements java.lang.Comparable<java.math.BigInteger> java.io.Serializable {
+  public class BigInteger extends java.lang.Number implements java.lang.Comparable<java.math.BigInteger> {
+    ctor public BigInteger(byte[]);
+    ctor public BigInteger(int, byte[]);
+    ctor public BigInteger(@NonNull String, int);
+    ctor public BigInteger(@NonNull String);
     ctor public BigInteger(int, @NonNull java.util.Random);
     ctor public BigInteger(int, int, @NonNull java.util.Random);
-    ctor public BigInteger(@NonNull String);
-    ctor public BigInteger(@NonNull String, int);
-    ctor public BigInteger(int, byte[]);
-    ctor public BigInteger(byte[]);
     method @NonNull public java.math.BigInteger abs();
     method @NonNull public java.math.BigInteger add(@NonNull java.math.BigInteger);
     method @NonNull public java.math.BigInteger and(@NonNull java.math.BigInteger);
     method @NonNull public java.math.BigInteger andNot(@NonNull java.math.BigInteger);
     method public int bitCount();
     method public int bitLength();
+    method public byte byteValueExact();
     method @NonNull public java.math.BigInteger clearBit(int);
     method public int compareTo(@NonNull java.math.BigInteger);
     method @NonNull public java.math.BigInteger divide(@NonNull java.math.BigInteger);
@@ -63977,8 +63981,10 @@
     method @NonNull public java.math.BigInteger gcd(@NonNull java.math.BigInteger);
     method public int getLowestSetBit();
     method public int intValue();
+    method public int intValueExact();
     method public boolean isProbablePrime(int);
     method public long longValue();
+    method public long longValueExact();
     method @NonNull public java.math.BigInteger max(@NonNull java.math.BigInteger);
     method @NonNull public java.math.BigInteger min(@NonNull java.math.BigInteger);
     method @NonNull public java.math.BigInteger mod(@NonNull java.math.BigInteger);
@@ -63995,6 +64001,7 @@
     method @NonNull public java.math.BigInteger setBit(int);
     method @NonNull public java.math.BigInteger shiftLeft(int);
     method @NonNull public java.math.BigInteger shiftRight(int);
+    method public short shortValueExact();
     method public int signum();
     method @NonNull public java.math.BigInteger subtract(@NonNull java.math.BigInteger);
     method public boolean testBit(int);
diff --git a/non-updatable-api/module-lib-current.txt b/non-updatable-api/module-lib-current.txt
index 35b483b..8892a29 100644
--- a/non-updatable-api/module-lib-current.txt
+++ b/non-updatable-api/module-lib-current.txt
@@ -7,6 +7,7 @@
 
   public class NotificationManager {
     method public boolean hasEnabledNotificationListener(@NonNull String, @NonNull android.os.UserHandle);
+    field public static final String ACTION_NOTIFICATION_LISTENER_ENABLED_CHANGED = "android.app.action.NOTIFICATION_LISTENER_ENABLED_CHANGED";
   }
 
 }
@@ -45,6 +46,13 @@
     field public static final int FLAG_EXCLUSIVE_GLOBAL_PRIORITY = 65536; // 0x10000
   }
 
+  public final class MediaSessionManager {
+    method public void dispatchMediaKeyEventAsSystemService(@NonNull android.view.KeyEvent);
+    method public boolean dispatchMediaKeyEventAsSystemService(@NonNull android.media.session.MediaSession.Token, @NonNull android.view.KeyEvent);
+    method public void dispatchVolumeKeyEventAsSystemService(@NonNull android.view.KeyEvent, int);
+    method public void dispatchVolumeKeyEventAsSystemService(@NonNull android.media.session.MediaSession.Token, @NonNull android.view.KeyEvent);
+  }
+
 }
 
 package android.os {
diff --git a/non-updatable-api/system-current.txt b/non-updatable-api/system-current.txt
index cf792b6..3fd0ee1 100644
--- a/non-updatable-api/system-current.txt
+++ b/non-updatable-api/system-current.txt
@@ -4136,7 +4136,8 @@
 
   public class AudioManager {
     method @Deprecated public int abandonAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDeviceForStrategyChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDeviceForStrategyChangedListener) throws java.lang.SecurityException;
+    method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDeviceForStrategyChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDeviceForStrategyChangedListener) throws java.lang.SecurityException;
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDevicesForStrategyChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDevicesForStrategyChangedListener) throws java.lang.SecurityException;
     method public void clearAudioServerStateCallback();
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int dispatchAudioFocusChange(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy);
     method @IntRange(from=0) public long getAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo);
@@ -4148,13 +4149,15 @@
     method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMaxVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
     method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMinVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
     method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.AudioDeviceAttributes getPreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy);
+    method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioDeviceAttributes> getPreferredDevicesForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy);
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int[] getSupportedSystemUsages();
     method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
     method public boolean isAudioServerRunning();
     method public boolean isHdmiSystemAudioSupported();
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int registerAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy);
     method public void registerVolumeGroupCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.VolumeGroupCallback);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDeviceForStrategyChangedListener(@NonNull android.media.AudioManager.OnPreferredDeviceForStrategyChangedListener);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDeviceForStrategyChangedListener(@NonNull android.media.AudioManager.OnPreferredDeviceForStrategyChangedListener);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDevicesForStrategyChangedListener(@NonNull android.media.AudioManager.OnPreferredDevicesForStrategyChangedListener);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean removePreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, @NonNull android.media.AudioAttributes, int, int) throws java.lang.IllegalArgumentException;
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.MODIFY_AUDIO_ROUTING}) public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, @NonNull android.media.AudioAttributes, int, int, android.media.audiopolicy.AudioPolicy) throws java.lang.IllegalArgumentException;
@@ -4164,6 +4167,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setDeviceVolumeBehavior(@NonNull android.media.AudioDeviceAttributes, int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setFocusRequestResult(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setPreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull android.media.AudioDeviceAttributes);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setPreferredDevicesForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull java.util.List<android.media.AudioDeviceAttributes>);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setSupportedSystemUsages(@NonNull int[]);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setVolumeIndexForAttributes(@NonNull android.media.AudioAttributes, int, int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy);
@@ -4187,8 +4191,12 @@
     method public void onAudioServerUp();
   }
 
-  public static interface AudioManager.OnPreferredDeviceForStrategyChangedListener {
-    method public void onPreferredDeviceForStrategyChanged(@NonNull android.media.audiopolicy.AudioProductStrategy, @Nullable android.media.AudioDeviceAttributes);
+  @Deprecated public static interface AudioManager.OnPreferredDeviceForStrategyChangedListener {
+    method @Deprecated public void onPreferredDeviceForStrategyChanged(@NonNull android.media.audiopolicy.AudioProductStrategy, @Nullable android.media.AudioDeviceAttributes);
+  }
+
+  public static interface AudioManager.OnPreferredDevicesForStrategyChangedListener {
+    method public void onPreferredDevicesForStrategyChanged(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull java.util.List<android.media.AudioDeviceAttributes>);
   }
 
   public abstract static class AudioManager.VolumeGroupCallback {
@@ -11029,6 +11037,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningStringValue(int, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setRcsProvisioningStatusForCapability(int, boolean);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.Callback);
+    field public static final int KEY_VOICE_OVER_WIFI_ENTITLEMENT_ID = 67; // 0x43
     field public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27; // 0x1b
     field public static final int KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE = 26; // 0x1a
     field public static final int PROVISIONING_VALUE_DISABLED = 0; // 0x0
diff --git a/packages/CarSystemUI/Android.bp b/packages/CarSystemUI/Android.bp
index 8598f74e..08dd799 100644
--- a/packages/CarSystemUI/Android.bp
+++ b/packages/CarSystemUI/Android.bp
@@ -128,6 +128,8 @@
         "CarSystemUI-core",
     ],
 
+    export_package_resources: true,
+
     libs: [
         "android.car",
     ],
diff --git a/packages/CarSystemUI/proguard.flags b/packages/CarSystemUI/proguard.flags
index 66cbf26..f0b20c1 100644
--- a/packages/CarSystemUI/proguard.flags
+++ b/packages/CarSystemUI/proguard.flags
@@ -1,4 +1,7 @@
 -keep class com.android.systemui.CarSystemUIFactory
 -keep class com.android.car.notification.headsup.animationhelper.**
 
+-keep class com.android.systemui.DaggerCarGlobalRootComponent { *; }
+-keep class com.android.systemui.DaggerCarGlobalRootComponent$CarSysUIComponentImpl { *; }
+
 -include ../SystemUI/proguard.flags
diff --git a/packages/CarSystemUI/res/layout/car_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_navigation_bar.xml
index 9317498..a49a637 100644
--- a/packages/CarSystemUI/res/layout/car_navigation_bar.xml
+++ b/packages/CarSystemUI/res/layout/car_navigation_bar.xml
@@ -25,7 +25,7 @@
     <!--The 20dp padding is the difference between the background selected icon size and the ripple
         that was chosen, thus it's a hack to make it look pretty and not an official margin value-->
     <LinearLayout
-        android:id="@id/nav_buttons"
+        android:id="@+id/nav_buttons"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_weight="1"
diff --git a/packages/CarSystemUI/res/values/ids.xml b/packages/CarSystemUI/res/values/ids.xml
index 27ed2e2..05194a4 100644
--- a/packages/CarSystemUI/res/values/ids.xml
+++ b/packages/CarSystemUI/res/values/ids.xml
@@ -18,5 +18,4 @@
 <resources>
     <!-- Values used for finding elements on the system ui nav bars -->
     <item type="id" name="lock_screen_nav_buttons"/>
-    <item type="id" name="nav_buttons"/>
 </resources>
\ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_apps.xml b/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_apps.xml
new file mode 100644
index 0000000..a8d8a2f
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_apps.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:viewportWidth="44"
+        android:viewportHeight="44"
+        android:width="44dp"
+        android:height="44dp">
+<path
+    android:pathData="M7.33333333 14.6666667L14.6666667 14.6666667L14.6666667 7.33333333L7.33333333 7.33333333L7.33333333 14.6666667ZM18.3333333 36.6666667L25.6666667 36.6666667L25.6666667 29.3333333L18.3333333 29.3333333L18.3333333 36.6666667ZM7.33333333 36.6666667L14.6666667 36.6666667L14.6666667 29.3333333L7.33333333 29.3333333L7.33333333 36.6666667ZM7.33333333 25.6666667L14.6666667 25.6666667L14.6666667 18.3333333L7.33333333 18.3333333L7.33333333 25.6666667ZM18.3333333 25.6666667L25.6666667 25.6666667L25.6666667 18.3333333L18.3333333 18.3333333L18.3333333 25.6666667ZM29.3333333 7.33333333L29.3333333 14.6666667L36.6666667 14.6666667L36.6666667 7.33333333L29.3333333 7.33333333ZM18.3333333 14.6666667L25.6666667 14.6666667L25.6666667 7.33333333L18.3333333 7.33333333L18.3333333 14.6666667ZM29.3333333 25.6666667L36.6666667 25.6666667L36.6666667 18.3333333L29.3333333 18.3333333L29.3333333 25.6666667ZM29.3333333 36.6666667L36.6666667 36.6666667L36.6666667 29.3333333L29.3333333 29.3333333L29.3333333 36.6666667Z"
+    android:fillColor="@color/car_nav_icon_fill_color" />
+</vector>
\ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_apps_selected.xml b/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_apps_selected.xml
new file mode 100644
index 0000000..2a4e91a
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_apps_selected.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:viewportWidth="44"
+        android:viewportHeight="44"
+        android:width="44dp"
+        android:height="44dp">
+    <path
+        android:pathData="M7.33333333 14.6666667L14.6666667 14.6666667L14.6666667 7.33333333L7.33333333 7.33333333L7.33333333 14.6666667ZM18.3333333 36.6666667L25.6666667 36.6666667L25.6666667 29.3333333L18.3333333 29.3333333L18.3333333 36.6666667ZM7.33333333 36.6666667L14.6666667 36.6666667L14.6666667 29.3333333L7.33333333 29.3333333L7.33333333 36.6666667ZM7.33333333 25.6666667L14.6666667 25.6666667L14.6666667 18.3333333L7.33333333 18.3333333L7.33333333 25.6666667ZM18.3333333 25.6666667L25.6666667 25.6666667L25.6666667 18.3333333L18.3333333 18.3333333L18.3333333 25.6666667ZM29.3333333 7.33333333L29.3333333 14.6666667L36.6666667 14.6666667L36.6666667 7.33333333L29.3333333 7.33333333ZM18.3333333 14.6666667L25.6666667 14.6666667L25.6666667 7.33333333L18.3333333 7.33333333L18.3333333 14.6666667ZM29.3333333 25.6666667L36.6666667 25.6666667L36.6666667 18.3333333L29.3333333 18.3333333L29.3333333 25.6666667ZM29.3333333 36.6666667L36.6666667 36.6666667L36.6666667 29.3333333L29.3333333 29.3333333L29.3333333 36.6666667Z"
+        android:fillColor="@color/car_nav_icon_fill_color_selected" />
+</vector>
\ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_music.xml b/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_music.xml
new file mode 100644
index 0000000..6339ebb
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_music.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:viewportWidth="44"
+    android:viewportHeight="44"
+    android:width="44dp"
+    android:height="44dp">
+    <path
+        android:pathData="M22 5.5L22 24.8416667C20.9183333 24.2183333 19.6716667 23.8333333 18.3333333 23.8333333C14.2816667 23.8333333 11 27.115 11 31.1666667C11 35.2183333 14.2816667 38.5 18.3333333 38.5C22.385 38.5 25.6666667 35.2183333 25.6666667 31.1666667L25.6666667 12.8333333L33 12.8333333L33 5.5L22 5.5Z"
+        android:fillColor="@color/car_nav_icon_fill_color" />
+</vector>
\ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_music_selected.xml b/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_music_selected.xml
new file mode 100644
index 0000000..a56bcb3
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_music_selected.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:viewportWidth="44"
+        android:viewportHeight="44"
+        android:width="44dp"
+        android:height="44dp">
+    <path
+        android:pathData="M22 5.5L22 24.8416667C20.9183333 24.2183333 19.6716667 23.8333333 18.3333333 23.8333333C14.2816667 23.8333333 11 27.115 11 31.1666667C11 35.2183333 14.2816667 38.5 18.3333333 38.5C22.385 38.5 25.6666667 35.2183333 25.6666667 31.1666667L25.6666667 12.8333333L33 12.8333333L33 5.5L22 5.5Z"
+        android:fillColor="@color/car_nav_icon_fill_color_selected" />
+</vector>
\ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_navigation.xml b/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_navigation.xml
new file mode 100644
index 0000000..e1fabe0
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_navigation.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:viewportWidth="44"
+    android:viewportHeight="44"
+    android:width="44dp"
+    android:height="44dp">
+    <path
+        android:pathData="M39.8016667 20.6983333L23.3016667 4.19833333C22.5866667 3.48333333 21.4316667 3.48333333 20.7166667 4.19833333L4.21666667 20.6983333C3.50166667 21.4133333 3.50166667 22.5683333 4.21666667 23.2833333L20.7166667 39.7833333C21.4316667 40.4983333 22.5866667 40.4983333 23.3016667 39.7833333L39.8016667 23.2833333C40.5166667 22.5866667 40.5166667 21.4316667 39.8016667 20.6983333ZM25.6666667 26.5833333L25.6666667 22L18.3333333 22L18.3333333 27.5L14.6666667 27.5L14.6666667 20.1666667C14.6666667 19.1583333 15.4916667 18.3333333 16.5 18.3333333L25.6666667 18.3333333L25.6666667 13.75L32.0833333 20.1666667L25.6666667 26.5833333Z"
+        android:fillColor="@color/car_nav_icon_fill_color" />
+</vector>
\ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_navigation_selected.xml b/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_navigation_selected.xml
new file mode 100644
index 0000000..d11cf28
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_navigation_selected.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:viewportWidth="44"
+        android:viewportHeight="44"
+        android:width="44dp"
+        android:height="44dp">
+    <path
+        android:pathData="M39.8016667 20.6983333L23.3016667 4.19833333C22.5866667 3.48333333 21.4316667 3.48333333 20.7166667 4.19833333L4.21666667 20.6983333C3.50166667 21.4133333 3.50166667 22.5683333 4.21666667 23.2833333L20.7166667 39.7833333C21.4316667 40.4983333 22.5866667 40.4983333 23.3016667 39.7833333L39.8016667 23.2833333C40.5166667 22.5866667 40.5166667 21.4316667 39.8016667 20.6983333ZM25.6666667 26.5833333L25.6666667 22L18.3333333 22L18.3333333 27.5L14.6666667 27.5L14.6666667 20.1666667C14.6666667 19.1583333 15.4916667 18.3333333 16.5 18.3333333L25.6666667 18.3333333L25.6666667 13.75L32.0833333 20.1666667L25.6666667 26.5833333Z"
+        android:fillColor="@color/car_nav_icon_fill_color_selected" />
+</vector>
\ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_overview.xml b/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_overview.xml
new file mode 100644
index 0000000..f185eb9
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_overview.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:viewportWidth="44"
+    android:viewportHeight="44"
+    android:width="44dp"
+    android:height="44dp">
+    <path
+        android:pathData="M36.92857 22.39286A14.53571 14.53571 0 0 1 7.857143 22.39286A14.53571 14.53571 0 0 1 36.92857 22.39286Z"
+        android:strokeColor="@color/car_nav_icon_fill_color"
+        android:strokeWidth="4" />
+</vector>
\ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_overview_selected.xml b/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_overview_selected.xml
new file mode 100644
index 0000000..19b5583
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_overview_selected.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:viewportWidth="44"
+        android:viewportHeight="44"
+        android:width="44dp"
+        android:height="44dp">
+    <path
+        android:pathData="M36.92857 22.39286A14.53571 14.53571 0 0 1 7.857143 22.39286A14.53571 14.53571 0 0 1 36.92857 22.39286Z"
+        android:strokeColor="@color/car_nav_icon_fill_color_selected"
+        android:strokeWidth="4" />
+</vector>
\ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_phone.xml b/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_phone.xml
new file mode 100644
index 0000000..50e36b5
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_phone.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:viewportWidth="44"
+    android:viewportHeight="44"
+    android:width="44dp"
+    android:height="44dp">
+    <path
+        android:pathData="M12.1366667 19.7816667C14.7766667 24.97 19.03 29.205 24.2183333 31.8633333L28.2516667 27.83C28.7466667 27.335 29.48 27.17 30.1216667 27.39C32.175 28.0683333 34.3933333 28.435 36.6666667 28.435C37.675 28.435 38.5 29.26 38.5 30.2683333L38.5 36.6666667C38.5 37.675 37.675 38.5 36.6666667 38.5C19.4516667 38.5 5.5 24.5483333 5.5 7.33333333C5.5 6.325 6.325 5.5 7.33333333 5.5L13.75 5.5C14.7583333 5.5 15.5833333 6.325 15.5833333 7.33333333C15.5833333 9.625 15.95 11.825 16.6283333 13.8783333C16.83 14.52 16.6833333 15.235 16.17 15.7483333L12.1366667 19.7816667Z"
+        android:fillColor="@color/car_nav_icon_fill_color" />
+</vector>
\ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_phone_selected.xml b/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_phone_selected.xml
new file mode 100644
index 0000000..11b1687
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_phone_selected.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:viewportWidth="44"
+        android:viewportHeight="44"
+        android:width="44dp"
+        android:height="44dp">
+    <path
+        android:pathData="M12.1366667 19.7816667C14.7766667 24.97 19.03 29.205 24.2183333 31.8633333L28.2516667 27.83C28.7466667 27.335 29.48 27.17 30.1216667 27.39C32.175 28.0683333 34.3933333 28.435 36.6666667 28.435C37.675 28.435 38.5 29.26 38.5 30.2683333L38.5 36.6666667C38.5 37.675 37.675 38.5 36.6666667 38.5C19.4516667 38.5 5.5 24.5483333 5.5 7.33333333C5.5 6.325 6.325 5.5 7.33333333 5.5L13.75 5.5C14.7583333 5.5 15.5833333 6.325 15.5833333 7.33333333C15.5833333 9.625 15.95 11.825 16.6283333 13.8783333C16.83 14.52 16.6833333 15.235 16.17 15.7483333L12.1366667 19.7816667Z"
+        android:fillColor="@color/car_nav_icon_fill_color_selected" />
+</vector>
\ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample1/rro/res/drawable/system_bar_background.xml b/packages/CarSystemUI/samples/sample1/rro/res/drawable/system_bar_background.xml
new file mode 100644
index 0000000..6161ad9
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample1/rro/res/drawable/system_bar_background.xml
@@ -0,0 +1,33 @@
+<?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.
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
+    <corners
+        android:topLeftRadius="0dp"
+        android:topRightRadius="10dp"
+        android:bottomLeftRadius="0dp"
+        android:bottomRightRadius="0dp"
+    />
+    <solid
+        android:color="#404040"
+    />
+    <padding
+        android:left="0dp"
+        android:top="0dp"
+        android:right="0dp"
+        android:bottom="0dp"
+    />
+</shape>
\ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample1/rro/res/drawable/system_bar_background_2.xml b/packages/CarSystemUI/samples/sample1/rro/res/drawable/system_bar_background_2.xml
new file mode 100644
index 0000000..3582142
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample1/rro/res/drawable/system_bar_background_2.xml
@@ -0,0 +1,33 @@
+<?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.
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
+    <corners
+        android:topLeftRadius="10dp"
+        android:topRightRadius="0dp"
+        android:bottomLeftRadius="0dp"
+        android:bottomRightRadius="0dp"
+    />
+    <solid
+        android:color="#404040"
+    />
+    <padding
+        android:left="0dp"
+        android:top="0dp"
+        android:right="0dp"
+        android:bottom="0dp"
+    />
+</shape>
\ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample1/rro/res/drawable/system_bar_background_3.xml b/packages/CarSystemUI/samples/sample1/rro/res/drawable/system_bar_background_3.xml
new file mode 100644
index 0000000..afa5b32
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample1/rro/res/drawable/system_bar_background_3.xml
@@ -0,0 +1,33 @@
+<?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.
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
+    <corners
+        android:topLeftRadius="0dp"
+        android:topRightRadius="0dp"
+        android:bottomLeftRadius="10dp"
+        android:bottomRightRadius="0dp"
+    />
+    <solid
+        android:color="#404040"
+    />
+    <padding
+        android:left="0dp"
+        android:top="0dp"
+        android:right="0dp"
+        android:bottom="0dp"
+    />
+</shape>
\ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample1/rro/res/layout/car_navigation_bar.xml b/packages/CarSystemUI/samples/sample1/rro/res/layout/car_navigation_bar.xml
new file mode 100644
index 0000000..4358d97
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample1/rro/res/layout/car_navigation_bar.xml
@@ -0,0 +1,88 @@
+<?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.
+  -->
+<com.android.systemui.car.navigationbar.CarNavigationBarView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:systemui="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@android:color/transparent"
+    android:orientation="horizontal">
+    <!--The 20dp padding is the difference between the background selected icon size and the ripple
+        that was chosen, thus it's a hack to make it look pretty and not an official margin value-->
+    <LinearLayout
+        android:id="@+id/nav_buttons"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:background="@drawable/system_bar_background"
+        android:gravity="center"
+        android:layoutDirection="ltr"
+        android:paddingEnd="20dp"
+        android:paddingStart="20dp">
+
+        <com.android.systemui.car.navigationbar.CarNavigationButton
+            android:id="@+id/home"
+            style="@style/NavigationBarButton"
+            systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
+            systemui:icon="@drawable/car_ic_overview"
+            systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
+            systemui:selectedIcon="@drawable/car_ic_overview_selected"
+            systemui:highlightWhenSelected="true"
+        />
+
+        <com.android.systemui.car.navigationbar.CarNavigationButton
+            android:id="@+id/maps_nav"
+            style="@style/NavigationBarButton"
+            systemui:categories="android.intent.category.APP_MAPS"
+            systemui:icon="@drawable/car_ic_navigation"
+            systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MAPS;launchFlags=0x14000000;end"
+            systemui:selectedIcon="@drawable/car_ic_navigation_selected"
+            systemui:highlightWhenSelected="true"
+        />
+
+        <com.android.systemui.car.navigationbar.CarNavigationButton
+            android:id="@+id/music_nav"
+            style="@style/NavigationBarButton"
+            systemui:categories="android.intent.category.APP_MUSIC"
+            systemui:icon="@drawable/car_ic_music"
+            systemui:intent="intent:#Intent;action=android.car.intent.action.MEDIA_TEMPLATE;launchFlags=0x10000000;end"
+            systemui:packages="com.android.car.media"
+            systemui:selectedIcon="@drawable/car_ic_music_selected"
+            systemui:highlightWhenSelected="true"
+        />
+
+        <com.android.systemui.car.navigationbar.CarNavigationButton
+            android:id="@+id/phone_nav"
+            style="@style/NavigationBarButton"
+            systemui:icon="@drawable/car_ic_phone"
+            systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;package=com.android.car.dialer;launchFlags=0x10000000;end"
+            systemui:packages="com.android.car.dialer"
+            systemui:selectedIcon="@drawable/car_ic_phone_selected"
+            systemui:highlightWhenSelected="true"
+        />
+
+        <com.android.systemui.car.navigationbar.CarNavigationButton
+            android:id="@+id/grid_nav"
+            style="@style/NavigationBarButton"
+            systemui:componentNames="com.android.car.carlauncher/.AppGridActivity"
+            systemui:icon="@drawable/car_ic_apps"
+            systemui:intent="intent:#Intent;component=com.android.car.carlauncher/.AppGridActivity;launchFlags=0x24000000;end"
+            systemui:selectedIcon="@drawable/car_ic_apps_selected"
+            systemui:highlightWhenSelected="true"
+        />
+
+    </LinearLayout>
+</com.android.systemui.car.navigationbar.CarNavigationBarView>
diff --git a/packages/CarSystemUI/samples/sample1/rro/res/layout/car_right_navigation_bar.xml b/packages/CarSystemUI/samples/sample1/rro/res/layout/car_right_navigation_bar.xml
new file mode 100644
index 0000000..dc1d0d64
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample1/rro/res/layout/car_right_navigation_bar.xml
@@ -0,0 +1,141 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2018, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<com.android.systemui.car.navigationbar.CarNavigationBarView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:systemui="http://schemas.android.com/apk/res-auto"
+    android:layout_height="match_parent"
+    android:layout_width="match_parent"
+    android:orientation="vertical"
+    android:baselineAligned="false"
+    android:background="@android:color/transparent">
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="110dp"
+        android:background="@drawable/system_bar_background_3">
+        <FrameLayout
+            android:id="@+id/clock_container"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_centerInParent="true">
+            <com.android.systemui.car.navigationbar.CarNavigationButton
+                android:id="@+id/qs"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:background="@null"
+                systemui:intent="intent:#Intent;component=com.android.car.settings/.common.CarSettingActivities$QuickSettingActivity;launchFlags=0x24000000;end"
+            />
+            <com.android.systemui.statusbar.policy.Clock
+                android:id="@+id/clock"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:elevation="5dp"
+                android:singleLine="true"
+                android:textAppearance="@style/TextAppearance.StatusBar.Clock"
+            />
+        </FrameLayout>
+    </RelativeLayout>
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1"
+    />
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="110dp"
+        android:layout_gravity="bottom"
+        android:orientation="horizontal"
+        android:background="@drawable/system_bar_background_2">
+
+        <FrameLayout
+            android:id="@+id/left_hvac_container"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_alignParentStart="true">
+
+            <com.android.systemui.car.navigationbar.CarNavigationButton
+                android:id="@+id/hvacleft"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:background="@null"
+                systemui:broadcast="true"
+                systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end"
+            />
+
+            <com.android.systemui.car.hvac.AnimatedTemperatureView
+                android:id="@+id/lefttext"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:paddingStart="@*android:dimen/car_padding_4"
+                android:paddingEnd="16dp"
+                android:gravity="center_vertical|start"
+                android:minEms="4"
+                android:textAppearance="@style/TextAppearance.CarStatus"
+                systemui:hvacAreaId="49"
+                systemui:hvacMaxText="Max"
+                systemui:hvacMaxValue="126"
+                systemui:hvacMinText="Min"
+                systemui:hvacMinValue="0"
+                systemui:hvacPivotOffset="60dp"
+                systemui:hvacPropertyId="358614275"
+                systemui:hvacTempFormat="%.0f\u00B0"
+            />
+        </FrameLayout>
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:layout_weight="1"
+        />
+        <FrameLayout
+            android:id="@+id/right_hvac_container"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_alignParentEnd="true">
+
+            <com.android.systemui.car.navigationbar.CarNavigationButton
+                android:id="@+id/hvacright"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:background="@null"
+                systemui:broadcast="true"
+                systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end"
+            />
+
+            <com.android.systemui.car.hvac.AnimatedTemperatureView
+                android:id="@+id/righttext"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:paddingStart="16dp"
+                android:paddingEnd="@*android:dimen/car_padding_4"
+                android:gravity="center_vertical|end"
+                android:minEms="4"
+                android:textAppearance="@style/TextAppearance.CarStatus"
+                systemui:hvacAreaId="68"
+                systemui:hvacMaxText="Max"
+                systemui:hvacMaxValue="126"
+                systemui:hvacMinText="Min"
+                systemui:hvacMinValue="0"
+                systemui:hvacPivotOffset="60dp"
+                systemui:hvacPropertyId="358614275"
+                systemui:hvacTempFormat="%.0f\u00B0"
+            />
+        </FrameLayout>
+    </LinearLayout>
+</com.android.systemui.car.navigationbar.CarNavigationBarView>
diff --git a/packages/CarSystemUI/samples/sample1/rro/res/layout/system_icons.xml b/packages/CarSystemUI/samples/sample1/rro/res/layout/system_icons.xml
new file mode 100644
index 0000000..d235792
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample1/rro/res/layout/system_icons.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/system_icons"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="center_vertical">
+
+    <com.android.systemui.statusbar.phone.StatusIconContainer
+        android:id="@+id/statusIcons"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="1"
+        android:paddingEnd="4dp"
+        android:gravity="center_vertical"
+        android:orientation="horizontal"
+    />
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample1/rro/res/values/attrs.xml b/packages/CarSystemUI/samples/sample1/rro/res/values/attrs.xml
new file mode 100644
index 0000000..e02f9e6
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample1/rro/res/values/attrs.xml
@@ -0,0 +1,44 @@
+<?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.
+  -->
+
+<resources>
+    <attr name="broadcast" format="boolean"/>
+    <attr name="icon" format="reference"/>
+    <attr name="selectedIcon" format="reference"/>
+    <attr name="intent" format="string"/>
+    <attr name="longIntent" format="string"/>
+    <attr name="componentNames" format="string" />
+    <attr name="highlightWhenSelected" format="boolean" />
+    <attr name="categories" format="string"/>
+    <attr name="packages" format="string" />
+
+    <!-- Custom attributes to configure hvac values -->
+    <declare-styleable name="AnimatedTemperatureView">
+        <attr name="hvacAreaId" format="integer"/>
+        <attr name="hvacPropertyId" format="integer"/>
+        <attr name="hvacTempFormat" format="string"/>
+        <!-- how far away the animations should center around -->
+        <attr name="hvacPivotOffset" format="dimension"/>
+        <attr name="hvacMinValue" format="float"/>
+        <attr name="hvacMaxValue" format="float"/>
+        <attr name="hvacMinText" format="string|reference"/>
+        <attr name="hvacMaxText" format="string|reference"/>
+        <attr name="android:gravity"/>
+        <attr name="android:minEms"/>
+        <attr name="android:textAppearance"/>
+    </declare-styleable>
+</resources>
diff --git a/packages/CarSystemUI/samples/sample1/rro/res/values/colors.xml b/packages/CarSystemUI/samples/sample1/rro/res/values/colors.xml
new file mode 100644
index 0000000..c32d638
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample1/rro/res/values/colors.xml
@@ -0,0 +1,20 @@
+<?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.
+  -->
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <color name="car_nav_icon_fill_color">#8F8F8F</color>
+    <color name="car_nav_icon_fill_color_selected">#FFFFFF</color>
+</resources>
diff --git a/packages/CarSystemUI/samples/sample1/rro/res/values/config.xml b/packages/CarSystemUI/samples/sample1/rro/res/values/config.xml
index 854ab7d..2ec90e9 100644
--- a/packages/CarSystemUI/samples/sample1/rro/res/values/config.xml
+++ b/packages/CarSystemUI/samples/sample1/rro/res/values/config.xml
@@ -17,8 +17,8 @@
 
 <resources>
     <!-- Configure which system bars should be displayed. -->
-    <bool name="config_enableTopNavigationBar">true</bool>
-    <bool name="config_enableLeftNavigationBar">true</bool>
+    <bool name="config_enableTopNavigationBar">false</bool>
+    <bool name="config_enableLeftNavigationBar">false</bool>
     <bool name="config_enableRightNavigationBar">true</bool>
     <bool name="config_enableBottomNavigationBar">true</bool>
 
@@ -28,8 +28,8 @@
     <!--    STATUS_BAR_EXTRA = 2-->
     <!--    NAVIGATION_BAR_EXTRA = 3-->
     <integer name="config_topSystemBarType">0</integer>
-    <integer name="config_leftSystemBarType">2</integer>
-    <integer name="config_rightSystemBarType">3</integer>
+    <integer name="config_leftSystemBarType">0</integer>
+    <integer name="config_rightSystemBarType">0</integer>
     <integer name="config_bottomSystemBarType">1</integer>
 
     <!-- Configure the relative z-order among the system bars. When two system bars overlap (e.g.
@@ -40,8 +40,19 @@
          RuntimeException, since their placing order cannot be determined. Bars that do not overlap
          are allowed to have the same z-order. -->
     <!-- NOTE: If the z-order of a bar is 10 or above, it will also appear on top of HUN's.    -->
-    <integer name="config_topSystemBarZOrder">1</integer>
+    <integer name="config_topSystemBarZOrder">0</integer>
     <integer name="config_leftSystemBarZOrder">0</integer>
-    <integer name="config_rightSystemBarZOrder">0</integer>
+    <integer name="config_rightSystemBarZOrder">11</integer>
     <integer name="config_bottomSystemBarZOrder">10</integer>
+
+    <!-- Whether heads-up notifications should be shown on the bottom. If false, heads-up
+         notifications will be shown pushed to the top of their parent container. If true, they will
+         be shown pushed to the bottom of their parent container. If true, then should override
+         config_headsUpNotificationAnimationHelper to use a different AnimationHelper, such as
+         com.android.car.notification.headsup.animationhelper.
+         CarHeadsUpNotificationBottomAnimationHelper. -->
+    <bool name="config_showHeadsUpNotificationOnBottom">true</bool>
+
+    <string name="config_notificationPanelViewMediator" translatable="false">
+        com.android.systemui.car.notification.BottomNotificationPanelViewMediator</string>
 </resources>
\ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample1/rro/res/values/dimens.xml b/packages/CarSystemUI/samples/sample1/rro/res/values/dimens.xml
new file mode 100644
index 0000000..cdfed27
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample1/rro/res/values/dimens.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<resources>
+    <dimen name="car_right_navigation_bar_width">280dp</dimen>
+</resources>
diff --git a/packages/CarSystemUI/samples/sample1/rro/res/values/styles.xml b/packages/CarSystemUI/samples/sample1/rro/res/values/styles.xml
new file mode 100644
index 0000000..136dc3b
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample1/rro/res/values/styles.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.
+  -->
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <style name="TextAppearance.StatusBar.Clock"
+           parent="@*android:style/TextAppearance.StatusBar.Icon">
+        <item name="android:textSize">40sp</item>
+        <item name="android:fontFamily">sans-serif-regular</item>
+        <item name="android:textColor">#FFFFFF</item>
+    </style>
+
+    <style name="NavigationBarButton">
+        <item name="android:layout_height">96dp</item>
+        <item name="android:layout_width">96dp</item>
+        <item name="android:background">?android:attr/selectableItemBackground</item>
+    </style>
+
+    <style name="TextAppearance.CarStatus" parent="@android:style/TextAppearance.DeviceDefault">
+        <item name="android:textSize">30sp</item>
+        <item name="android:textColor">#FFFFFF</item>
+    </style>
+</resources>
\ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample1/rro/res/xml/car_sysui_overlays.xml b/packages/CarSystemUI/samples/sample1/rro/res/xml/car_sysui_overlays.xml
index 7bcb8e1..b8e1edc 100644
--- a/packages/CarSystemUI/samples/sample1/rro/res/xml/car_sysui_overlays.xml
+++ b/packages/CarSystemUI/samples/sample1/rro/res/xml/car_sysui_overlays.xml
@@ -16,10 +16,50 @@
   -->
 
 <overlay>
+    <item target="layout/car_navigation_bar" value="@layout/car_navigation_bar"/>
+    <item target="layout/system_icons" value="@layout/system_icons"/>
+    <item target="layout/car_right_navigation_bar" value="@layout/car_right_navigation_bar"/>
+
+    <item target="attr/icon" value="@attr/icon"/>
+    <item target="attr/selectedIcon" value="@attr/selectedIcon"/>
+    <item target="attr/intent" value="@attr/longIntent"/>
+    <item target="attr/componentNames" value="@attr/componentNames"/>
+    <item target="attr/highlightWhenSelected" value="@attr/highlightWhenSelected"/>
+    <item target="attr/categories" value="@attr/categories"/>
+    <item target="attr/packages" value="@attr/packages"/>
+    <item target="attr/hvacAreaId" value="@attr/hvacAreaId"/>
+    <item target="attr/hvacPropertyId" value="@attr/hvacPropertyId"/>
+    <item target="attr/hvacTempFormat" value="@attr/hvacTempFormat"/>
+    <item target="attr/hvacPivotOffset" value="@attr/hvacPivotOffset"/>
+    <item target="attr/hvacMinValue" value="@attr/hvacMinValue"/>
+    <item target="attr/hvacMaxValue" value="@attr/hvacMaxValue"/>
+    <item target="attr/hvacMinText" value="@attr/hvacMinText"/>
+    <item target="attr/hvacMaxText" value="@attr/hvacMaxText"/>
+    <!-- start the intent as a broad cast instead of an activity if true-->
+    <item target="attr/broadcast" value="@attr/broadcast"/>
+
+    <item target="drawable/car_ic_overview" value="@drawable/car_ic_overview" />
+    <item target="drawable/car_ic_overview_selected" value="@drawable/car_ic_overview_selected" />
+    <item target="drawable/car_ic_apps" value="@drawable/car_ic_apps" />
+    <item target="drawable/car_ic_apps_selected" value="@drawable/car_ic_apps_selected" />
+    <item target="drawable/car_ic_music" value="@drawable/car_ic_music" />
+    <item target="drawable/car_ic_music_selected" value="@drawable/car_ic_music_selected" />
+    <item target="drawable/car_ic_phone" value="@drawable/car_ic_phone" />
+    <item target="drawable/car_ic_phone_selected" value="@drawable/car_ic_phone_selected" />
+    <item target="drawable/car_ic_navigation" value="@drawable/car_ic_navigation" />
+    <item target="drawable/car_ic_navigation_selected" value="@drawable/car_ic_navigation_selected" />
+
+    <item target="dimen/car_right_navigation_bar_width" value="@dimen/car_right_navigation_bar_width" />
+
+    <item target="style/NavigationBarButton" value="@style/NavigationBarButton"/>
+
+    <item target="color/car_nav_icon_fill_color" value="@color/car_nav_icon_fill_color" />
+
     <item target="bool/config_enableTopNavigationBar" value="@bool/config_enableTopNavigationBar"/>
     <item target="bool/config_enableLeftNavigationBar" value="@bool/config_enableLeftNavigationBar"/>
     <item target="bool/config_enableRightNavigationBar" value="@bool/config_enableRightNavigationBar"/>
     <item target="bool/config_enableBottomNavigationBar" value="@bool/config_enableBottomNavigationBar"/>
+    <item target="bool/config_showHeadsUpNotificationOnBottom" value="@bool/config_showHeadsUpNotificationOnBottom"/>
 
     <item target="integer/config_topSystemBarType" value="@integer/config_topSystemBarType"/>
     <item target="integer/config_leftSystemBarType" value="@integer/config_leftSystemBarType"/>
@@ -30,4 +70,6 @@
     <item target="integer/config_leftSystemBarZOrder" value="@integer/config_leftSystemBarZOrder"/>
     <item target="integer/config_rightSystemBarZOrder" value="@integer/config_rightSystemBarZOrder"/>
     <item target="integer/config_bottomSystemBarZOrder" value="@integer/config_bottomSystemBarZOrder"/>
+
+    <item target="string/config_notificationPanelViewMediator" value="@string/config_notificationPanelViewMediator"/>
 </overlay>
\ No newline at end of file
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarGlobalRootComponent.java b/packages/CarSystemUI/src/com/android/systemui/CarGlobalRootComponent.java
new file mode 100644
index 0000000..552cadf
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/CarGlobalRootComponent.java
@@ -0,0 +1,42 @@
+/*
+ * 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;
+
+import com.android.systemui.dagger.GlobalRootComponent;
+
+import javax.inject.Singleton;
+
+import dagger.Component;
+
+/** Car subclass for GlobalRootComponent. */
+@Singleton
+@Component(
+        modules = {
+                CarSysUIComponentModule.class
+        })
+public interface CarGlobalRootComponent extends GlobalRootComponent {
+    /**
+     * Builder for a CarGlobalRootComponent.
+     */
+    @Component.Builder
+    interface Builder extends GlobalRootComponent.Builder {
+        CarGlobalRootComponent build();
+    }
+
+    @Override
+    CarSysUIComponent.Builder getSysUIComponent();
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSysUIComponent.java b/packages/CarSystemUI/src/com/android/systemui/CarSysUIComponent.java
new file mode 100644
index 0000000..b2f98ec
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSysUIComponent.java
@@ -0,0 +1,53 @@
+/*
+ * 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;
+
+import com.android.systemui.dagger.DependencyBinder;
+import com.android.systemui.dagger.DependencyProvider;
+import com.android.systemui.dagger.SysUIComponent;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.SystemServicesModule;
+import com.android.systemui.dagger.SystemUIModule;
+import com.android.systemui.onehanded.dagger.OneHandedModule;
+import com.android.systemui.pip.phone.dagger.PipModule;
+
+import dagger.Subcomponent;
+
+/**
+ * Dagger Subcomponent for Core SysUI.
+ */
+@SysUISingleton
+@Subcomponent(modules = {
+        CarComponentBinder.class,
+        DependencyProvider.class,
+        DependencyBinder.class,
+        PipModule.class,
+        OneHandedModule.class,
+        SystemServicesModule.class,
+        SystemUIModule.class,
+        CarSystemUIModule.class,
+        CarSystemUIBinder.class})
+public interface CarSysUIComponent extends SysUIComponent {
+
+    /**
+     * Builder for a CarSysUIComponent.
+     */
+    @Subcomponent.Builder
+    interface Builder extends SysUIComponent.Builder {
+        CarSysUIComponent build();
+    }
+}
diff --git a/media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl b/packages/CarSystemUI/src/com/android/systemui/CarSysUIComponentModule.java
similarity index 67%
copy from media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl
copy to packages/CarSystemUI/src/com/android/systemui/CarSysUIComponentModule.java
index b1f99e6..4de3166 100644
--- a/media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSysUIComponentModule.java
@@ -14,17 +14,15 @@
  * limitations under the License.
  */
 
-package android.media;
+package com.android.systemui;
 
-import android.media.AudioDeviceAttributes;
+import dagger.Module;
 
 /**
- * AIDL for AudioService to signal audio strategy-preferred device updates.
+ * Dagger module for including the CarSysUIComponent.
  *
- * {@hide}
+ * TODO(b/162923491): Remove or otherwise refactor this module. This is a stop gap.
  */
-oneway interface IStrategyPreferredDeviceDispatcher {
-
-    void dispatchPrefDeviceChanged(int strategyId, in AudioDeviceAttributes device);
-
+@Module(subcomponents = {CarSysUIComponent.class})
+public abstract class CarSysUIComponentModule {
 }
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java
index 03ea941..a65edc5 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java
@@ -19,7 +19,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 
-import com.android.systemui.dagger.SystemUIRootComponent;
+import com.android.systemui.dagger.GlobalRootComponent;
 
 import java.util.HashSet;
 import java.util.Set;
@@ -30,8 +30,8 @@
 public class CarSystemUIFactory extends SystemUIFactory {
 
     @Override
-    protected SystemUIRootComponent buildSystemUIRootComponent(Context context) {
-        return DaggerCarSystemUIRootComponent.builder()
+    protected GlobalRootComponent buildGlobalRootComponent(Context context) {
+        return DaggerCarGlobalRootComponent.builder()
                 .context(context)
                 .build();
     }
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
index f4e704e..290700f 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
@@ -22,24 +22,23 @@
 import android.content.Context;
 import android.os.Handler;
 import android.os.PowerManager;
-import android.view.IWindowManager;
 
 import com.android.keyguard.KeyguardViewController;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.car.CarDeviceProvisionedController;
 import com.android.systemui.car.CarDeviceProvisionedControllerImpl;
 import com.android.systemui.car.keyguard.CarKeyguardViewController;
+import com.android.systemui.car.notification.NotificationShadeWindowControllerImpl;
 import com.android.systemui.car.statusbar.DozeServiceHost;
-import com.android.systemui.car.statusbar.DummyNotificationShadeWindowController;
 import com.android.systemui.car.volume.CarVolumeDialogComponent;
-import com.android.systemui.dagger.SystemUIRootComponent;
+import com.android.systemui.dagger.GlobalRootComponent;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dock.DockManagerImpl;
 import com.android.systemui.doze.DozeHost;
-import com.android.systemui.pip.phone.PipMenuActivity;
-import com.android.systemui.pip.phone.dagger.PipMenuActivityClass;
 import com.android.systemui.plugins.qs.QSFactory;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.power.EnhancedEstimates;
@@ -52,12 +51,12 @@
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.ShadeControllerImpl;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -66,30 +65,30 @@
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.volume.VolumeDialogComponent;
-import com.android.systemui.wm.DisplaySystemBarsController;
-import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.common.DisplayImeController;
-import com.android.wm.shell.common.SystemWindows;
-import com.android.wm.shell.common.TransactionPool;
+import com.android.systemui.wmshell.CarWMShellModule;
 
 import javax.inject.Named;
-import javax.inject.Singleton;
 
 import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
 
-@Module(includes = {DividerModule.class, QSModule.class})
-public abstract class CarSystemUIModule {
+@Module(
+        includes = {
+                DividerModule.class,
+                QSModule.class,
+                CarWMShellModule.class
+        })
+abstract class CarSystemUIModule {
 
-    @Singleton
+    @SysUISingleton
     @Provides
     @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME)
     static boolean provideAllowNotificationLongPress() {
         return false;
     }
 
-    @Singleton
+    @SysUISingleton
     @Provides
     static HeadsUpManagerPhone provideHeadsUpManagerPhone(
             Context context,
@@ -101,7 +100,7 @@
                 groupManager, configurationController);
     }
 
-    @Singleton
+    @SysUISingleton
     @Provides
     @Named(LEAK_REPORT_EMAIL_NAME)
     static String provideLeakReportEmail() {
@@ -109,48 +108,12 @@
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static Recents provideRecents(Context context, RecentsImplementation recentsImplementation,
             CommandQueue commandQueue) {
         return new Recents(context, recentsImplementation, commandQueue);
     }
 
-    @Singleton
-    @Provides
-    static TransactionPool provideTransactionPool() {
-        return new TransactionPool();
-    }
-
-    @Singleton
-    @Provides
-    static DisplayController providerDisplayController(Context context, @Main Handler handler,
-            IWindowManager wmService) {
-        return new DisplayController(context, handler, wmService);
-    }
-
-    @Singleton
-    @Provides
-    static SystemWindows provideSystemWindows(DisplayController displayController,
-            IWindowManager wmService) {
-        return new SystemWindows(displayController, wmService);
-    }
-
-    @Singleton
-    @Provides
-    static DisplayImeController provideDisplayImeController(Context context,
-            IWindowManager wmService, DisplayController displayController,
-            @Main Handler mainHandler, TransactionPool transactionPool) {
-        return new DisplaySystemBarsController.Builder(context, wmService, displayController,
-                mainHandler, transactionPool).build();
-    }
-
-    @Singleton
-    @PipMenuActivityClass
-    @Provides
-    static Class<?> providePipMenuActivityClass() {
-        return PipMenuActivity.class;
-    }
-
     @Binds
     abstract HeadsUpManager bindHeadsUpManagerPhone(HeadsUpManagerPhone headsUpManagerPhone);
 
@@ -162,19 +125,20 @@
             NotificationLockscreenUserManagerImpl notificationLockscreenUserManager);
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static BatteryController provideBatteryController(Context context,
             EnhancedEstimates enhancedEstimates, PowerManager powerManager,
-            BroadcastDispatcher broadcastDispatcher, @Main Handler mainHandler,
+            BroadcastDispatcher broadcastDispatcher, DemoModeController demoModeController,
+            @Main Handler mainHandler,
             @Background Handler bgHandler) {
         BatteryController bC = new BatteryControllerImpl(context, enhancedEstimates, powerManager,
-                broadcastDispatcher, mainHandler, bgHandler);
+                broadcastDispatcher, demoModeController, mainHandler, bgHandler);
         bC.init();
         return bC;
     }
 
     @Binds
-    @Singleton
+    @SysUISingleton
     public abstract QSFactory bindQSFactory(QSFactoryImpl qsFactoryImpl);
 
     @Binds
@@ -188,8 +152,8 @@
     abstract ShadeController provideShadeController(ShadeControllerImpl shadeController);
 
     @Binds
-    abstract SystemUIRootComponent bindSystemUIRootComponent(
-            CarSystemUIRootComponent systemUIRootComponent);
+    abstract GlobalRootComponent bindGlobalRootComponent(
+            CarGlobalRootComponent globalRootComponent);
 
     @Binds
     abstract VolumeDialogComponent bindVolumeDialogComponent(
@@ -200,6 +164,10 @@
             CarKeyguardViewController carKeyguardViewController);
 
     @Binds
+    abstract NotificationShadeWindowController bindNotificationShadeController(
+            NotificationShadeWindowControllerImpl notificationPanelViewController);
+
+    @Binds
     abstract DeviceProvisionedController bindDeviceProvisionedController(
             CarDeviceProvisionedControllerImpl deviceProvisionedController);
 
@@ -208,9 +176,5 @@
             CarDeviceProvisionedControllerImpl deviceProvisionedController);
 
     @Binds
-    abstract NotificationShadeWindowController bindNotificationShadeWindowController(
-            DummyNotificationShadeWindowController notificationShadeWindowController);
-
-    @Binds
     abstract DozeHost bindDozeHost(DozeServiceHost dozeServiceHost);
 }
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIRootComponent.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIRootComponent.java
deleted file mode 100644
index ece3bee..0000000
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIRootComponent.java
+++ /dev/null
@@ -1,52 +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;
-
-import com.android.systemui.dagger.DependencyBinder;
-import com.android.systemui.dagger.DependencyProvider;
-import com.android.systemui.dagger.SystemServicesModule;
-import com.android.systemui.dagger.SystemUIModule;
-import com.android.systemui.dagger.SystemUIRootComponent;
-import com.android.systemui.onehanded.dagger.OneHandedModule;
-import com.android.systemui.pip.phone.dagger.PipModule;
-
-import javax.inject.Singleton;
-
-import dagger.Component;
-
-@Singleton
-@Component(
-        modules = {
-                CarComponentBinder.class,
-                DependencyProvider.class,
-                DependencyBinder.class,
-                PipModule.class,
-                OneHandedModule.class,
-                SystemServicesModule.class,
-                SystemUIModule.class,
-                CarSystemUIModule.class,
-                CarSystemUIBinder.class
-        })
-public interface CarSystemUIRootComponent extends SystemUIRootComponent {
-    /**
-     * Builder for a CarSystemUIRootComponent.
-     */
-    @Component.Builder
-    interface Builder extends SystemUIRootComponent.Builder {
-        CarSystemUIRootComponent build();
-    }
-}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/CarDeviceProvisionedControllerImpl.java b/packages/CarSystemUI/src/com/android/systemui/car/CarDeviceProvisionedControllerImpl.java
index 09e62d2..a2ba880 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/CarDeviceProvisionedControllerImpl.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/CarDeviceProvisionedControllerImpl.java
@@ -27,17 +27,17 @@
 
 import com.android.systemui.Dependency;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.statusbar.policy.DeviceProvisionedControllerImpl;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * A controller that monitors the status of SUW progress for each user in addition to the
  * functionality provided by {@link DeviceProvisionedControllerImpl}.
  */
-@Singleton
+@SysUISingleton
 public class CarDeviceProvisionedControllerImpl extends DeviceProvisionedControllerImpl implements
         CarDeviceProvisionedController {
     private static final Uri USER_SETUP_IN_PROGRESS_URI = Settings.Secure.getUriFor(
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/CarServiceProvider.java b/packages/CarSystemUI/src/com/android/systemui/car/CarServiceProvider.java
index 80ee371..5778d66 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/CarServiceProvider.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/CarServiceProvider.java
@@ -21,14 +21,15 @@
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.systemui.dagger.SysUISingleton;
+
 import java.util.ArrayList;
 import java.util.List;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /** Provides a common connection to the car service that can be shared. */
-@Singleton
+@SysUISingleton
 public class CarServiceProvider {
 
     private final Context mContext;
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/hvac/HvacController.java b/packages/CarSystemUI/src/com/android/systemui/car/hvac/HvacController.java
index 236a6a4..a4b6bfc 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/hvac/HvacController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/hvac/HvacController.java
@@ -29,6 +29,7 @@
 import android.view.ViewGroup;
 
 import com.android.systemui.car.CarServiceProvider;
+import com.android.systemui.dagger.SysUISingleton;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -40,13 +41,12 @@
 import java.util.Set;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Manages the connection to the Car service and delegates value changes to the registered
  * {@link TemperatureView}s
  */
-@Singleton
+@SysUISingleton
 public class HvacController {
     public static final String TAG = "HvacController";
     private static final boolean DEBUG = true;
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java
index 51a7245..276ddfb 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java
@@ -38,6 +38,7 @@
 import com.android.systemui.car.navigationbar.CarNavigationBarController;
 import com.android.systemui.car.window.OverlayViewController;
 import com.android.systemui.car.window.OverlayViewGlobalStateController;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.plugins.FalsingManager;
@@ -49,7 +50,6 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 
@@ -57,7 +57,7 @@
  * Automotive implementation of the {@link KeyguardViewController}. It controls the Keyguard View
  * that is mounted to the SystemUIOverlayWindow.
  */
-@Singleton
+@SysUISingleton
 public class CarKeyguardViewController extends OverlayViewController implements
         KeyguardViewController {
     private static final String TAG = "CarKeyguardViewController";
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewMediator.java b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewMediator.java
index 5a35c48..155b73e 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewMediator.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewMediator.java
@@ -18,15 +18,15 @@
 
 import com.android.systemui.car.userswitcher.FullScreenUserSwitcherViewController;
 import com.android.systemui.car.window.OverlayViewMediator;
+import com.android.systemui.dagger.SysUISingleton;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Manages events originating from the Keyguard service that cause Keyguard or other OverlayWindow
  * Components to appear or disappear.
  */
-@Singleton
+@SysUISingleton
 public class CarKeyguardViewMediator implements OverlayViewMediator {
 
     private final CarKeyguardViewController mCarKeyguardViewController;
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/ButtonRoleHolderController.java b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/ButtonRoleHolderController.java
index 5c83c02..f8cd20f 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/ButtonRoleHolderController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/ButtonRoleHolderController.java
@@ -30,13 +30,13 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.car.CarDeviceProvisionedController;
+import com.android.systemui.dagger.SysUISingleton;
 
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Some CarNavigationButtons can be associated to a {@link RoleManager} role. When they are, it is
@@ -46,7 +46,7 @@
  * This class monitors the current role holders for each role type and updates the button icon for
  * this buttons with have this feature enabled.
  */
-@Singleton
+@SysUISingleton
 public class ButtonRoleHolderController {
     private static final String TAG = "ButtonRoleHolderController";
 
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/ButtonSelectionStateController.java b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/ButtonSelectionStateController.java
index eedcfa5..aa6da89 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/ButtonSelectionStateController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/ButtonSelectionStateController.java
@@ -26,13 +26,14 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import com.android.systemui.dagger.SysUISingleton;
+
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * CarNavigationButtons can optionally have selection state that toggles certain visual indications
@@ -42,7 +43,7 @@
  * This class controls the selection state of CarNavigationButtons that have opted in to have such
  * selection state-dependent visual indications.
  */
-@Singleton
+@SysUISingleton
 public class ButtonSelectionStateController {
 
     private final Set<CarNavigationButton> mRegisteredViews = new HashSet<>();
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/ButtonSelectionStateListener.java b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/ButtonSelectionStateListener.java
index 1361798..d6216ba 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/ButtonSelectionStateListener.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/ButtonSelectionStateListener.java
@@ -19,16 +19,16 @@
 import android.app.ActivityTaskManager;
 import android.util.Log;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * An implementation of TaskStackChangeListener, that listens for changes in the system
  * task stack and notifies the navigation bar.
  */
-@Singleton
+@SysUISingleton
 class ButtonSelectionStateListener extends TaskStackChangeListener {
     private static final String TAG = ButtonSelectionStateListener.class.getSimpleName();
 
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBarController.java b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBarController.java
index fe26040..51a8838 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBarController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBarController.java
@@ -23,14 +23,14 @@
 import androidx.annotation.Nullable;
 
 import com.android.systemui.car.hvac.HvacController;
+import com.android.systemui.dagger.SysUISingleton;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 
 /** A single class which controls the navigation bar views. */
-@Singleton
+@SysUISingleton
 public class CarNavigationBarController {
 
     private final Context mContext;
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/NavigationBarViewFactory.java b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/NavigationBarViewFactory.java
index adf8d4d..a473bb7 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/NavigationBarViewFactory.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/NavigationBarViewFactory.java
@@ -26,12 +26,12 @@
 
 import com.android.car.ui.FocusParkingView;
 import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /** A factory that creates and caches views for navigation bars. */
-@Singleton
+@SysUISingleton
 public class NavigationBarViewFactory {
 
     private static final String TAG = NavigationBarViewFactory.class.getSimpleName();
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/SystemBarConfigs.java b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/SystemBarConfigs.java
index 3527bf9..143c444 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/SystemBarConfigs.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/SystemBarConfigs.java
@@ -29,6 +29,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 
 import java.lang.annotation.ElementType;
@@ -40,13 +41,12 @@
 import java.util.Set;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Reads configs for system bars for each side (TOP, BOTTOM, LEFT, and RIGHT) and returns the
  * corresponding {@link android.view.WindowManager.LayoutParams} per the configuration.
  */
-@Singleton
+@SysUISingleton
 public class SystemBarConfigs {
 
     private static final String TAG = SystemBarConfigs.class.getSimpleName();
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/BottomNotificationPanelViewMediator.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/BottomNotificationPanelViewMediator.java
index 7d353f5a..8468bef 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/notification/BottomNotificationPanelViewMediator.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/BottomNotificationPanelViewMediator.java
@@ -20,16 +20,16 @@
 import com.android.systemui.car.CarDeviceProvisionedController;
 import com.android.systemui.car.navigationbar.CarNavigationBarController;
 import com.android.systemui.car.window.OverlayPanelViewController;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Implementation of NotificationPanelViewMediator that sets the notification panel to be opened
  * from the top navigation bar.
  */
-@Singleton
+@SysUISingleton
 public class BottomNotificationPanelViewMediator extends NotificationPanelViewMediator {
 
     @Inject
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainer.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainer.java
index d4f72071..3b22a30 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainer.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainer.java
@@ -29,15 +29,15 @@
 import com.android.car.notification.headsup.CarHeadsUpNotificationContainer;
 import com.android.systemui.car.CarDeviceProvisionedController;
 import com.android.systemui.car.window.OverlayViewGlobalStateController;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * A controller for SysUI's HUN display.
  */
-@Singleton
+@SysUISingleton
 public class CarHeadsUpNotificationSystemContainer implements CarHeadsUpNotificationContainer {
     private final CarDeviceProvisionedController mCarDeviceProvisionedController;
     private final OverlayViewGlobalStateController mOverlayViewGlobalStateController;
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/CarNotificationModule.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/CarNotificationModule.java
index b7bc631..8a3bcfc 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/notification/CarNotificationModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/CarNotificationModule.java
@@ -25,8 +25,7 @@
 import com.android.car.notification.NotificationDataManager;
 import com.android.car.notification.headsup.CarHeadsUpNotificationContainer;
 import com.android.internal.statusbar.IStatusBarService;
-
-import javax.inject.Singleton;
+import com.android.systemui.dagger.SysUISingleton;
 
 import dagger.Binds;
 import dagger.Module;
@@ -38,26 +37,26 @@
 @Module
 public abstract class CarNotificationModule {
     @Provides
-    @Singleton
+    @SysUISingleton
     static NotificationClickHandlerFactory provideNotificationClickHandlerFactory(
             IStatusBarService barService) {
         return new NotificationClickHandlerFactory(barService);
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static NotificationDataManager provideNotificationDataManager() {
         return new NotificationDataManager();
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static CarUxRestrictionManagerWrapper provideCarUxRestrictionManagerWrapper() {
         return new CarUxRestrictionManagerWrapper();
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static CarNotificationListener provideCarNotificationListener(Context context,
             CarUxRestrictionManagerWrapper carUxRestrictionManagerWrapper,
             CarHeadsUpNotificationManager carHeadsUpNotificationManager,
@@ -69,7 +68,7 @@
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static CarHeadsUpNotificationManager provideCarHeadsUpNotificationManager(Context context,
             NotificationClickHandlerFactory notificationClickHandlerFactory,
             NotificationDataManager notificationDataManager,
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
index 8d58436..3b22fdb 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
@@ -49,6 +49,7 @@
 import com.android.systemui.car.CarServiceProvider;
 import com.android.systemui.car.window.OverlayPanelViewController;
 import com.android.systemui.car.window.OverlayViewGlobalStateController;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -59,10 +60,9 @@
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /** View controller for the notification panel. */
-@Singleton
+@SysUISingleton
 public class NotificationPanelViewController extends OverlayPanelViewController
         implements CommandQueue.Callbacks {
 
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewMediator.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewMediator.java
index 0c185ba..17b6b74 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewMediator.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewMediator.java
@@ -31,16 +31,16 @@
 import com.android.systemui.car.CarDeviceProvisionedController;
 import com.android.systemui.car.navigationbar.CarNavigationBarController;
 import com.android.systemui.car.window.OverlayViewMediator;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * The view mediator which attaches the view controller to other elements of the system ui. Disables
  * drag open behavior of the notification panel from any navigation bar.
  */
-@Singleton
+@SysUISingleton
 public class NotificationPanelViewMediator implements OverlayViewMediator,
         ConfigurationController.ConfigurationListener {
 
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationShadeWindowControllerImpl.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationShadeWindowControllerImpl.java
new file mode 100644
index 0000000..1a1da89
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationShadeWindowControllerImpl.java
@@ -0,0 +1,46 @@
+/*
+ * 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.car.notification;
+
+import com.android.systemui.car.window.OverlayViewGlobalStateController;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
+
+import javax.inject.Inject;
+
+/** The automotive version of the notification shade window controller. */
+@SysUISingleton
+public class NotificationShadeWindowControllerImpl implements
+        NotificationShadeWindowController {
+
+    private final OverlayViewGlobalStateController mController;
+
+    @Inject
+    public NotificationShadeWindowControllerImpl(OverlayViewGlobalStateController controller) {
+        mController = controller;
+    }
+
+    @Override
+    public void setForceDozeBrightness(boolean forceDozeBrightness) {
+        // No-op since dozing is not supported in Automotive devices.
+    }
+
+    @Override
+    public void setNotificationShadeFocusable(boolean focusable) {
+        mController.setWindowFocusable(focusable);
+    }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationVisibilityLogger.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationVisibilityLogger.java
index 44c8197..b263f72 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationVisibilityLogger.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationVisibilityLogger.java
@@ -24,19 +24,19 @@
 import com.android.car.notification.NotificationDataManager;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 
 import java.util.Set;
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Handles notification logging, in particular, logging which notifications are visible and which
  * are not.
  */
-@Singleton
+@SysUISingleton
 public class NotificationVisibilityLogger {
 
     private static final String TAG = "NotificationVisibilityLogger";
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/PowerManagerHelper.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/PowerManagerHelper.java
index 92a11d8..da43c54 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/notification/PowerManagerHelper.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/PowerManagerHelper.java
@@ -23,14 +23,14 @@
 import android.util.Log;
 
 import com.android.systemui.car.CarServiceProvider;
+import com.android.systemui.dagger.SysUISingleton;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Helper class for connecting to the {@link CarPowerManager} and listening for power state changes.
  */
-@Singleton
+@SysUISingleton
 public class PowerManagerHelper {
     public static final String TAG = "PowerManagerHelper";
 
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/TopNotificationPanelViewMediator.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/TopNotificationPanelViewMediator.java
index 89c9931..9bc5b74c 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/notification/TopNotificationPanelViewMediator.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/TopNotificationPanelViewMediator.java
@@ -20,16 +20,16 @@
 import com.android.systemui.car.CarDeviceProvisionedController;
 import com.android.systemui.car.navigationbar.CarNavigationBarController;
 import com.android.systemui.car.window.OverlayPanelViewController;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Implementation of NotificationPanelViewMediator that sets the notification panel to be opened
  * from the top navigation bar.
  */
-@Singleton
+@SysUISingleton
 public class TopNotificationPanelViewMediator extends NotificationPanelViewMediator {
 
     @Inject
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/sideloaded/SideLoadedAppController.java b/packages/CarSystemUI/src/com/android/systemui/car/sideloaded/SideLoadedAppController.java
index 6b41b35..b8d6964 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/sideloaded/SideLoadedAppController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/sideloaded/SideLoadedAppController.java
@@ -22,14 +22,14 @@
 import android.util.Log;
 
 import com.android.systemui.SystemUI;
+import com.android.systemui.dagger.SysUISingleton;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Controller responsible for detecting unsafe apps.
  */
-@Singleton
+@SysUISingleton
 public class SideLoadedAppController extends SystemUI {
     private static final String TAG = SideLoadedAppController.class.getSimpleName();
 
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/sideloaded/SideLoadedAppDetector.java b/packages/CarSystemUI/src/com/android/systemui/car/sideloaded/SideLoadedAppDetector.java
index a2cd044..eb32edb 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/sideloaded/SideLoadedAppDetector.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/sideloaded/SideLoadedAppDetector.java
@@ -29,19 +29,19 @@
 
 import com.android.systemui.R;
 import com.android.systemui.car.CarDeviceProvisionedController;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 
 import java.util.Arrays;
 import java.util.List;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * A class that detects unsafe apps.
  * An app is considered safe if is a system app or installed through allowed sources.
  */
-@Singleton
+@SysUISingleton
 public class SideLoadedAppDetector {
     private static final String TAG = SideLoadedAppDetector.class.getSimpleName();
 
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/sideloaded/SideLoadedAppStateController.java b/packages/CarSystemUI/src/com/android/systemui/car/sideloaded/SideLoadedAppStateController.java
index 1d66dda..5b4faa1 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/sideloaded/SideLoadedAppStateController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/sideloaded/SideLoadedAppStateController.java
@@ -19,13 +19,14 @@
 import android.util.Log;
 import android.view.Display;
 
+import com.android.systemui.dagger.SysUISingleton;
+
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Manager responsible for displaying proper UI when an unsafe app is detected.
  */
-@Singleton
+@SysUISingleton
 public class SideLoadedAppStateController {
     private static final String TAG = SideLoadedAppStateController.class.getSimpleName();
 
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/statusbar/DozeServiceHost.java b/packages/CarSystemUI/src/com/android/systemui/car/statusbar/DozeServiceHost.java
index d23660c..3fb3cd8 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/statusbar/DozeServiceHost.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/statusbar/DozeServiceHost.java
@@ -16,13 +16,13 @@
 
 package com.android.systemui.car.statusbar;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.doze.DozeHost;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /** No-op implementation of {@link DozeHost} for use by car sysui, which does not support dozing. */
-@Singleton
+@SysUISingleton
 public class DozeServiceHost implements DozeHost {
 
     @Inject
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/statusbar/DummyNotificationShadeWindowController.java b/packages/CarSystemUI/src/com/android/systemui/car/statusbar/DummyNotificationShadeWindowController.java
deleted file mode 100644
index 13f2b7e..0000000
--- a/packages/CarSystemUI/src/com/android/systemui/car/statusbar/DummyNotificationShadeWindowController.java
+++ /dev/null
@@ -1,74 +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.car.statusbar;
-
-import android.app.IActivityManager;
-import android.content.Context;
-import android.view.WindowManager;
-
-import com.android.systemui.car.window.SystemUIOverlayWindowController;
-import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.keyguard.KeyguardViewMediator;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.phone.BiometricUnlockController;
-import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-/**
- * A dummy implementation of {@link NotificationShadeWindowController}.
- *
- * TODO(b/155711562): This should be replaced with a longer term solution (i.e. separating
- * {@link BiometricUnlockController} from the views it depends on).
- */
-@Singleton
-public class DummyNotificationShadeWindowController extends NotificationShadeWindowController {
-    private final SystemUIOverlayWindowController mOverlayWindowController;
-
-    @Inject
-    public DummyNotificationShadeWindowController(Context context,
-            WindowManager windowManager, IActivityManager activityManager,
-            DozeParameters dozeParameters,
-            StatusBarStateController statusBarStateController,
-            ConfigurationController configurationController,
-            KeyguardViewMediator keyguardViewMediator,
-            KeyguardBypassController keyguardBypassController,
-            SysuiColorExtractor colorExtractor,
-            DumpManager dumpManager,
-            SystemUIOverlayWindowController overlayWindowController) {
-        super(context, windowManager, activityManager, dozeParameters, statusBarStateController,
-                configurationController, keyguardViewMediator, keyguardBypassController,
-                colorExtractor, dumpManager);
-        mOverlayWindowController = overlayWindowController;
-    }
-
-    @Override
-    public void setForceDozeBrightness(boolean forceDozeBrightness) {
-        // No op.
-    }
-
-    @Override
-    public void setNotificationShadeFocusable(boolean focusable) {
-        // The overlay window is the car sysui equivalent of the notification shade.
-        mOverlayWindowController.setWindowFocusable(focusable);
-    }
-}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullScreenUserSwitcherViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullScreenUserSwitcherViewController.java
index 1a8f19e..66bfb2d 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullScreenUserSwitcherViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullScreenUserSwitcherViewController.java
@@ -30,15 +30,15 @@
 import com.android.systemui.car.CarServiceProvider;
 import com.android.systemui.car.window.OverlayViewController;
 import com.android.systemui.car.window.OverlayViewGlobalStateController;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Controller for {@link R.layout#car_fullscreen_user_switcher}.
  */
-@Singleton
+@SysUISingleton
 public class FullScreenUserSwitcherViewController extends OverlayViewController {
     private final Context mContext;
     private final Resources mResources;
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullscreenUserSwitcherViewMediator.java b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullscreenUserSwitcherViewMediator.java
index 8b399f8..165fe63 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullscreenUserSwitcherViewMediator.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullscreenUserSwitcherViewMediator.java
@@ -18,16 +18,16 @@
 
 import com.android.systemui.car.keyguard.CarKeyguardViewController;
 import com.android.systemui.car.window.OverlayViewMediator;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.StatusBarState;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Manages the fullscreen user switcher and it's interactions with the keyguard.
  */
-@Singleton
+@SysUISingleton
 public class FullscreenUserSwitcherViewMediator implements OverlayViewMediator {
     private static final String TAG = FullscreenUserSwitcherViewMediator.class.getSimpleName();
 
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewController.java
index 0d77c13..6178cbd 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewController.java
@@ -38,15 +38,15 @@
 import com.android.systemui.R;
 import com.android.systemui.car.window.OverlayViewController;
 import com.android.systemui.car.window.OverlayViewGlobalStateController;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Handles showing and hiding UserSwitchTransitionView that is mounted to SystemUiOverlayWindow.
  */
-@Singleton
+@SysUISingleton
 public class UserSwitchTransitionViewController extends OverlayViewController {
     private static final String TAG = "UserSwitchTransition";
     private static final String ENABLE_DEVELOPER_MESSAGE_TRUE = "true";
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/volume/CarVolumeDialogComponent.java b/packages/CarSystemUI/src/com/android/systemui/car/volume/CarVolumeDialogComponent.java
index 98d24b1..4cdbfa3 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/volume/CarVolumeDialogComponent.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/volume/CarVolumeDialogComponent.java
@@ -19,18 +19,19 @@
 import android.content.Context;
 
 import com.android.systemui.car.CarServiceProvider;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.plugins.VolumeDialog;
 import com.android.systemui.volume.VolumeDialogComponent;
 import com.android.systemui.volume.VolumeDialogControllerImpl;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Allows for adding car specific dialog when the volume dialog is created.
  */
-@Singleton
+@SysUISingleton
 public class CarVolumeDialogComponent extends VolumeDialogComponent {
 
     private CarVolumeDialogImpl mCarVolumeDialog;
@@ -38,8 +39,9 @@
     @Inject
     public CarVolumeDialogComponent(Context context, KeyguardViewMediator keyguardViewMediator,
             VolumeDialogControllerImpl volumeDialogController,
+            DemoModeController demoModeController,
             CarServiceProvider carServiceProvider) {
-        super(context, keyguardViewMediator, volumeDialogController);
+        super(context, keyguardViewMediator, volumeDialogController, demoModeController);
         mCarVolumeDialog.setCarServiceProvider(carServiceProvider);
     }
 
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/volume/VolumeUI.java b/packages/CarSystemUI/src/com/android/systemui/car/volume/VolumeUI.java
index 03b61e0..b0321ab 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/volume/VolumeUI.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/volume/VolumeUI.java
@@ -27,6 +27,7 @@
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
 import com.android.systemui.car.CarServiceProvider;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.volume.VolumeDialogComponent;
 
@@ -34,12 +35,11 @@
 import java.io.PrintWriter;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 
 /** The entry point for controlling the volume ui in cars. */
-@Singleton
+@SysUISingleton
 public class VolumeUI extends SystemUI {
 
     private static final String TAG = "VolumeUI";
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java
index 2494242..22b6455 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java
@@ -27,6 +27,8 @@
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.systemui.dagger.SysUISingleton;
+
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
@@ -35,7 +37,6 @@
 import java.util.TreeMap;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * This controller is responsible for the following:
@@ -46,7 +47,7 @@
  * global state of SystemUIOverlayWindow.
  * </ul>
  */
-@Singleton
+@SysUISingleton
 public class OverlayViewGlobalStateController {
     private static final boolean DEBUG = false;
     private static final String TAG = OverlayViewGlobalStateController.class.getSimpleName();
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/window/SystemUIOverlayWindowController.java b/packages/CarSystemUI/src/com/android/systemui/car/window/SystemUIOverlayWindowController.java
index 029bd37..81b1bf9 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/window/SystemUIOverlayWindowController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/window/SystemUIOverlayWindowController.java
@@ -29,17 +29,17 @@
 import android.view.WindowManager;
 
 import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Controls the expansion state of the primary window which will contain all of the fullscreen sysui
  * behavior. This window still has a collapsed state in order to watch for swipe events to expand
  * this window for the notification panel.
  */
-@Singleton
+@SysUISingleton
 public class SystemUIOverlayWindowController implements
         ConfigurationController.ConfigurationListener {
 
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/window/SystemUIOverlayWindowManager.java b/packages/CarSystemUI/src/com/android/systemui/car/window/SystemUIOverlayWindowManager.java
index 8cca0ed..6395ebf 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/window/SystemUIOverlayWindowManager.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/window/SystemUIOverlayWindowManager.java
@@ -21,6 +21,7 @@
 
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
+import com.android.systemui.dagger.SysUISingleton;
 
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
@@ -28,13 +29,12 @@
 
 import javax.inject.Inject;
 import javax.inject.Provider;
-import javax.inject.Singleton;
 
 /**
  * Registers {@link OverlayViewMediator}(s) and synchronizes their calls to hide/show {@link
  * OverlayViewController}(s) to allow for the correct visibility of system bars.
  */
-@Singleton
+@SysUISingleton
 public class SystemUIOverlayWindowManager extends SystemUI {
     private static final String TAG = "SystemUIOverlayWM";
     private final Map<Class<?>, Provider<OverlayViewMediator>>
diff --git a/packages/CarSystemUI/src/com/android/systemui/wmshell/CarWMShellModule.java b/packages/CarSystemUI/src/com/android/systemui/wmshell/CarWMShellModule.java
new file mode 100644
index 0000000..2324c3d
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/wmshell/CarWMShellModule.java
@@ -0,0 +1,54 @@
+/*
+ * 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.wmshell;
+
+import android.content.Context;
+import android.os.Handler;
+import android.view.IWindowManager;
+
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.pip.phone.PipMenuActivity;
+import com.android.systemui.pip.phone.dagger.PipMenuActivityClass;
+import com.android.systemui.wm.DisplaySystemBarsController;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.TransactionPool;
+
+import dagger.Module;
+import dagger.Provides;
+
+/** Provides dependencies from {@link com.android.wm.shell} for CarSystemUI. */
+@Module(includes = WMShellBaseModule.class)
+public class CarWMShellModule {
+    @SysUISingleton
+    @Provides
+    DisplayImeController provideDisplayImeController(Context context,
+            IWindowManager wmService, DisplayController displayController,
+            @Main Handler mainHandler, TransactionPool transactionPool) {
+        return new DisplaySystemBarsController.Builder(context, wmService, displayController,
+                mainHandler, transactionPool).build();
+    }
+
+    /** TODO(b/150319024): PipMenuActivity will move to a Window */
+    @SysUISingleton
+    @PipMenuActivityClass
+    @Provides
+    Class<?> providePipMenuActivityClass() {
+        return PipMenuActivity.class;
+    }
+}
diff --git a/packages/PrintSpooler/res/values-as/strings.xml b/packages/PrintSpooler/res/values-as/strings.xml
index a93fceb..b6b287f 100644
--- a/packages/PrintSpooler/res/values-as/strings.xml
+++ b/packages/PrintSpooler/res/values-as/strings.xml
@@ -47,7 +47,7 @@
     <string name="savetopdf_button" msgid="2976186791686924743">"PDFৰ জৰিয়তে ছেভ কৰক"</string>
     <string name="print_options_expanded" msgid="6944679157471691859">"প্ৰিণ্ট বিকল্পসমূহ বিস্তাৰ কৰা হ’ল"</string>
     <string name="print_options_collapsed" msgid="7455930445670414332">"প্ৰিণ্ট বিকল্পসমূহ সংকুচিত কৰা হ’ল"</string>
-    <string name="search" msgid="5421724265322228497">"সন্ধান কৰক"</string>
+    <string name="search" msgid="5421724265322228497">"Search"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"সকলো প্ৰিণ্টাৰ"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"সেৱা যোগ কৰক"</string>
     <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"সন্ধান বাকচটো দেখুওৱা হ’ল"</string>
diff --git a/packages/PrintSpooler/res/values-bn/strings.xml b/packages/PrintSpooler/res/values-bn/strings.xml
index 637becb..b2e1eed 100644
--- a/packages/PrintSpooler/res/values-bn/strings.xml
+++ b/packages/PrintSpooler/res/values-bn/strings.xml
@@ -47,7 +47,7 @@
     <string name="savetopdf_button" msgid="2976186791686924743">"পিডিএফ হিসাবে সেভ করুন"</string>
     <string name="print_options_expanded" msgid="6944679157471691859">"প্রিন্ট বিকল্প প্রসারিত হয়েছে"</string>
     <string name="print_options_collapsed" msgid="7455930445670414332">"প্রিন্ট বিকল্প সংকুচিত হয়েছে"</string>
-    <string name="search" msgid="5421724265322228497">"খুঁজুন"</string>
+    <string name="search" msgid="5421724265322228497">"সার্চ"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"সমস্ত প্রিন্টার"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"পরিষেবা যোগ করুন"</string>
     <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"সার্চ বাক্স দেখানো হচ্ছে"</string>
diff --git a/packages/PrintSpooler/res/values-kn/strings.xml b/packages/PrintSpooler/res/values-kn/strings.xml
index 150ede4..261fe4b 100644
--- a/packages/PrintSpooler/res/values-kn/strings.xml
+++ b/packages/PrintSpooler/res/values-kn/strings.xml
@@ -47,7 +47,7 @@
     <string name="savetopdf_button" msgid="2976186791686924743">"PDF ಗೆ ಉಳಿಸು"</string>
     <string name="print_options_expanded" msgid="6944679157471691859">"ಪ್ರಿಂಟ್ ಆಯ್ಕೆಗಳನ್ನು ವಿಸ್ತರಿಸಲಾಗಿದೆ"</string>
     <string name="print_options_collapsed" msgid="7455930445670414332">"ಪ್ರಿಂಟ್ ಆಯ್ಕೆಗಳನ್ನು ಮುಚ್ಚಲಾಗಿದೆ"</string>
-    <string name="search" msgid="5421724265322228497">"ಹುಡುಕಿ"</string>
+    <string name="search" msgid="5421724265322228497">"Search"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"ಎಲ್ಲಾ ಪ್ರಿಂಟರ್‌ಗಳು"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"ಸೇವೆಯನ್ನು ಸೇರಿಸು"</string>
     <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"ಹುಡುಕಾಟ ಪೆಟ್ಟಿಗೆಯನ್ನು ತೋರಿಸಲಾಗಿದೆ"</string>
diff --git a/packages/PrintSpooler/res/values-ml/strings.xml b/packages/PrintSpooler/res/values-ml/strings.xml
index dbcd34b..73af95d 100644
--- a/packages/PrintSpooler/res/values-ml/strings.xml
+++ b/packages/PrintSpooler/res/values-ml/strings.xml
@@ -47,7 +47,7 @@
     <string name="savetopdf_button" msgid="2976186791686924743">"PDF-ൽ സംരക്ഷിക്കുക"</string>
     <string name="print_options_expanded" msgid="6944679157471691859">"പ്രിന്റ് ചെയ്യാനുള്ള ഓപ്‌ഷനുകൾ വിപുലീകരിച്ചു"</string>
     <string name="print_options_collapsed" msgid="7455930445670414332">"പ്രിന്റ് ചെയ്യാനുള്ള ഓപ്‌ഷനുകൾ ചുരുക്കി"</string>
-    <string name="search" msgid="5421724265322228497">"തിരയൽ"</string>
+    <string name="search" msgid="5421724265322228497">"Search"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"എല്ലാ പ്രിന്ററുകളും"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"സേവനം ചേർക്കുക"</string>
     <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"തിരയൽ ബോക്‌സ് ദൃശ്യമാക്കിയിരിക്കുന്നു"</string>
diff --git a/packages/PrintSpooler/res/values-mr/strings.xml b/packages/PrintSpooler/res/values-mr/strings.xml
index 44456b4..4d7e919 100644
--- a/packages/PrintSpooler/res/values-mr/strings.xml
+++ b/packages/PrintSpooler/res/values-mr/strings.xml
@@ -47,7 +47,7 @@
     <string name="savetopdf_button" msgid="2976186791686924743">"पीडीएफ वर सेव्ह करा"</string>
     <string name="print_options_expanded" msgid="6944679157471691859">"प्रिंट पर्याय विस्तृत झाले"</string>
     <string name="print_options_collapsed" msgid="7455930445670414332">"प्रिंट पर्याय संक्षिप्त झाले"</string>
-    <string name="search" msgid="5421724265322228497">"शोध"</string>
+    <string name="search" msgid="5421724265322228497">"Search"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"सर्व प्रिंटर"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"सेवा जोडा"</string>
     <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"शोध बॉक्स दर्शविला"</string>
diff --git a/packages/PrintSpooler/res/values-or/strings.xml b/packages/PrintSpooler/res/values-or/strings.xml
index a1675fa..7000b95 100644
--- a/packages/PrintSpooler/res/values-or/strings.xml
+++ b/packages/PrintSpooler/res/values-or/strings.xml
@@ -47,7 +47,7 @@
     <string name="savetopdf_button" msgid="2976186791686924743">"PDFରେ ସେଭ୍‍ କରନ୍ତୁ"</string>
     <string name="print_options_expanded" msgid="6944679157471691859">"ପ୍ରିଣ୍ଟ ବିକଳ୍ପକୁ ବଡ଼ କରାଯାଇଛି"</string>
     <string name="print_options_collapsed" msgid="7455930445670414332">"ପ୍ରିଣ୍ଟ ବିକଳ୍ପକୁ ଛୋଟ କରାଯାଇଛି"</string>
-    <string name="search" msgid="5421724265322228497">"ସର୍ଚ୍ଚ କରନ୍ତୁ"</string>
+    <string name="search" msgid="5421724265322228497">"Search"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"ସମସ୍ତ ପ୍ରିଣ୍ଟର୍‌"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"ସେବା ଯୋଗ କରନ୍ତୁ"</string>
     <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"ସର୍ଚ୍ଚ ବକ୍ସ ଦେଖାଯାଇଛି"</string>
diff --git a/packages/PrintSpooler/res/values-te/strings.xml b/packages/PrintSpooler/res/values-te/strings.xml
index b01b50a..79944bb 100644
--- a/packages/PrintSpooler/res/values-te/strings.xml
+++ b/packages/PrintSpooler/res/values-te/strings.xml
@@ -47,7 +47,7 @@
     <string name="savetopdf_button" msgid="2976186791686924743">"PDF వలె సేవ్ చేయి"</string>
     <string name="print_options_expanded" msgid="6944679157471691859">"ముద్రణ ఎంపికలు విస్తరించబడ్డాయి"</string>
     <string name="print_options_collapsed" msgid="7455930445670414332">"ముద్రణ ఎంపికలు కుదించబడ్డాయి"</string>
-    <string name="search" msgid="5421724265322228497">"వెతుకు"</string>
+    <string name="search" msgid="5421724265322228497">"సెర్చ్"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"అన్ని ప్రింటర్‌లు"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"సేవను జోడించు"</string>
     <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"శోధన పెట్టె చూపబడింది"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index 4b4861a..59d8acb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -298,18 +298,12 @@
             CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
             if (cachedDevice == null) {
                 cachedDevice = mDeviceManager.addDevice(device);
-                Log.d(TAG, "DeviceFoundHandler created new CachedBluetoothDevice: "
-                        + cachedDevice);
+                Log.d(TAG, "DeviceFoundHandler created new CachedBluetoothDevice");
             } else if (cachedDevice.getBondState() == BluetoothDevice.BOND_BONDED
                     && !cachedDevice.getDevice().isConnected()) {
                 // Dispatch device add callback to show bonded but
                 // not connected devices in discovery mode
                 dispatchDeviceAdded(cachedDevice);
-                Log.d(TAG, "DeviceFoundHandler found bonded and not connected device:"
-                        + cachedDevice);
-            } else {
-                Log.d(TAG, "DeviceFoundHandler found existing CachedBluetoothDevice:"
-                        + cachedDevice);
             }
             cachedDevice.setRssi(rssi);
             cachedDevice.setJustDiscovered(true);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 287f804..4c80b91 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -151,8 +151,8 @@
 
     void onProfileStateChanged(LocalBluetoothProfile profile, int newProfileState) {
         if (BluetoothUtils.D) {
-            Log.d(TAG, "onProfileStateChanged: profile " + profile + ", device=" + mDevice
-                    + ", newProfileState " + newProfileState);
+            Log.d(TAG, "onProfileStateChanged: profile " + profile + ", device "
+                    + mDevice.getAlias() + ", newProfileState " + newProfileState);
         }
         if (mLocalAdapter.getState() == BluetoothAdapter.STATE_TURNING_OFF)
         {
@@ -290,9 +290,6 @@
     }
 
     public void setHiSyncId(long id) {
-        if (BluetoothUtils.D) {
-            Log.d(TAG, "setHiSyncId: mDevice " + mDevice + ", id " + id);
-        }
         mHiSyncId = id;
     }
 
@@ -638,7 +635,7 @@
         }
 
         if (BluetoothUtils.D) {
-            Log.e(TAG, "updating profiles for " + mDevice.getAlias() + ", " + mDevice);
+            Log.d(TAG, "updating profiles for " + mDevice.getAlias());
             BluetoothClass bluetoothClass = mDevice.getBluetoothClass();
 
             if (bluetoothClass != null) Log.v(TAG, "Class: " + bluetoothClass.toString());
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 9d06c84..72a6074 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -465,7 +465,16 @@
             synchronized (mMediaDevicesLock) {
                 mMediaDevices.clear();
                 mMediaDevices.addAll(devices);
-                mMediaDevices.addAll(buildDisconnectedBluetoothDevice());
+                // Add disconnected bluetooth devices only when phone output device is available.
+                for (MediaDevice device : devices) {
+                    final int type = device.getDeviceType();
+                    if (type == MediaDevice.MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE
+                            || type == MediaDevice.MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE
+                            || type == MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE) {
+                        mMediaDevices.addAll(buildDisconnectedBluetoothDevice());
+                        break;
+                    }
+                }
             }
 
             final MediaDevice infoMediaDevice = mInfoMediaManager.getCurrentConnectedDevice();
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index b7ae3dc..bc58bfc 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -189,10 +189,12 @@
                 }
             }
             updateStatusLabel();
+            mCallback.run();
         } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) {
             // Default to -200 as its below WifiManager.MIN_RSSI.
             updateRssi(intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200));
             updateStatusLabel();
+            mCallback.run();
         }
     }
 
@@ -218,13 +220,15 @@
             return;
         }
         NetworkCapabilities networkCapabilities;
-        final Network currentWifiNetwork = mWifiManager.getCurrentNetwork();
-        if (currentWifiNetwork != null && currentWifiNetwork.equals(mDefaultNetwork)) {
+        isDefaultNetwork = false;
+        if (mDefaultNetworkCapabilities != null) {
+            isDefaultNetwork = mDefaultNetworkCapabilities.hasTransport(
+                    NetworkCapabilities.TRANSPORT_WIFI);
+        }
+        if (isDefaultNetwork) {
             // Wifi is connected and the default network.
-            isDefaultNetwork = true;
             networkCapabilities = mDefaultNetworkCapabilities;
         } else {
-            isDefaultNetwork = false;
             networkCapabilities = mConnectivityManager.getNetworkCapabilities(
                     mWifiManager.getCurrentNetwork());
         }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
index a654fd4..8e850b2 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
@@ -585,6 +585,7 @@
         when(device1.getId()).thenReturn(TEST_DEVICE_ID_1);
         when(device2.getId()).thenReturn(TEST_DEVICE_ID_2);
         when(device3.getId()).thenReturn(TEST_DEVICE_ID_3);
+        when(device1.getDeviceType()).thenReturn(MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE);
         when(mLocalMediaManager.mPhoneDevice.getId()).thenReturn("test_phone_id");
 
         assertThat(mLocalMediaManager.mMediaDevices).hasSize(2);
@@ -683,6 +684,7 @@
         when(device1.getId()).thenReturn(TEST_DEVICE_ID_1);
         when(device2.getId()).thenReturn(TEST_DEVICE_ID_2);
         when(device3.getId()).thenReturn(TEST_DEVICE_ID_3);
+        when(device1.getDeviceType()).thenReturn(MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE);
         when(mLocalMediaManager.mPhoneDevice.getId()).thenReturn("test_phone_id");
 
         assertThat(mLocalMediaManager.mMediaDevices).hasSize(2);
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 4eea8ad..bcd2ff7 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -165,6 +165,7 @@
         Settings.Secure.AWARE_TAP_PAUSE_TOUCH_COUNT,
         Settings.Secure.PEOPLE_STRIP,
         Settings.Secure.MEDIA_CONTROLS_RESUME,
+        Settings.Secure.MEDIA_CONTROLS_RESUME_BLOCKED,
         Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE,
         Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
         Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY,
@@ -173,5 +174,7 @@
         Settings.Secure.TAPS_APP_TO_EXIT,
         Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED,
         Settings.Secure.PANIC_GESTURE_ENABLED,
+        Settings.Secure.PANIC_SOUND_ENABLED,
+        Settings.Secure.ADAPTIVE_CONNECTIVITY_ENABLED
     };
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index c68ddbd..3630f25 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -244,6 +244,8 @@
         VALIDATORS.put(Secure.TAP_GESTURE, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.PEOPLE_STRIP, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.MEDIA_CONTROLS_RESUME, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.MEDIA_CONTROLS_RESUME_BLOCKED,
+                COLON_SEPARATED_PACKAGE_LIST_VALIDATOR);
         VALIDATORS.put(Secure.ACCESSIBILITY_MAGNIFICATION_MODE,
                 new InclusiveIntegerRangeValidator(
                         Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN,
@@ -260,5 +262,7 @@
         VALIDATORS.put(Secure.TAPS_APP_TO_EXIT, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.PANIC_GESTURE_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.PANIC_SOUND_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.ADAPTIVE_CONNECTIVITY_ENABLED, BOOLEAN_VALIDATOR);
     }
 }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index fa06e14..66be8dd 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -773,29 +773,29 @@
                 Settings.Global.GPU_DEBUG_LAYERS_GLES,
                 GlobalSettingsProto.Gpu.DEBUG_LAYERS_GLES);
         dumpSetting(s, p,
-                Settings.Global.GAME_DRIVER_ALL_APPS,
-                GlobalSettingsProto.Gpu.GAME_DRIVER_ALL_APPS);
+                Settings.Global.UPDATABLE_DRIVER_ALL_APPS,
+                GlobalSettingsProto.Gpu.UPDATABLE_DRIVER_ALL_APPS);
         dumpSetting(s, p,
-                Settings.Global.GAME_DRIVER_OPT_IN_APPS,
-                GlobalSettingsProto.Gpu.GAME_DRIVER_OPT_IN_APPS);
+                Settings.Global.UPDATABLE_DRIVER_PRODUCTION_OPT_IN_APPS,
+                GlobalSettingsProto.Gpu.UPDATABLE_DRIVER_PRODUCTION_OPT_IN_APPS);
         dumpSetting(s, p,
-                Settings.Global.GAME_DRIVER_PRERELEASE_OPT_IN_APPS,
-                GlobalSettingsProto.Gpu.GAME_DRIVER_PRERELEASE_OPT_IN_APPS);
+                Settings.Global.UPDATABLE_DRIVER_PRERELEASE_OPT_IN_APPS,
+                GlobalSettingsProto.Gpu.UPDATABLE_DRIVER_PRERELEASE_OPT_IN_APPS);
         dumpSetting(s, p,
-                Settings.Global.GAME_DRIVER_OPT_OUT_APPS,
-                GlobalSettingsProto.Gpu.GAME_DRIVER_OPT_OUT_APPS);
+                Settings.Global.UPDATABLE_DRIVER_PRODUCTION_OPT_OUT_APPS,
+                GlobalSettingsProto.Gpu.UPDATABLE_DRIVER_PRODUCTION_OPT_OUT_APPS);
         dumpSetting(s, p,
-                Settings.Global.GAME_DRIVER_DENYLIST,
-                GlobalSettingsProto.Gpu.GAME_DRIVER_DENYLIST);
+                Settings.Global.UPDATABLE_DRIVER_PRODUCTION_DENYLIST,
+                GlobalSettingsProto.Gpu.UPDATABLE_DRIVER_PRODUCTION_DENYLIST);
         dumpSetting(s, p,
-                Settings.Global.GAME_DRIVER_ALLOWLIST,
-                GlobalSettingsProto.Gpu.GAME_DRIVER_ALLOWLIST);
+                Settings.Global.UPDATABLE_DRIVER_PRODUCTION_ALLOWLIST,
+                GlobalSettingsProto.Gpu.UPDATABLE_DRIVER_PRODUCTION_ALLOWLIST);
         dumpSetting(s, p,
-                Settings.Global.GAME_DRIVER_DENYLISTS,
-                GlobalSettingsProto.Gpu.GAME_DRIVER_DENYLISTS);
+                Settings.Global.UPDATABLE_DRIVER_PRODUCTION_DENYLISTS,
+                GlobalSettingsProto.Gpu.UPDATABLE_DRIVER_PRODUCTION_DENYLISTS);
         dumpSetting(s, p,
-                Settings.Global.GAME_DRIVER_SPHAL_LIBRARIES,
-                GlobalSettingsProto.Gpu.GAME_DRIVER_SPHAL_LIBRARIES);
+                Settings.Global.UPDATABLE_DRIVER_SPHAL_LIBRARIES,
+                GlobalSettingsProto.Gpu.UPDATABLE_DRIVER_SPHAL_LIBRARIES);
         p.end(gpuToken);
 
         final long hdmiToken = p.start(GlobalSettingsProto.HDMI);
@@ -1976,6 +1976,9 @@
         dumpSetting(s, p,
                 Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS,
                 SecureSettingsProto.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS);
+        dumpSetting(s, p,
+                Settings.Secure.ADAPTIVE_CONNECTIVITY_ENABLED,
+                SecureSettingsProto.ADAPTIVE_CONNECTIVITY_ENABLED);
 
         final long controlsToken = p.start(SecureSettingsProto.CONTROLS);
         dumpSetting(s, p,
@@ -2028,6 +2031,9 @@
         dumpSetting(s, p,
                 Settings.Secure.PANIC_GESTURE_ENABLED,
                 SecureSettingsProto.EmergencyResponse.PANIC_GESTURE_ENABLED);
+        dumpSetting(s, p,
+                Settings.Secure.PANIC_SOUND_ENABLED,
+                SecureSettingsProto.EmergencyResponse.PANIC_SOUND_ENABLED);
         p.end(emergencyResponseToken);
 
         dumpSetting(s, p,
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 4bb8f45..0ac3355 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -504,14 +504,14 @@
                     Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_PKGS,
                     Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES,
                     Settings.Global.GLOBAL_SETTINGS_ANGLE_ALLOWLIST,
-                    Settings.Global.GAME_DRIVER_ALL_APPS,
-                    Settings.Global.GAME_DRIVER_OPT_IN_APPS,
-                    Settings.Global.GAME_DRIVER_PRERELEASE_OPT_IN_APPS,
-                    Settings.Global.GAME_DRIVER_OPT_OUT_APPS,
-                    Settings.Global.GAME_DRIVER_DENYLISTS,
-                    Settings.Global.GAME_DRIVER_DENYLIST,
-                    Settings.Global.GAME_DRIVER_ALLOWLIST,
-                    Settings.Global.GAME_DRIVER_SPHAL_LIBRARIES,
+                    Settings.Global.UPDATABLE_DRIVER_ALL_APPS,
+                    Settings.Global.UPDATABLE_DRIVER_PRODUCTION_OPT_IN_APPS,
+                    Settings.Global.UPDATABLE_DRIVER_PRERELEASE_OPT_IN_APPS,
+                    Settings.Global.UPDATABLE_DRIVER_PRODUCTION_OPT_OUT_APPS,
+                    Settings.Global.UPDATABLE_DRIVER_PRODUCTION_DENYLISTS,
+                    Settings.Global.UPDATABLE_DRIVER_PRODUCTION_DENYLIST,
+                    Settings.Global.UPDATABLE_DRIVER_PRODUCTION_ALLOWLIST,
+                    Settings.Global.UPDATABLE_DRIVER_SPHAL_LIBRARIES,
                     Settings.Global.GLOBAL_SETTINGS_SHOW_ANGLE_IN_USE_DIALOG_BOX,
                     Settings.Global.GPU_DEBUG_LAYER_APP,
                     Settings.Global.ENABLE_GNSS_RAW_MEAS_FULL_TRACKING,
diff --git a/packages/SoundPicker/res/values-ar/strings.xml b/packages/SoundPicker/res/values-ar/strings.xml
index a917955..f8844e9 100644
--- a/packages/SoundPicker/res/values-ar/strings.xml
+++ b/packages/SoundPicker/res/values-ar/strings.xml
@@ -25,5 +25,5 @@
     <string name="delete_ringtone_text" msgid="201443984070732499">"حذف"</string>
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"يتعذر إضافة نغمة رنين مخصصة"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"يتعذر حذف نغمة الرنين المخصصة"</string>
-    <string name="app_label" msgid="3091611356093417332">"Sounds"</string>
+    <string name="app_label" msgid="3091611356093417332">"الأصوات"</string>
 </resources>
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 98d3553..4ce9f5a 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -240,6 +240,8 @@
     <!-- Listen app op changes -->
     <uses-permission android:name="android.permission.WATCH_APPOPS" />
     <uses-permission android:name="android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS" />
+    <!-- For handling silent audio recordings -->
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" />
 
     <!-- to read and change hvac values in a car -->
     <uses-permission android:name="android.car.permission.CONTROL_CAR_CLIMATE" />
@@ -267,6 +269,7 @@
     <!-- Permission to make accessibility service access Bubbles -->
     <uses-permission android:name="android.permission.ADD_TRUSTED_DISPLAY" />
 
+
     <protected-broadcast android:name="com.android.settingslib.action.REGISTER_SLICE_RECEIVER" />
     <protected-broadcast android:name="com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER" />
     <protected-broadcast android:name="com.android.settings.flashlight.action.FLASHLIGHT_CHANGED" />
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java
index d2112a0..883f4de 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java
@@ -75,11 +75,6 @@
     public MenuItem getLongpressMenuItem(Context context);
 
     /**
-     * @return the {@link MenuItem} to display when app ops icons are pressed.
-     */
-    public MenuItem getAppOpsMenuItem(Context context);
-
-    /**
      * @return the {@link MenuItem} to display when feedback icon is pressed.
      */
     public MenuItem getFeedbackMenuItem(Context context);
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java
index 0d960f0..6c5c4ef 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java
@@ -39,6 +39,10 @@
      */
     boolean isDozing();
 
+    /**
+     * Is the status bar panel expanded.
+     */
+    boolean isExpanded();
 
     /**
      * Is device pulsing.
@@ -113,5 +117,10 @@
          * Callback to be notified when the pulsing state changes
          */
         default void onPulsingChanged(boolean pulsing) {}
+
+        /**
+         * Callback to be notified when the expanded state of the status bar changes
+         */
+        default void onExpandedChanged(boolean isExpanded) {}
     }
 }
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index 14097b1..b42d71a 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -1,9 +1,9 @@
--keep class com.android.systemui.statusbar.policy.KeyButtonView {
+-keep class com.android.systemui.navigationbar.buttons.KeyButtonView {
   public float getDrawingAlpha();
   public void setDrawingAlpha(float);
 }
 
--keep class com.android.systemui.statusbar.policy.KeyButtonRipple {
+-keep class com.android.systemui.navigationbar.buttons.KeyButtonRipple {
   public float getGlowAlpha();
   public float getGlowScale();
   public void setGlowAlpha(float);
@@ -41,4 +41,8 @@
     public <init>(android.content.Context);
 }
 
--keep class com.android.wm.shell.*
\ No newline at end of file
+-keep class com.android.wm.shell.*
+
+-keep class com.android.systemui.dagger.GlobalRootComponent { *; }
+-keep class com.android.systemui.dagger.GlobalRootComponent$SysUIComponentImpl { *; }
+-keep class com.android.systemui.dagger.Dagger** { *; }
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/back.xml b/packages/SystemUI/res/layout/back.xml
index 4e8726b..046aecd 100644
--- a/packages/SystemUI/res/layout/back.xml
+++ b/packages/SystemUI/res/layout/back.xml
@@ -14,7 +14,7 @@
      limitations under the License.
 -->
 
-<com.android.systemui.statusbar.policy.KeyButtonView
+<com.android.systemui.navigationbar.buttons.KeyButtonView
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:systemui="http://schemas.android.com/apk/res-auto"
     android:id="@+id/back"
diff --git a/packages/SystemUI/res/layout/contextual.xml b/packages/SystemUI/res/layout/contextual.xml
index 90a7768..2cd7926 100644
--- a/packages/SystemUI/res/layout/contextual.xml
+++ b/packages/SystemUI/res/layout/contextual.xml
@@ -24,7 +24,7 @@
              android:clipChildren="false"
              android:clipToPadding="false"
              >
-    <com.android.systemui.statusbar.policy.KeyButtonView
+    <com.android.systemui.navigationbar.buttons.KeyButtonView
         android:id="@+id/menu"
         android:layout_height="match_parent"
         android:layout_width="match_parent"
@@ -47,7 +47,7 @@
              android:layout_height="match_parent"
              android:visibility="invisible"
     />
-    <com.android.systemui.statusbar.policy.KeyButtonView
+    <com.android.systemui.navigationbar.buttons.KeyButtonView
         android:id="@+id/accessibility_button"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
diff --git a/packages/SystemUI/res/layout/custom_key.xml b/packages/SystemUI/res/layout/custom_key.xml
index 0b5cb72..dc65777 100644
--- a/packages/SystemUI/res/layout/custom_key.xml
+++ b/packages/SystemUI/res/layout/custom_key.xml
@@ -13,7 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<com.android.systemui.statusbar.policy.KeyButtonView
+<com.android.systemui.navigationbar.buttons.KeyButtonView
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:systemui="http://schemas.android.com/apk/res-auto"
     android:layout_width="@dimen/navigation_side_padding"
diff --git a/packages/SystemUI/res/layout/global_screenshot_action_chip.xml b/packages/SystemUI/res/layout/global_screenshot_action_chip.xml
index 46396e3..4b3534b 100644
--- a/packages/SystemUI/res/layout/global_screenshot_action_chip.xml
+++ b/packages/SystemUI/res/layout/global_screenshot_action_chip.xml
@@ -32,6 +32,7 @@
         android:gravity="center">
         <ImageView
             android:id="@+id/screenshot_action_chip_icon"
+            android:tint="@*android:color/accent_device_default"
             android:layout_width="@dimen/screenshot_action_chip_icon_size"
             android:layout_height="@dimen/screenshot_action_chip_icon_size"
             android:layout_marginStart="@dimen/screenshot_action_chip_padding_start"
diff --git a/packages/SystemUI/res/layout/home.xml b/packages/SystemUI/res/layout/home.xml
index 9586327..84eed6a 100644
--- a/packages/SystemUI/res/layout/home.xml
+++ b/packages/SystemUI/res/layout/home.xml
@@ -13,7 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<com.android.systemui.statusbar.policy.KeyButtonView
+<com.android.systemui.navigationbar.buttons.KeyButtonView
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:systemui="http://schemas.android.com/apk/res-auto"
     android:id="@+id/home"
diff --git a/packages/SystemUI/res/layout/home_handle.xml b/packages/SystemUI/res/layout/home_handle.xml
index 54a0b9f..c9d3f98 100644
--- a/packages/SystemUI/res/layout/home_handle.xml
+++ b/packages/SystemUI/res/layout/home_handle.xml
@@ -15,7 +15,7 @@
   ~ limitations under the License.
   -->
 
-<com.android.systemui.statusbar.phone.NavigationHandle
+<com.android.systemui.navigationbar.gestural.NavigationHandle
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/home_handle"
     android:layout_width="@dimen/navigation_home_handle_width"
diff --git a/packages/SystemUI/res/layout/ime_switcher.xml b/packages/SystemUI/res/layout/ime_switcher.xml
index 7710b25..a2c8308 100644
--- a/packages/SystemUI/res/layout/ime_switcher.xml
+++ b/packages/SystemUI/res/layout/ime_switcher.xml
@@ -15,7 +15,7 @@
   ~ limitations under the License
   -->
 
-<com.android.systemui.statusbar.policy.KeyButtonView
+<com.android.systemui.navigationbar.buttons.KeyButtonView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/ime_switcher"
     android:layout_width="@dimen/navigation_key_width"
diff --git a/packages/SystemUI/res/layout/menu_ime.xml b/packages/SystemUI/res/layout/menu_ime.xml
index df717f6..0bb622b 100644
--- a/packages/SystemUI/res/layout/menu_ime.xml
+++ b/packages/SystemUI/res/layout/menu_ime.xml
@@ -25,7 +25,7 @@
     are placed inside a view that has a size controlled by weight. Ensure weight is large enough to
     support icon size. Use layout_width=navigation_side_padding like other navbar buttons. -->
 
-    <com.android.systemui.statusbar.policy.KeyButtonView
+    <com.android.systemui.navigationbar.buttons.KeyButtonView
         android:id="@+id/menu"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
@@ -43,7 +43,7 @@
         android:paddingStart="0dp"
         android:paddingEnd="0dp"
         />
-    <com.android.systemui.statusbar.policy.KeyButtonView
+    <com.android.systemui.navigationbar.buttons.KeyButtonView
         android:id="@+id/rotate_suggestion"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
@@ -51,7 +51,7 @@
         android:scaleType="centerInside"
         android:contentDescription="@string/accessibility_rotate_button"
         />
-    <com.android.systemui.statusbar.policy.KeyButtonView
+    <com.android.systemui.navigationbar.buttons.KeyButtonView
         android:id="@+id/accessibility_button"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
diff --git a/packages/SystemUI/res/layout/navigation_bar.xml b/packages/SystemUI/res/layout/navigation_bar.xml
index ba6b695..23f36a9 100644
--- a/packages/SystemUI/res/layout/navigation_bar.xml
+++ b/packages/SystemUI/res/layout/navigation_bar.xml
@@ -17,9 +17,10 @@
 */
 -->
 
-<com.android.systemui.statusbar.phone.NavigationBarView
+<com.android.systemui.navigationbar.NavigationBarView
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:systemui="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/navigation_bar_view"
     android:layout_height="match_parent"
     android:layout_width="match_parent"
     android:background="@drawable/system_bar_background">
@@ -39,9 +40,9 @@
         android:rotation="180"
         android:visibility="gone"/>
 
-    <com.android.systemui.statusbar.phone.NavigationBarInflaterView
+    <com.android.systemui.navigationbar.NavigationBarInflaterView
         android:id="@+id/navigation_inflater"
         android:layout_width="match_parent"
         android:layout_height="match_parent" />
 
-</com.android.systemui.statusbar.phone.NavigationBarView>
+</com.android.systemui.navigationbar.NavigationBarView>
diff --git a/packages/SystemUI/res/layout/navigation_bar_window.xml b/packages/SystemUI/res/layout/navigation_bar_window.xml
index f98cbd8..b2473cd 100644
--- a/packages/SystemUI/res/layout/navigation_bar_window.xml
+++ b/packages/SystemUI/res/layout/navigation_bar_window.xml
@@ -16,7 +16,7 @@
 ** limitations under the License.
 */
 -->
-<com.android.systemui.statusbar.phone.NavigationBarFrame
+<com.android.systemui.navigationbar.NavigationBarFrame
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:systemui="http://schemas.android.com/apk/res-auto"
     android:id="@+id/navigation_bar_frame"
@@ -24,4 +24,4 @@
     android:layout_height="match_parent"
     android:layout_width="match_parent">
 
-</com.android.systemui.statusbar.phone.NavigationBarFrame>
+</com.android.systemui.navigationbar.NavigationBarFrame>
diff --git a/packages/SystemUI/res/layout/navigation_layout.xml b/packages/SystemUI/res/layout/navigation_layout.xml
index db1c79d..0e576fb 100644
--- a/packages/SystemUI/res/layout/navigation_layout.xml
+++ b/packages/SystemUI/res/layout/navigation_layout.xml
@@ -25,7 +25,7 @@
     android:paddingEnd="@dimen/nav_content_padding"
     android:id="@+id/horizontal">
 
-    <com.android.systemui.statusbar.phone.NearestTouchFrame
+    <com.android.systemui.navigationbar.buttons.NearestTouchFrame
         android:id="@+id/nav_buttons"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
@@ -50,6 +50,6 @@
             android:clipToPadding="false"
             android:clipChildren="false" />
 
-    </com.android.systemui.statusbar.phone.NearestTouchFrame>
+    </com.android.systemui.navigationbar.buttons.NearestTouchFrame>
 
 </FrameLayout>
diff --git a/packages/SystemUI/res/layout/navigation_layout_vertical.xml b/packages/SystemUI/res/layout/navigation_layout_vertical.xml
index 285c5c4..4b67700 100644
--- a/packages/SystemUI/res/layout/navigation_layout_vertical.xml
+++ b/packages/SystemUI/res/layout/navigation_layout_vertical.xml
@@ -25,14 +25,14 @@
     android:paddingBottom="@dimen/nav_content_padding"
     android:id="@+id/vertical">
 
-    <com.android.systemui.statusbar.phone.NearestTouchFrame
+    <com.android.systemui.navigationbar.buttons.NearestTouchFrame
         android:id="@+id/nav_buttons"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:clipChildren="false"
         android:clipToPadding="false">
 
-        <com.android.systemui.statusbar.phone.ReverseLinearLayout
+        <com.android.systemui.navigationbar.buttons.ReverseLinearLayout
             android:id="@+id/ends_group"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
@@ -40,7 +40,7 @@
             android:clipToPadding="false"
             android:clipChildren="false" />
 
-        <com.android.systemui.statusbar.phone.ReverseLinearLayout
+        <com.android.systemui.navigationbar.buttons.ReverseLinearLayout
             android:id="@+id/center_group"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
@@ -49,6 +49,6 @@
             android:clipToPadding="false"
             android:clipChildren="false" />
 
-    </com.android.systemui.statusbar.phone.NearestTouchFrame>
+    </com.android.systemui.navigationbar.buttons.NearestTouchFrame>
 
 </FrameLayout>
diff --git a/packages/SystemUI/res/layout/recent_apps.xml b/packages/SystemUI/res/layout/recent_apps.xml
index 870bcf7..e2b1374 100644
--- a/packages/SystemUI/res/layout/recent_apps.xml
+++ b/packages/SystemUI/res/layout/recent_apps.xml
@@ -14,7 +14,7 @@
      limitations under the License.
 -->
 
-<com.android.systemui.statusbar.policy.KeyButtonView
+<com.android.systemui.navigationbar.buttons.KeyButtonView
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:systemui="http://schemas.android.com/apk/res-auto"
     android:id="@+id/recent_apps"
diff --git a/packages/SystemUI/res/layout/rotate_suggestion.xml b/packages/SystemUI/res/layout/rotate_suggestion.xml
index d7f67db..194d2e0 100644
--- a/packages/SystemUI/res/layout/rotate_suggestion.xml
+++ b/packages/SystemUI/res/layout/rotate_suggestion.xml
@@ -15,7 +15,7 @@
   ~ limitations under the License
   -->
 
-<com.android.systemui.statusbar.policy.KeyButtonView
+<com.android.systemui.navigationbar.buttons.KeyButtonView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/rotate_suggestion"
     android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/udfps_view.xml b/packages/SystemUI/res/layout/udfps_view.xml
index 732758a..31a33fb 100644
--- a/packages/SystemUI/res/layout/udfps_view.xml
+++ b/packages/SystemUI/res/layout/udfps_view.xml
@@ -5,6 +5,6 @@
     android:id="@+id/udfps_view"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    systemui:sensorRadius="140px"
-    systemui:sensorMarginBottom="630px"
+    systemui:sensorRadius="130px"
+    systemui:sensorCenterY="1636px"
     systemui:sensorTouchAreaCoefficient="0.5"/>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index aedbded..43ab1d2 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -73,7 +73,7 @@
     <string name="usb_contaminant_message" msgid="7730476585174719805">"መሣሪያዎን ከፈሳሽ ወይም ፍርስራሽ ለመጠበቅ ሲባል የዩኤስቢ ወደቡ ተሰናክሏል፣ እና ማናቸውም ተቀጥላዎችን አያገኝም።\n\nየዩኤስቢ ወደቡን እንደገና መጠቀም ችግር በማይኖረው ጊዜ ማሳወቂያ ይደርሰዎታል።"</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"ኃይል መሙያዎችን እና ተጨማሪ መሣሪያዎችን ፈልጎ ለማግኘት የነቃ የዩኤስቢ ወደብ"</string>
     <string name="usb_disable_contaminant_detection" msgid="3827082183595978641">"ዩኤስቢ አንቃ"</string>
-    <string name="learn_more" msgid="4690632085667273811">"የበለጠ መረዳት"</string>
+    <string name="learn_more" msgid="4690632085667273811">"የበለጠ ለመረዳት"</string>
     <string name="compat_mode_on" msgid="4963711187149440884">"ማያ እንዲሞላ አጉላ"</string>
     <string name="compat_mode_off" msgid="7682459748279487945">"ማያ ለመሙለት ሳብ"</string>
     <string name="global_action_screenshot" msgid="2760267567509131654">"ቅጽበታዊ ገጽ እይታ"</string>
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"ምክሮችን በመጫን ላይ"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"ሚዲያ"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"የአሁኑን ክፍለ-ጊዜ ደብቅ።"</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"አሰናብት"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"ከቆመበት ቀጥል"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"ቅንብሮች"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"ንቁ ያልኾነ፣ መተግበሪያን ይፈትሹ"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index fee86ca..c68d088 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -1093,8 +1093,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"جارٍ تحميل الاقتراحات"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"الوسائط"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"إخفاء الجلسة الحالية"</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"إغلاق"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"استئناف التشغيل"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"الإعدادات"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"غير نشط، تحقّق من التطبيق."</string>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index 3778df8..d604fd4 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -125,7 +125,7 @@
     <string name="accessibility_accessibility_button" msgid="4089042473497107709">"দিব্যাংগসকলৰ বাবে থকা সুবিধাসমূহ"</string>
     <string name="accessibility_rotate_button" msgid="1238584767612362586">"স্ক্ৰীণ ঘূৰাওক"</string>
     <string name="accessibility_recent" msgid="901641734769533575">"অৱলোকন"</string>
-    <string name="accessibility_search_light" msgid="524741790416076988">"সন্ধান কৰক"</string>
+    <string name="accessibility_search_light" msgid="524741790416076988">"Search"</string>
     <string name="accessibility_camera_button" msgid="2938898391716647247">"কেমেৰা"</string>
     <string name="accessibility_phone_button" msgid="4256353121703100427">"ফ\'ন"</string>
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"কণ্ঠধ্বনিৰে সহায়"</string>
@@ -441,7 +441,7 @@
     <string name="expanded_header_battery_charging_with_time" msgid="757991461445765011">"বেটাৰিৰ চ্চাৰ্জ সম্পূর্ণ হ\'বলৈ <xliff:g id="CHARGING_TIME">%s</xliff:g> বাকী"</string>
     <string name="expanded_header_battery_not_charging" msgid="809409140358955848">"চ্চার্জ কৰি থকা নাই"</string>
     <string name="ssl_ca_cert_warning" msgid="8373011375250324005">"নেটৱৰ্ক \nনিৰীক্ষণ কৰা হ\'ব পাৰে"</string>
-    <string name="description_target_search" msgid="3875069993128855865">"অনুসন্ধান কৰক"</string>
+    <string name="description_target_search" msgid="3875069993128855865">"Search"</string>
     <string name="description_direction_up" msgid="3632251507574121434">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>ৰ বাবে ওপৰলৈ শ্লাইড কৰক।"</string>
     <string name="description_direction_left" msgid="4762708739096907741">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>ৰ বাবে বাওঁফাললৈ শ্লাইড কৰক।"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"আপুনি নিৰ্দিষ্ট কৰা এলাৰ্ম, ৰিমাইণ্ডাৰ, ইভেন্ট আৰু কল কৰোঁতাৰ বাহিৰে আন কোনো শব্দৰ পৰা আপুনি অসুবিধা নাপাব। কিন্তু, সংগীত, ভিডিঅ\' আৰু খেলসমূহকে ধৰি আপুনি প্লে কৰিব খোজা যিকোনো বস্তু তথাপি শুনিব পাৰিব।"</string>
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"চুপাৰিছসমূহ ল’ড কৰি থকা হৈছে"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"মিডিয়া"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"বৰ্তমানৰ ছেশ্বনটো লুকুৱাওক।"</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"অগ্ৰাহ্য কৰক"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"পুনৰ আৰম্ভ কৰক"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"ছেটিংসমূহ"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"সক্ৰিয় নহয়, এপ্‌টো পৰীক্ষা কৰক"</string>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index 874bf74..b1df252 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Tövsiyələr yüklənir"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Media"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Cari sessiyanı gizlədin."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"İmtina edin"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Davam edin"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Ayarlar"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Aktiv deyil, tətbiqi yoxlayın"</string>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index 2a120c5..f3c4c6b 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -1075,8 +1075,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Učitavaju se preporuke"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Mediji"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Sakrijte aktuelnu sesiju."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Odbaci"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Nastavi"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Podešavanja"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Neaktivno. Vidite aplikaciju"</string>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index e4bb67a..9c0eeb2 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -1081,8 +1081,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Загружаюцца рэкамендацыі"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Мультымедыя"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Схаваць цяперашні сеанс."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Адхіліць"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Узнавіць"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Налады"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Неактыўна, праверце праграму"</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index c51e58c..3bf12ad 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Препоръките се зареждат"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Мултимедия"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Скриване на текущата сесия."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Отхвърляне"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Възобновяване"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Настройки"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Неактивно, проверете прилож."</string>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index 3d00ca8..5b7c953 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -125,7 +125,7 @@
     <string name="accessibility_accessibility_button" msgid="4089042473497107709">"অ্যাক্সেসযোগ্যতা"</string>
     <string name="accessibility_rotate_button" msgid="1238584767612362586">"স্ক্রিন ঘোরান"</string>
     <string name="accessibility_recent" msgid="901641734769533575">"এক নজরে"</string>
-    <string name="accessibility_search_light" msgid="524741790416076988">"খুঁজুন"</string>
+    <string name="accessibility_search_light" msgid="524741790416076988">"সার্চ"</string>
     <string name="accessibility_camera_button" msgid="2938898391716647247">"ক্যামেরা"</string>
     <string name="accessibility_phone_button" msgid="4256353121703100427">"ফোন"</string>
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"ভয়েস সহায়তা"</string>
@@ -441,7 +441,7 @@
     <string name="expanded_header_battery_charging_with_time" msgid="757991461445765011">"পূর্ণ হতে <xliff:g id="CHARGING_TIME">%s</xliff:g> সময় লাগবে"</string>
     <string name="expanded_header_battery_not_charging" msgid="809409140358955848">"চার্জ হচ্ছে না"</string>
     <string name="ssl_ca_cert_warning" msgid="8373011375250324005">"নেটওয়ার্ক নিরীক্ষণ\nকরা হতে পারে"</string>
-    <string name="description_target_search" msgid="3875069993128855865">"খুঁজুন"</string>
+    <string name="description_target_search" msgid="3875069993128855865">"সার্চ"</string>
     <string name="description_direction_up" msgid="3632251507574121434">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> এর জন্য উপরের দিকে স্লাইড করুন৷"</string>
     <string name="description_direction_left" msgid="4762708739096907741">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> এর জন্য বাঁ দিকে স্লাইড করুন৷"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"অ্যালার্ম, রিমাইন্ডার, ইভেন্ট, এবং আপনার নির্দিষ্ট করে দেওয়া ব্যক্তিদের কল ছাড়া অন্য কোনও আওয়াজ বা ভাইব্রেশন হবে না। তবে সঙ্গীত, ভিডিও, এবং গেম সহ আপনি যা কিছু চালাবেন তার আওয়াজ শুনতে পাবেন।"</string>
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"সাজেশন লোড করা হচ্ছে"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"মিডিয়া"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"বর্তমান সেশন লুকান।"</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"খারিজ করুন"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"আবার চালু করুন"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"সেটিংস"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"বন্ধ আছে, অ্যাপ চেক করুন"</string>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index 1425984..31f88c8f 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -1075,8 +1075,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Učitavanje preporuka"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Mediji"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Sakrijte trenutnu sesiju."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Odbaci"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Nastavi"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Postavke"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Neaktivno, vidite aplikaciju"</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index b150155..753e25f 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Carregant les recomanacions"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Multimèdia"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Amaga la sessió actual."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Ignora"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Reprèn"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Configuració"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inactiu; comprova l\'aplicació"</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index ae6cc13..039500d 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -1081,8 +1081,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Načítání doporučení"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Média"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Skrýt aktuální relaci."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Zavřít"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Pokračovat"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Nastavení"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Neaktivní, zkontrolujte aplikaci"</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 09f7f1a..0c8a5b7 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Indlæser anbefalinger"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Medie"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Skjul den aktuelle session."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Luk"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Genoptag"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Indstillinger"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inaktiv. Tjek appen"</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index 2410e47..1c4c7ab 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Empfehlungen werden geladen"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Medien"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Du kannst die aktuelle Sitzung ausblenden."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Ablehnen"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Fortsetzen"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Einstellungen"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inaktiv – sieh in der App nach"</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index f66c5a8..07ff807 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Φόρτωση προτάσεων"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Μέσα"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Απόκρυψη της τρέχουσας περιόδου λειτουργίας."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Παράβλεψη"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Συνέχιση"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Ρυθμίσεις"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Ανενεργό, έλεγχος εφαρμογής"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 45e0724..38fa528 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Cargando recomendaciones"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Contenido multimedia"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Oculta la sesión actual."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Descartar"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Reanudar"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Configuración"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inactivo. Verifica la app"</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index f7ebbd0..10aa6ca 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Cargando recomendaciones"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Multimedia"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Ocultar la sesión."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Cerrar"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Reanudar"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Ajustes"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inactivo, comprobar aplicación"</string>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index 1e5e49e..9122ce5 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Soovituste laadimine"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Meedia"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Peidetakse praegune seanss."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Loobu"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Jätka"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Seaded"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Passiivne, vaadake rakendust"</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index fa076c4..52e5e8c 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Ladataan suosituksia"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Media"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Piilota nykyinen käyttökerta."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Ohita"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Jatka"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Asetukset"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Epäaktiivinen, tarkista sovellus"</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 9c418f7..4797f32 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Chargement des recommandations…"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Commandes multimédias"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Masquer la session en cours."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Fermer"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Reprendre"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Paramètres"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Délai expiré, vérifiez l\'appli"</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index 3e2f5fd..e5df728 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Chargement des recommandations"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Multimédia"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Masquer la session en cours."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Fermer"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Reprendre"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Paramètres"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Délai expiré, vérifier l\'appli"</string>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index 9ecd75e..246fefd 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Cargando recomendacións"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Contido multimedia"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Oculta a sesión actual."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Ignorar"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Retomar"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Configuración"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inactivo. Comproba a app"</string>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index 6e05a54..beaacaa 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -125,7 +125,7 @@
     <string name="accessibility_accessibility_button" msgid="4089042473497107709">"ઍક્સેસિબિલિટી"</string>
     <string name="accessibility_rotate_button" msgid="1238584767612362586">"સ્ક્રીન ફેરવો"</string>
     <string name="accessibility_recent" msgid="901641734769533575">"ઝલક"</string>
-    <string name="accessibility_search_light" msgid="524741790416076988">"શોધો"</string>
+    <string name="accessibility_search_light" msgid="524741790416076988">"શોધ"</string>
     <string name="accessibility_camera_button" msgid="2938898391716647247">"કૅમેરો"</string>
     <string name="accessibility_phone_button" msgid="4256353121703100427">"ફોન"</string>
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"વૉઇસ સહાય"</string>
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"સુઝાવ લોડ કરી રહ્યાં છીએ"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"મીડિયા"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"હાલનું સત્ર છુપાવો."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"છોડી દો"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"ફરી શરૂ કરો"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"સેટિંગ"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"નિષ્ક્રિય, ઍપને ચેક કરો"</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index 4bebe79..7bf0b93 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -1075,8 +1075,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Učitavanje preporuka"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Mediji"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Sakrij trenutačnu sesiju."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Odbaci"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Nastavi"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Postavke"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Neaktivno, provjerite aplik."</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 42e251f..875a05f 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Javaslatok betöltése…"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Média"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Jelenlegi munkamenet elrejtése."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Elvetés"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Folytatás"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Beállítások"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inaktív, ellenőrizze az appot"</string>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index e6b7139..4cc406b 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Բեռնման խորհուրդներ"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Մեդիա"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Թաքցրեք ընթացիկ աշխատաշրջանը"</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Փակել"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Շարունակել"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Կարգավորումներ"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Ակտիվ չէ, ստուգեք հավելվածը"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index fec4205..5f7dce4 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Memuat rekomendasi"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Media"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Menyembunyikan sesi saat ini."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Tutup"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Lanjutkan"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Setelan"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Nonaktif, periksa aplikasi"</string>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index 3a9e63b..b5f6e57 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Hleður tillögum"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Margmiðlunarefni"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Fela núverandi lotu."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Hunsa"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Halda áfram"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Stillingar"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Óvirkt, athugaðu forrit"</string>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index 3eca501..f4453b7 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Caricamento dei consigli"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Contenuti multimediali"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Nascondi la sessione attuale."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Ignora"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Riprendi"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Impostazioni"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inattivo, controlla l\'app"</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index e88c951..161fedc 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -1081,8 +1081,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"בטעינת המלצות"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"מדיה"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"הסתרת הסשן הנוכחי."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"סגירה"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"המשך"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"הגדרות"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"לא פעיל, יש לבדוק את האפליקציה"</string>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index 130f682..fa9c040 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"候補を読み込んでいます"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"メディア"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"現在のセッションを非表示にします。"</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"閉じる"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"再開"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"設定"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"無効: アプリをご確認ください"</string>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index d050875..a3ddf70 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"მიმდინარეობს რეკომენდაციების ჩატვირთვა"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"მედია"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"დაიმალოს მიმდინარე სესია"</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"დახურვა"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"გაგრძელება"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"პარამეტრები"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"არააქტიურია, გადაამოწმეთ აპი"</string>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index 36c726e..0d3f7f9 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Жүктеуге қатысты ұсыныстар"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Мультимедиа"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Ағымдағы сеансты жасыру"</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Жабу"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Жалғастыру"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Параметрлер"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Өшірулі. Қолданба тексеріңіз."</string>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index 9d87c58..5abd740 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"កំពុងផ្ទុក​ការណែនាំ"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"មេឌៀ"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"លាក់វគ្គ​បច្ចុប្បន្ន។"</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"ច្រាន​ចោល"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"បន្ត"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"ការកំណត់"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"អសកម្ម ពិនិត្យមើល​កម្មវិធី"</string>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index 901f024..47f2385 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -125,7 +125,7 @@
     <string name="accessibility_accessibility_button" msgid="4089042473497107709">"ಪ್ರವೇಶಿಸುವಿಕೆ"</string>
     <string name="accessibility_rotate_button" msgid="1238584767612362586">"ಪರದೆಯನ್ನು ತಿರುಗಿಸಿ"</string>
     <string name="accessibility_recent" msgid="901641734769533575">"ಸಮಗ್ರ ನೋಟ"</string>
-    <string name="accessibility_search_light" msgid="524741790416076988">"ಹುಡುಕಿ"</string>
+    <string name="accessibility_search_light" msgid="524741790416076988">"Search"</string>
     <string name="accessibility_camera_button" msgid="2938898391716647247">"ಕ್ಯಾಮರಾ"</string>
     <string name="accessibility_phone_button" msgid="4256353121703100427">"ಫೋನ್"</string>
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"ಧ್ವನಿ ಸಹಾಯಕ"</string>
@@ -441,7 +441,7 @@
     <string name="expanded_header_battery_charging_with_time" msgid="757991461445765011">"<xliff:g id="CHARGING_TIME">%s</xliff:g> ಪೂರ್ಣಗೊಳ್ಳುವವರೆಗೆ"</string>
     <string name="expanded_header_battery_not_charging" msgid="809409140358955848">"ಚಾರ್ಜ್‌ ಆಗುತ್ತಿಲ್ಲ"</string>
     <string name="ssl_ca_cert_warning" msgid="8373011375250324005">"ನೆಟ್‌ವರ್ಕ್\n ವೀಕ್ಷಿಸಬಹುದಾಗಿರುತ್ತದೆ"</string>
-    <string name="description_target_search" msgid="3875069993128855865">"ಹುಡುಕಿ"</string>
+    <string name="description_target_search" msgid="3875069993128855865">"Search"</string>
     <string name="description_direction_up" msgid="3632251507574121434">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> ಗಾಗಿ ಮೇಲಕ್ಕೆ ಸ್ಲೈಡ್ ಮಾಡಿ."</string>
     <string name="description_direction_left" msgid="4762708739096907741">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> ಗಾಗಿ ಎಡಕ್ಕೆ ಸ್ಲೈಡ್ ಮಾಡಿ."</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"ಅಲಾರಾಂಗಳು, ಜ್ಞಾಪನೆಗಳು, ಈವೆಂಟ್‌ಗಳು ಹಾಗೂ ನೀವು ಸೂಚಿಸಿರುವ ಕರೆದಾರರನ್ನು ಹೊರತುಪಡಿಸಿ ಬೇರಾವುದೇ ಸದ್ದುಗಳು ಅಥವಾ ವೈಬ್ರೇಶನ್‌ಗಳು ನಿಮಗೆ ತೊಂದರೆ ನೀಡುವುದಿಲ್ಲ. ಹಾಗಿದ್ದರೂ, ನೀವು ಪ್ಲೇ ಮಾಡುವ ಸಂಗೀತ, ವೀಡಿಯೊಗಳು ಮತ್ತು ಆಟಗಳ ಆಡಿಯೊವನ್ನು ನೀವು ಕೇಳಿಸಿಕೊಳ್ಳುತ್ತೀರಿ."</string>
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"ಶಿಫಾರಸುಗಳು ಲೋಡ್ ಆಗುತ್ತಿವೆ"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"ಮಾಧ್ಯಮ"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"ಪ್ರಸ್ತುತ ಸೆಶನ್ ಅನ್ನು ಮರೆಮಾಡಿ."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"ವಜಾಗೊಳಿಸಿ"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"ಪುನರಾರಂಭಿಸಿ"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"ನಿಷ್ಕ್ರಿಯ, ಆ್ಯಪ್ ಪರಿಶೀಲಿಸಿ"</string>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 8a64155..beb7eda 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -502,10 +502,10 @@
     <string name="battery_saver_notification_title" msgid="8419266546034372562">"절전 모드 사용 중"</string>
     <string name="battery_saver_notification_text" msgid="2617841636449016951">"성능 및 백그라운드 데이터를 줄입니다."</string>
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"절전 모드 사용 중지"</string>
-    <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>이(가) 녹화 또는 전송 중에 화면에 표시되거나 기기에서 재생되는 모든 정보에 액세스할 수 있습니다. 여기에는 비밀번호, 결제 세부정보, 사진, 메시지, 재생하는 오디오 같은 정보가 포함됩니다."</string>
+    <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>이 녹화 또는 전송 중에 화면에 표시되거나 기기에서 재생되는 모든 정보에 액세스할 수 있습니다. 여기에는 비밀번호, 결제 세부정보, 사진, 메시지, 재생하는 오디오 같은 정보가 포함됩니다."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"이 기능을 제공하는 서비스는 녹화 또는 전송 중에 화면에 표시되거나 기기에서 재생되는 모든 정보에 액세스할 수 있습니다. 여기에는 비밀번호, 결제 세부정보, 사진, 메시지, 재생하는 오디오 같은 정보가 포함됩니다."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"녹화 또는 전송을 시작하시겠습니까?"</string>
-    <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>(으)로 녹화 또는 전송을 시작하시겠습니까?"</string>
+    <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>으로 녹화 또는 전송을 시작하시겠습니까?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"다시 표시 안함"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"모두 지우기"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"관리"</string>
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"추천 제어 기능 로드 중"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"미디어"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"현재 세션을 숨깁니다."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"닫기"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"다시 시작"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"설정"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"비활성. 앱을 확인하세요."</string>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index 3cf4126..4fbb09e 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Сунуштар жүктөлүүдө"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Медиа"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Учурдагы сеансты жашыруу."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Жабуу"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Улантуу"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Жөндөөлөр"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Жигерсиз. Колдонмону текшериңиз"</string>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index b1c5a5c..6d7feab 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"ກຳລັງໂຫຼດຄຳແນະນຳ"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"ມີເດຍ"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"ເຊື່ອງເຊດຊັນປັດຈຸບັນ."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"ປິດໄວ້"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"ສືບຕໍ່"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"ການຕັ້ງຄ່າ"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"ບໍ່ເຮັດວຽກ, ກະລຸນາກວດສອບແອັບ"</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index 4e9b802..21217a0 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -1081,8 +1081,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Įkeliamos rekomendacijos"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Medija"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Slėpti dabartinį seansą."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Atsisakyti"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Tęsti"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Nustatymai"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Neaktyvu, patikrinkite progr."</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index a4c4666..4945dfa 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -1075,8 +1075,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Notiek ieteikumu ielāde"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Multivide"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Paslēpiet pašreizējo sesiju."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Nerādīt"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Atsākt"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Iestatījumi"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Neaktīva, pārbaudiet lietotni"</string>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index 7e623ad..40d4698 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Се вчитуваат препораки"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Аудиовизуелни содржини"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Сокриј ја тековнава сесија."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Отфрли"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Продолжи"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Поставки"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Неактивна, провери апликација"</string>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index 35e6427..c3bd65e 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -125,7 +125,7 @@
     <string name="accessibility_accessibility_button" msgid="4089042473497107709">"ഉപയോഗസഹായി"</string>
     <string name="accessibility_rotate_button" msgid="1238584767612362586">"സ്‌ക്രീൻ തിരിക്കുക"</string>
     <string name="accessibility_recent" msgid="901641734769533575">"അവലോകനം"</string>
-    <string name="accessibility_search_light" msgid="524741790416076988">"തിരയൽ"</string>
+    <string name="accessibility_search_light" msgid="524741790416076988">"Search"</string>
     <string name="accessibility_camera_button" msgid="2938898391716647247">"ക്യാമറ"</string>
     <string name="accessibility_phone_button" msgid="4256353121703100427">"ഫോണ്‍"</string>
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"വോയ്‌സ് സഹായം"</string>
@@ -441,7 +441,7 @@
     <string name="expanded_header_battery_charging_with_time" msgid="757991461445765011">"ഫുൾ ചാർജാകാൻ, <xliff:g id="CHARGING_TIME">%s</xliff:g>"</string>
     <string name="expanded_header_battery_not_charging" msgid="809409140358955848">"ചാർജ്ജുചെയ്യുന്നില്ല"</string>
     <string name="ssl_ca_cert_warning" msgid="8373011375250324005">"നെറ്റ്‌വർക്ക്\nനിരീക്ഷിക്കപ്പെടാം"</string>
-    <string name="description_target_search" msgid="3875069993128855865">"തിരയൽ"</string>
+    <string name="description_target_search" msgid="3875069993128855865">"Search"</string>
     <string name="description_direction_up" msgid="3632251507574121434">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> എന്നതിനായി മുകളിലേയ്‌ക്ക് സ്ലൈഡുചെയ്യുക."</string>
     <string name="description_direction_left" msgid="4762708739096907741">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> എന്നതിനായി ഇടത്തേയ്‌ക്ക് സ്ലൈഡുചെയ്യുക."</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"നിങ്ങൾ സജ്ജീകരിച്ച അലാറങ്ങൾ, റിമൈൻഡറുകൾ, ഇവന്റുകൾ, കോളർമാർ എന്നിവയിൽ നിന്നുള്ള ശബ്‌ദങ്ങളും വൈബ്രേഷനുകളുമൊഴികെ മറ്റൊന്നും നിങ്ങളെ ശല്യപ്പെടുത്തുകയില്ല. സംഗീതം, വീഡിയോകൾ, ഗെയിമുകൾ എന്നിവയുൾപ്പെടെ പ്ലേ ചെയ്യുന്നതെന്തും നിങ്ങൾക്ക് ‌തുടർന്നും കേൾക്കാൻ കഴിയും."</string>
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"നിർദ്ദേശങ്ങൾ ലോഡ് ചെയ്യുന്നു"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"മീഡിയ"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"നിലവിലെ സെഷൻ മറയ്‌ക്കുക."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"ഡിസ്‌മിസ് ചെയ്യുക"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"പുനരാരംഭിക്കുക"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"ക്രമീകരണം"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"നിഷ്‌ക്രിയം, ആപ്പ് പരിശോധിക്കൂ"</string>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index b4690d4..5f08f05 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Зөвлөмжүүдийг ачаалж байна"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Медиа"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Одоогийн харилцан үйлдлийг нуугаарай."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Хаах"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Үргэлжлүүлэх"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Тохиргоо"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Идэвхгүй байна, аппыг шалгана уу"</string>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index 4ea965a..6d525df 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -125,7 +125,7 @@
     <string name="accessibility_accessibility_button" msgid="4089042473497107709">"अ‍ॅक्सेसिबिलिटी"</string>
     <string name="accessibility_rotate_button" msgid="1238584767612362586">"स्क्रीन फिरवा"</string>
     <string name="accessibility_recent" msgid="901641734769533575">"अवलोकन"</string>
-    <string name="accessibility_search_light" msgid="524741790416076988">"शोधा"</string>
+    <string name="accessibility_search_light" msgid="524741790416076988">"Search"</string>
     <string name="accessibility_camera_button" msgid="2938898391716647247">"कॅमेरा"</string>
     <string name="accessibility_phone_button" msgid="4256353121703100427">"फोन"</string>
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"व्हॉइस सहाय्य"</string>
@@ -441,7 +441,7 @@
     <string name="expanded_header_battery_charging_with_time" msgid="757991461445765011">"<xliff:g id="CHARGING_TIME">%s</xliff:g> पूर्ण होईपर्यंत"</string>
     <string name="expanded_header_battery_not_charging" msgid="809409140358955848">"चार्ज होत नाही"</string>
     <string name="ssl_ca_cert_warning" msgid="8373011375250324005">"नेटवर्कचे परीक्षण\nकेले जाऊ शकते"</string>
-    <string name="description_target_search" msgid="3875069993128855865">"शोध"</string>
+    <string name="description_target_search" msgid="3875069993128855865">"Search"</string>
     <string name="description_direction_up" msgid="3632251507574121434">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> साठी वर स्लाइड करा."</string>
     <string name="description_direction_left" msgid="4762708739096907741">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> साठी डावीकडे स्लाइड करा."</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"अलार्म, रिमाइंडर, इव्‍हेंट आणि तुम्ही निश्चित केलेल्या कॉलर व्यतिरिक्त तुम्हाला कोणत्याही आवाज आणि कंपनांचा व्यत्त्यय आणला जाणार नाही. तरीही तुम्ही प्ले करायचे ठरवलेले कोणतेही संगीत, व्हिडिओ आणि गेमचे आवाज ऐकू शकतात."</string>
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"शिफारशी लोड करत आहे"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"मीडिया"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"सध्याचे सेशन लपवा."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"डिसमिस करा"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"पुन्हा सुरू करा"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"सेटिंग्ज"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"निष्क्रिय, ॲप तपासा"</string>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index 38ee25c..4f832e4 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Memuatkan cadangan"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Media"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Sembunyikan sesi semasa."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Tolak"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Sambung semula"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Tetapan"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Tidak aktif, semak apl"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index d872a89..849c2e9 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Laster inn anbefalinger"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Medier"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Skjul den nåværende økten."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Lukk"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Gjenoppta"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Innstillinger"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inaktiv. Sjekk appen"</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index 1974e80..2ac442e 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"सिफारिसहरू लोड गर्दै"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"मिडिया"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"हालको सत्र लुकाउनुहोस्।"</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"हटाउनुहोस्"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"सुचारु गर्नुहोस्"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"सेटिङ"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"निष्क्रिय छ, एप जाँच गर्नु…"</string>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 9da8afa..da0f427 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Aanbevelingen laden"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Media"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"De huidige sessie verbergen."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Sluiten"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Hervatten"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Instellingen"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inactief, check de app"</string>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index 5b5cbc6..029aa69 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -125,7 +125,7 @@
     <string name="accessibility_accessibility_button" msgid="4089042473497107709">"ଆକ୍ସେସିବିଲିଟୀ"</string>
     <string name="accessibility_rotate_button" msgid="1238584767612362586">"ସ୍କ୍ରୀନ୍‌କୁ ଘୁରାନ୍ତୁ"</string>
     <string name="accessibility_recent" msgid="901641734769533575">"ଓଭରଭିଉ"</string>
-    <string name="accessibility_search_light" msgid="524741790416076988">"ସର୍ଚ୍ଚ କରନ୍ତୁ"</string>
+    <string name="accessibility_search_light" msgid="524741790416076988">"Search"</string>
     <string name="accessibility_camera_button" msgid="2938898391716647247">"କ୍ୟାମେରା"</string>
     <string name="accessibility_phone_button" msgid="4256353121703100427">"ଫୋନ୍‍"</string>
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"ଭଏସ୍‌ ସହାୟକ"</string>
@@ -441,7 +441,7 @@
     <string name="expanded_header_battery_charging_with_time" msgid="757991461445765011">"ପୂର୍ଣ୍ଣ ଚାର୍ଜ ହେବାକୁ ଆଉ <xliff:g id="CHARGING_TIME">%s</xliff:g> ଅଛି"</string>
     <string name="expanded_header_battery_not_charging" msgid="809409140358955848">"ଚାର୍ଜ ହେଉନାହିଁ"</string>
     <string name="ssl_ca_cert_warning" msgid="8373011375250324005">"ନେଟ୍‍ୱର୍କ\nମନିଟର୍‍ କରାଯାଇପାରେ"</string>
-    <string name="description_target_search" msgid="3875069993128855865">"ସର୍ଚ୍ଚ"</string>
+    <string name="description_target_search" msgid="3875069993128855865">"Search"</string>
     <string name="description_direction_up" msgid="3632251507574121434">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> ପାଇଁ ଉପରକୁ ସ୍ଲାଇଡ୍‍ କରନ୍ତୁ।"</string>
     <string name="description_direction_left" msgid="4762708739096907741">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> ପାଇଁ ବାମକୁ ସ୍ଲାଇଡ୍ କରନ୍ତୁ"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"ଆଲାର୍ମ, ରିମାଇଣ୍ଡର୍‌, ଇଭେଣ୍ଟ ଏବଂ ଆପଣ ନିର୍ଦ୍ଦିଷ୍ଟ କରିଥିବା କଲର୍‌ଙ୍କ ବ୍ୟତୀତ ଆପଣଙ୍କ ଧ୍ୟାନ ଅନ୍ୟ କୌଣସି ଧ୍ୱନୀ ଏବଂ ଭାଇବ୍ରେଶନ୍‌ରେ ଆକର୍ଷଣ କରାଯିବନାହିଁ। ମ୍ୟୁଜିକ୍‍, ଭିଡିଓ ଏବଂ ଗେମ୍‌ ସମେତ ନିଜେ ଚଲାଇବାକୁ ବାଛିଥିବା ଅନ୍ୟ ସବୁକିଛି ଆପଣ ଶୁଣିପାରିବେ।"</string>
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"ସୁପାରିଶଗୁଡ଼ିକ ଲୋଡ୍ କରାଯାଉଛି"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"ମିଡିଆ"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"ବର୍ତ୍ତମାନର ସେସନ୍ ଲୁଚାନ୍ତୁ।"</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"ଖାରଜ କରନ୍ତୁ"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"ପୁଣି ଆରମ୍ଭ କରନ୍ତୁ"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"ସେଟିଂସ୍"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"ନିଷ୍କ୍ରିୟ ଅଛି, ଆପ ଯାଞ୍ଚ କରନ୍ତୁ"</string>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index 5c31ce7..ed5f40c 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"ਸਿਫ਼ਾਰਸ਼ਾਂ ਲੋਡ ਹੋ ਰਹੀਆਂ ਹਨ"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"ਮੀਡੀਆ"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"ਮੌਜੂਦਾ ਸੈਸ਼ਨ ਨੂੰ ਲੁਕਾਓ।"</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"ਖਾਰਜ ਕਰੋ"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"ਮੁੜ-ਚਾਲੂ ਕਰੋ"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"ਸੈਟਿੰਗਾਂ"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"ਅਕਿਰਿਆਸ਼ੀਲ, ਐਪ ਦੀ ਜਾਂਚ ਕਰੋ"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index b7ee750..ba30ced 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -1081,8 +1081,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Wczytuję rekomendacje"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Multimedia"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Ukryj bieżącą sesję."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Odrzuć"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Wznów"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Ustawienia"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Nieaktywny, sprawdź aplikację"</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index 49c3ba0..cf428a3 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -1075,8 +1075,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Se încarcă recomandările"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Media"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Ascunde sesiunea actuală."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Închideți"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Reia"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Setări"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inactiv, verificați aplicația"</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 7fb456d..ba86e95 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -1081,8 +1081,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Загрузка рекомендаций…"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Медиа"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Скрыть текущий сеанс?"</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Скрыть"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Возобновить"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Настройки"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Нет ответа. Проверьте приложение."</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 9d7e7d1..7548607 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -1081,8 +1081,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Načítavajú sa odporúčania"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Médiá"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Skryť aktuálnu reláciu."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Zavrieť"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Pokračovať"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Nastavenia"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Neaktívne, preverte aplikáciu"</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index ecbf1d9..07ad8c1 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -1081,8 +1081,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Nalaganje priporočil"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Predstavnost"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Skrije trenutno sejo."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Opusti"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Nadaljuj"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Nastavitve"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Neaktivno, poglejte aplikacijo"</string>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index 70245b8..b92744d 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Po ngarkon rekomandimet"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Media"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Fshih sesionin aktual."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Hiq"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Vazhdo"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Cilësimet"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Joaktive, kontrollo aplikacionin"</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 1d02ca6..f57d92b 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -1075,8 +1075,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Учитавају се препоруке"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Медији"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Сакријте актуелну сесију."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Одбаци"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Настави"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Подешавања"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Неактивно. Видите апликацију"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index 7a7448b..876e337 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Rekommendationer läses in"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Media"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Dölj den aktuella sessionen."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Stäng"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Återuppta"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Inställningar"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inaktiv, kolla appen"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index b8d95fc..25e3493 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Inapakia mapendekezo"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Maudhui"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Ficha kipindi cha sasa."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Ondoa"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Endelea"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Mipangilio"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Haitumiki, angalia programu"</string>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index 60c1cf9..19e67db 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"பரிந்துரைகளை ஏற்றுகிறது"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"மீடியா"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"இந்த அமர்வை மறையுங்கள்."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"மூடுக"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"தொடர்க"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"அமைப்புகள்"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"செயலில் இல்லை , சரிபார்க்கவும்"</string>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 6cfe03d..34ff9b1 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -125,7 +125,7 @@
     <string name="accessibility_accessibility_button" msgid="4089042473497107709">"యాక్సెస్ సామర్థ్యం"</string>
     <string name="accessibility_rotate_button" msgid="1238584767612362586">"స్క్రీన్‌ను తిప్పండి"</string>
     <string name="accessibility_recent" msgid="901641734769533575">"ఓవర్‌వ్యూ"</string>
-    <string name="accessibility_search_light" msgid="524741790416076988">"వెతుకు"</string>
+    <string name="accessibility_search_light" msgid="524741790416076988">"సెర్చ్"</string>
     <string name="accessibility_camera_button" msgid="2938898391716647247">"కెమెరా"</string>
     <string name="accessibility_phone_button" msgid="4256353121703100427">"ఫోన్"</string>
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"వాయిస్ అసిస్టెంట్"</string>
@@ -441,7 +441,7 @@
     <string name="expanded_header_battery_charging_with_time" msgid="757991461445765011">"పూర్తిగా నిండటానికి <xliff:g id="CHARGING_TIME">%s</xliff:g>"</string>
     <string name="expanded_header_battery_not_charging" msgid="809409140358955848">"ఛార్జ్ కావడం లేదు"</string>
     <string name="ssl_ca_cert_warning" msgid="8373011375250324005">"నెట్‌వర్క్\nపర్యవేక్షించబడవచ్చు"</string>
-    <string name="description_target_search" msgid="3875069993128855865">"శోధించండి"</string>
+    <string name="description_target_search" msgid="3875069993128855865">"సెర్చ్"</string>
     <string name="description_direction_up" msgid="3632251507574121434">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> కోసం పైకి స్లైడ్ చేయండి."</string>
     <string name="description_direction_left" msgid="4762708739096907741">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> కోసం ఎడమవైపుకు స్లైడ్ చేయండి."</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"మీరు పేర్కొనే అలారాలు, రిమైండర్‌లు, ఈవెంట్‌లు మరియు కాలర్‌ల నుండి మినహా మరే ఇతర ధ్వనులు మరియు వైబ్రేషన్‌లతో మీకు అంతరాయం కలగదు. మీరు ఇప్పటికీ సంగీతం, వీడియోలు మరియు గేమ్‌లతో సహా మీరు ప్లే చేయడానికి ఎంచుకున్నవి ఏవైనా వింటారు."</string>
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"సిఫార్సులు లోడ్ అవుతున్నాయి"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"మీడియా"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"ప్రస్తుత సెషన్‌ను దాచు."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"విస్మరించు"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"కొనసాగించండి"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"సెట్టింగ్‌లు"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"ఇన్‌యాక్టివ్, యాప్ చెక్ చేయండి"</string>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index a2c1127..d37be57 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"กำลังโหลดคำแนะนำ"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"สื่อ"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"ซ่อนเซสชันปัจจุบัน"</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"ปิด"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"เล่นต่อ"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"การตั้งค่า"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"ไม่มีการใช้งาน โปรดตรวจสอบแอป"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index e5f0532..af474c7 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Nilo-load ang rekomendasyon"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Media"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Itago ang kasalukuyang session."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"I-dismiss"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Ituloy"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Mga Setting"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Hindi aktibo, tingnan ang app"</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 4943e2d..ee494a2 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Öneriler yükleniyor"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Medya"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Mevcut oturumu gizle."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Kapat"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Devam ettir"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Ayarlar"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Devre dışı, uygulamaya bakın"</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 447912e..c444c47 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -1081,8 +1081,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Завантаження рекомендацій"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Медіа"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Приховати поточний сеанс."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Закрити"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Відновити"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Налаштування"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Неактивно, перейдіть у додаток"</string>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index 20b5518..729ed5a 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"تجاویز لوڈ ہو رہی ہیں"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"میڈیا"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"موجودہ سیشن چھپائیں۔"</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"برخاست کریں"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"دوبارہ شروع کریں"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"ترتیبات"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"غیر فعال، ایپ چیک کریں"</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index a12a08d..5794bdf 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Đang tải các đề xuất"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Nội dung nghe nhìn"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Ẩn phiên hiện tại."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Đóng"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Tiếp tục"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Cài đặt"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Không hoạt động, hãy kiểm tra ứng dụng"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 3382365..41c132c 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"正在加载推荐内容"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"媒体"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"隐藏当前会话。"</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"关闭"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"继续播放"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"设置"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"无效,请检查应用"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index 1d55ca2..b2e1b90 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"正在載入建議"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"媒體"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"隱藏目前的工作階段。"</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"關閉"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"繼續播放"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"設定"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"已停用,請檢查應用程式"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index e62c164..346b1239 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"正在載入建議控制項"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"媒體"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"隱藏目前的工作階段。"</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"關閉"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"繼續播放"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"設定"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"無效,請查看應用程式"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index bd73912..5abce7f 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -1069,8 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Ilayisha izincomo"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Imidiya"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Fihla iseshini yamanje."</string>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Cashisa"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Qalisa kabusha"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Izilungiselelo"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Akusebenzi, hlola uhlelo lokusebenza"</string>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index 84dbd60..a625029 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -159,7 +159,7 @@
 
     <declare-styleable name="UdfpsView">
         <attr name="sensorRadius" format="dimension"/>
-        <attr name="sensorMarginBottom" format="dimension"/>
+        <attr name="sensorCenterY" format="dimension"/>
         <attr name="sensorPressureCoefficient" format="float"/>
         <attr name="sensorTouchAreaCoefficient" format="float"/>
     </declare-styleable>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 17ba7c9..ce1ca5a 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -565,15 +565,9 @@
     <!-- If the config font scale is >= this value, potentially adjust the number of columns-->
     <item name="controls_max_columns_adjust_above_font_scale" translatable="false" format="float" type="dimen">1.25</item>
 
-    <!-- One handed mode default offset % of display size -->
-    <fraction name="config_one_handed_offset">50%</fraction>
-
     <!-- Allow one handed to enable round corner -->
     <bool name="config_one_handed_enable_round_corner">true</bool>
 
-    <!-- Animation duration for translating of one handed when trigger / dismiss. -->
-    <integer name="config_one_handed_translate_animation_duration">150</integer>
-
     <!-- Show a separate icon for low and high volume on the volume dialog -->
     <bool name="config_showLowMediaVolumeIcon">false</bool>
 </resources>
diff --git a/packages/SystemUI/res/values/config_tv.xml b/packages/SystemUI/res/values/config_tv.xml
index 5cb840f..7451ba8 100644
--- a/packages/SystemUI/res/values/config_tv.xml
+++ b/packages/SystemUI/res/values/config_tv.xml
@@ -22,9 +22,4 @@
     <!-- Whether to enable microphone disclosure indicator
          (com.android.systemui.statusbar.tv.micdisclosure.AudioRecordingDisclosureBar). -->
     <bool name="audio_recording_disclosure_enabled">true</bool>
-
-    <!-- Whether the indicator should expand and show the recording application's label.
-         When disabled (false) the "minimized" indicator would appear on the screen whenever an
-         application is recording, but will not reveal to the user what application this is.  -->
-    <bool name="audio_recording_disclosure_reveal_packages">false</bool>
 </resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 122fcb2..765a942 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1036,6 +1036,11 @@
     <!-- The maximum offset in either direction that icons move to prevent burn-in on AOD. -->
     <dimen name="default_burn_in_prevention_offset">15dp</dimen>
 
+    <!-- The maximum offset for the under-display fingerprint sensor (UDFPS) icon in either
+         direction that elements aer moved to prevent burn-in on AOD-->
+    <dimen name="udfps_burn_in_offset_x">8dp</dimen>
+    <dimen name="udfps_burn_in_offset_y">8dp</dimen>
+
     <dimen name="corner_size">8dp</dimen>
     <dimen name="top_padding">0dp</dimen>
     <dimen name="bottom_padding">48dp</dimen>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index d6fabd6..6f19613 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -36,8 +36,8 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
-import com.android.systemui.statusbar.NavigationBarController;
-import com.android.systemui.statusbar.phone.NavigationBarView;
+import com.android.systemui.navigationbar.NavigationBarController;
+import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.util.InjectionInflationController;
 
 public class KeyguardDisplayManager {
@@ -54,9 +54,6 @@
 
     private final SparseArray<Presentation> mPresentations = new SparseArray<>();
 
-    private final NavigationBarController mNavBarController =
-            Dependency.get(NavigationBarController.class);
-
     private final DisplayManager.DisplayListener mDisplayListener =
             new DisplayManager.DisplayListener() {
 
@@ -227,7 +224,8 @@
         // Leave this task to {@link StatusBarKeyguardViewManager}
         if (displayId == DEFAULT_DISPLAY) return;
 
-        NavigationBarView navBarView = mNavBarController.getNavigationBarView(displayId);
+        NavigationBarView navBarView = Dependency.get(NavigationBarController.class)
+                .getNavigationBarView(displayId);
         // We may not have nav bar on a display.
         if (navBarView == null) return;
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 1db2e32..b048333 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -256,7 +256,7 @@
         mUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
         mSpringAnimation = new SpringAnimation(this, DynamicAnimation.Y);
         mInjectionInflationController =  new InjectionInflationController(
-            SystemUIFactory.getInstance().getRootComponent());
+            SystemUIFactory.getInstance().getSysUIComponent().createViewInstanceCreatorFactory());
         mViewConfiguration = ViewConfiguration.get(context);
         mKeyguardStateController = Dependency.get(KeyguardStateController.class);
         mSecondaryLockScreenController = new AdminSecondaryLockScreenController(context, this,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
index 17abfae..ac2160e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
@@ -24,11 +24,11 @@
 
 import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.Dependency;
+import com.android.systemui.dagger.SysUISingleton;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
-@Singleton
+@SysUISingleton
 public class KeyguardSecurityModel {
 
     /**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 878947f..c354241 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -94,6 +94,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
@@ -124,14 +125,13 @@
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Watches for updates that may be interesting to the keyguard, and provides
  * the up to date information as well as a registration for callbacks that care
  * to be updated.
  */
-@Singleton
+@SysUISingleton
 public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpable {
 
     private static final String TAG = "KeyguardUpdateMonitor";
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
index 2200b22..3775628 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
@@ -34,6 +34,7 @@
 
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dock.DockManager.DockEventListener;
 import com.android.systemui.plugins.ClockPlugin;
@@ -50,12 +51,11 @@
 import java.util.function.Supplier;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Manages custom clock faces for AOD and lock screen.
  */
-@Singleton
+@SysUISingleton
 public final class ClockManager {
 
     private static final String TAG = "ClockOptsProvider";
diff --git a/packages/SystemUI/src/com/android/systemui/ActivityIntentHelper.java b/packages/SystemUI/src/com/android/systemui/ActivityIntentHelper.java
index 63840bc..43b3929 100644
--- a/packages/SystemUI/src/com/android/systemui/ActivityIntentHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/ActivityIntentHelper.java
@@ -22,15 +22,16 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 
+import com.android.systemui.dagger.SysUISingleton;
+
 import java.util.List;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Contains useful methods for querying properties of an Activity Intent.
  */
-@Singleton
+@SysUISingleton
 public class ActivityIntentHelper {
 
     private final Context mContext;
diff --git a/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java b/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java
index 47a10af..3d6d381 100644
--- a/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java
@@ -18,13 +18,13 @@
 import android.content.Intent;
 import android.view.View;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.statusbar.phone.StatusBar;
 
 import java.util.Optional;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 
@@ -33,7 +33,7 @@
  * delegates to an actual implementation (StatusBar).
  */
 @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
-@Singleton
+@SysUISingleton
 public class ActivityStarterDelegate implements ActivityStarter {
 
     private Optional<Lazy<StatusBar>> mActualStarter;
diff --git a/packages/SystemUI/src/com/android/systemui/BootCompleteCacheImpl.kt b/packages/SystemUI/src/com/android/systemui/BootCompleteCacheImpl.kt
index aef1872..9eaf4c9 100644
--- a/packages/SystemUI/src/com/android/systemui/BootCompleteCacheImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/BootCompleteCacheImpl.kt
@@ -18,13 +18,13 @@
 
 import android.util.Log
 import com.android.internal.annotations.GuardedBy
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
 import java.io.FileDescriptor
 import java.io.PrintWriter
 import java.lang.ref.WeakReference
 import java.util.concurrent.atomic.AtomicBoolean
 import javax.inject.Inject
-import javax.inject.Singleton
 
 /**
  * Caches whether the device has reached [SystemService.PHASE_BOOT_COMPLETED].
@@ -32,7 +32,7 @@
  * This class is constructed and set by [SystemUIApplication] and will notify all listeners when
  * boot is completed.
  */
-@Singleton
+@SysUISingleton
 class BootCompleteCacheImpl @Inject constructor(dumpManager: DumpManager) :
         BootCompleteCache, Dumpable {
 
diff --git a/packages/SystemUI/src/com/android/systemui/DemoMode.java b/packages/SystemUI/src/com/android/systemui/DemoMode.java
deleted file mode 100644
index 5c39715..0000000
--- a/packages/SystemUI/src/com/android/systemui/DemoMode.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2013 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;
-
-import android.os.Bundle;
-
-public interface DemoMode {
-
-    public static final String DEMO_MODE_ALLOWED = "sysui_demo_allowed";
-
-    void dispatchDemoCommand(String command, Bundle args);
-
-    public static final String ACTION_DEMO = "com.android.systemui.demo";
-
-    public static final String EXTRA_COMMAND = "command";
-
-    public static final String COMMAND_ENTER = "enter";
-    public static final String COMMAND_EXIT = "exit";
-    public static final String COMMAND_CLOCK = "clock";
-    public static final String COMMAND_BATTERY = "battery";
-    public static final String COMMAND_NETWORK = "network";
-    public static final String COMMAND_BARS = "bars";
-    public static final String COMMAND_STATUS = "status";
-    public static final String COMMAND_NOTIFICATIONS = "notifications";
-    public static final String COMMAND_VOLUME = "volume";
-    public static final String COMMAND_OPERATOR = "operator";
-}
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 58f8c07..748a9c9 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -39,6 +39,7 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dock.DockManager;
@@ -47,6 +48,8 @@
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.model.SysUiState;
+import com.android.systemui.navigationbar.NavigationBarController;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.PluginDependencyProvider;
@@ -64,18 +67,18 @@
 import com.android.systemui.shared.system.PackageManagerWrapper;
 import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.NavigationBarController;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.NotificationViewHierarchyManager;
 import com.android.systemui.statusbar.SmartReplyController;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment;
 import com.android.systemui.statusbar.notification.NotificationFilter;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -85,10 +88,8 @@
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
 import com.android.systemui.statusbar.phone.ManagedProfileController;
-import com.android.systemui.statusbar.phone.NavigationModeController;
 import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -131,7 +132,6 @@
 
 import javax.inject.Inject;
 import javax.inject.Named;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 
@@ -150,7 +150,7 @@
  * they have no clients they should not have any registered resources like bound
  * services, registered receivers, etc.
  */
-@Singleton
+@SysUISingleton
 public class Dependency {
     /**
      * Key for getting a the main looper.
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java
index 2deeb12..d859a63 100644
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java
+++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java
@@ -24,36 +24,26 @@
 
 import com.android.internal.messages.nano.SystemMessageProto;
 import com.android.systemui.appops.AppOpsController;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.util.Assert;
 
-import java.util.Set;
-
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Tracks state of foreground services and notifications related to foreground services per user.
  */
-@Singleton
+@SysUISingleton
 public class ForegroundServiceController {
-    public static final int[] APP_OPS = new int[] {AppOpsManager.OP_CAMERA,
-            AppOpsManager.OP_SYSTEM_ALERT_WINDOW,
-            AppOpsManager.OP_RECORD_AUDIO,
-            AppOpsManager.OP_COARSE_LOCATION,
-            AppOpsManager.OP_FINE_LOCATION};
+    public static final int[] APP_OPS = new int[] {AppOpsManager.OP_SYSTEM_ALERT_WINDOW};
 
     private final SparseArray<ForegroundServicesUserState> mUserServices = new SparseArray<>();
     private final Object mMutex = new Object();
-    private final NotificationEntryManager mEntryManager;
     private final Handler mMainHandler;
 
     @Inject
-    public ForegroundServiceController(NotificationEntryManager entryManager,
-            AppOpsController appOpsController, @Main Handler mainHandler) {
-        mEntryManager = entryManager;
+    public ForegroundServiceController(AppOpsController appOpsController,
+            @Main Handler mainHandler) {
         mMainHandler = mainHandler;
         appOpsController.addCallback(APP_OPS, (code, uid, packageName, active) -> {
             mMainHandler.post(() -> {
@@ -87,19 +77,6 @@
     }
 
     /**
-     * Returns the keys for notifications from this package using the standard template,
-     * if they exist.
-     */
-    @Nullable
-    public ArraySet<String> getStandardLayoutKeys(int userId, String pkg) {
-        synchronized (mMutex) {
-            final ForegroundServicesUserState services = mUserServices.get(userId);
-            if (services == null) return null;
-            return services.getStandardLayoutKeys(pkg);
-        }
-    }
-
-    /**
      * Gets active app ops for this user and package
      */
     @Nullable
@@ -140,31 +117,6 @@
                 userServices.removeOp(packageName, appOpCode);
             }
         }
-
-        // TODO: (b/145659174) remove when moving to NewNotifPipeline. Replaced by
-        //  AppOpsCoordinator
-        // Update appOps if there are associated pending or visible notifications
-        final Set<String> notificationKeys = getStandardLayoutKeys(userId, packageName);
-        if (notificationKeys != null) {
-            boolean changed = false;
-            for (String key : notificationKeys) {
-                final NotificationEntry entry = mEntryManager.getPendingOrActiveNotif(key);
-                if (entry != null
-                        && uid == entry.getSbn().getUid()
-                        && packageName.equals(entry.getSbn().getPackageName())) {
-                    synchronized (entry.mActiveAppOps) {
-                        if (active) {
-                            changed |= entry.mActiveAppOps.add(appOpCode);
-                        } else {
-                            changed |= entry.mActiveAppOps.remove(appOpCode);
-                        }
-                    }
-                }
-            }
-            if (changed) {
-                mEntryManager.updateNotifications("appOpChanged pkg=" + packageName);
-            }
-        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
index bb44583..d6cb114 100644
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
@@ -21,10 +21,10 @@
 import android.content.Context;
 import android.os.Bundle;
 import android.service.notification.StatusBarNotification;
-import android.util.ArraySet;
 import android.util.Log;
 
 import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -33,10 +33,9 @@
 import com.android.systemui.util.time.SystemClock;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /** Updates foreground service notification state in response to notification data events. */
-@Singleton
+@SysUISingleton
 public class ForegroundServiceNotificationListener {
 
     private static final String TAG = "FgServiceController";
@@ -172,24 +171,8 @@
                                     sbn.getPackageName(), sbn.getKey());
                         }
                     }
-                    tagAppOps(entry);
                     return true;
                 },
                 true /* create if not found */);
     }
-
-    // TODO: (b/145659174) remove when moving to NewNotifPipeline. Replaced by
-    //  AppOpsCoordinator
-    private void tagAppOps(NotificationEntry entry) {
-        final StatusBarNotification sbn = entry.getSbn();
-        ArraySet<Integer> activeOps = mForegroundServiceController.getAppOps(
-                sbn.getUserId(),
-                sbn.getPackageName());
-        synchronized (entry.mActiveAppOps) {
-            entry.mActiveAppOps.clear();
-            if (activeOps != null) {
-                entry.mActiveAppOps.addAll(activeOps);
-            }
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/InitController.java b/packages/SystemUI/src/com/android/systemui/InitController.java
index a2dd123..9bb576b 100644
--- a/packages/SystemUI/src/com/android/systemui/InitController.java
+++ b/packages/SystemUI/src/com/android/systemui/InitController.java
@@ -14,16 +14,17 @@
 
 package com.android.systemui;
 
+import com.android.systemui.dagger.SysUISingleton;
+
 import java.util.ArrayList;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Created by {@link Dependency} on SystemUI startup. Add tasks which need to be executed only
  * after all other dependencies have been created.
  */
-@Singleton
+@SysUISingleton
 public class InitController {
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/LatencyTester.java b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
index dc0cb03..9c5a40c 100644
--- a/packages/SystemUI/src/com/android/systemui/LatencyTester.java
+++ b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
@@ -30,16 +30,16 @@
 import com.android.internal.util.LatencyTracker;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.phone.BiometricUnlockController;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Class that only runs on debuggable builds that listens to broadcasts that simulate actions in the
  * system that are used for testing the latency.
  */
-@Singleton
+@SysUISingleton
 public class LatencyTester extends SystemUI {
 
     private static final String
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index ad11d71..f663d1a 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -83,6 +83,7 @@
 import com.android.internal.util.Preconditions;
 import com.android.systemui.RegionInterceptingFrameLayout.RegionInterceptableView;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.qs.SecureSetting;
 import com.android.systemui.tuner.TunerService;
@@ -92,13 +93,12 @@
 import java.util.List;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * An overlay that draws screen decorations in software (e.g for rounded corners or display cutout)
  * for antialiasing and emulation purposes.
  */
-@Singleton
+@SysUISingleton
 public class ScreenDecorations extends SystemUI implements Tunable {
     private static final boolean DEBUG = false;
     private static final String TAG = "ScreenDecorations";
diff --git a/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java b/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java
index 73295a3..34efa35 100644
--- a/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java
@@ -41,6 +41,7 @@
 import android.widget.PopupWindow;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.statusbar.CommandQueue;
@@ -48,10 +49,9 @@
 import java.lang.ref.WeakReference;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /** Shows a restart-activity button when the foreground activity is in size compatibility mode. */
-@Singleton
+@SysUISingleton
 public class SizeCompatModeActivityController extends SystemUI implements CommandQueue.Callbacks {
     private static final String TAG = "SizeCompatMode";
 
diff --git a/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java b/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java
index 7a4ef2b..0b997d0 100644
--- a/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java
@@ -29,15 +29,15 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.settingslib.SliceBroadcastRelay;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Allows settings to register certain broadcasts to launch the settings app for pinned slices.
  * @see SliceBroadcastRelay
  */
-@Singleton
+@SysUISingleton
 public class SliceBroadcastRelayHandler extends SystemUI {
     private static final String TAG = "SliceBroadcastRelay";
     private static final boolean DEBUG = false;
diff --git a/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java b/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java
index 449ed8c..2365f12 100644
--- a/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java
@@ -16,6 +16,7 @@
 
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 
+import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.slice.SliceManager;
@@ -29,6 +30,7 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.text.BidiFormatter;
+import android.util.EventLog;
 import android.util.Log;
 import android.widget.CheckBox;
 import android.widget.TextView;
@@ -50,10 +52,17 @@
 
         mUri = getIntent().getParcelableExtra(SliceProvider.EXTRA_BIND_URI);
         mCallingPkg = getIntent().getStringExtra(SliceProvider.EXTRA_PKG);
-        mProviderPkg = getIntent().getStringExtra(SliceProvider.EXTRA_PROVIDER_PKG);
+        if (mUri == null) {
+            Log.e(TAG, SliceProvider.EXTRA_BIND_URI + " wasn't provided");
+            finish();
+            return;
+        }
 
         try {
             PackageManager pm = getPackageManager();
+            mProviderPkg = pm.resolveContentProvider(mUri.getAuthority(),
+                    PackageManager.GET_META_DATA).applicationInfo.packageName;
+            verifyCallingPkg();
             CharSequence app1 = BidiFormatter.getInstance().unicodeWrap(pm.getApplicationInfo(
                     mCallingPkg, 0).loadSafeLabel(pm, PackageItemInfo.DEFAULT_MAX_LABEL_SIZE_PX,
                     PackageItemInfo.SAFE_LABEL_FLAG_TRIM
@@ -97,4 +106,29 @@
     public void onDismiss(DialogInterface dialog) {
         finish();
     }
+
+    private void verifyCallingPkg() {
+        final String providerPkg = getIntent().getStringExtra("provider_pkg");
+        if (providerPkg == null || mProviderPkg.equals(providerPkg)) return;
+        final String callingPkg = getCallingPkg();
+        EventLog.writeEvent(0x534e4554, "159145361", getUid(callingPkg), String.format(
+                "pkg %s (disguised as %s) attempted to request permission to show %s slices in %s",
+                callingPkg, providerPkg, mProviderPkg, mCallingPkg));
+    }
+
+    @Nullable
+    private String getCallingPkg() {
+        final Uri referrer = getReferrer();
+        if (referrer == null) return null;
+        return referrer.getHost();
+    }
+
+    private int getUid(@Nullable final String pkg) {
+        if (pkg == null) return -1;
+        try {
+            return getPackageManager().getApplicationInfo(pkg, 0).uid;
+        } catch (NameNotFoundException e) {
+        }
+        return -1;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
index cacbf96..bb7906e 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
@@ -29,6 +29,10 @@
 import androidx.core.app.AppComponentFactory;
 
 import com.android.systemui.dagger.ContextComponentHelper;
+import com.android.systemui.dagger.SysUIComponent;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 
 import javax.inject.Inject;
 
@@ -61,7 +65,7 @@
             ((ContextInitializer) app).setContextAvailableCallback(
                     context -> {
                         SystemUIFactory.createFromConfig(context);
-                        SystemUIFactory.getInstance().getRootComponent().inject(
+                        SystemUIFactory.getInstance().getSysUIComponent().inject(
                                 SystemUIAppComponentFactory.this);
                     }
             );
@@ -81,8 +85,17 @@
             ((ContextInitializer) contentProvider).setContextAvailableCallback(
                     context -> {
                         SystemUIFactory.createFromConfig(context);
-                        SystemUIFactory.getInstance().getRootComponent().inject(
-                                contentProvider);
+                        SysUIComponent rootComponent =
+                                SystemUIFactory.getInstance().getSysUIComponent();
+                        try {
+                            Method injectMethod = rootComponent.getClass()
+                                    .getMethod("inject", contentProvider.getClass());
+                            injectMethod.invoke(rootComponent, contentProvider);
+                        } catch (NoSuchMethodException
+                                | IllegalAccessException
+                                | InvocationTargetException e) {
+                            // no-op
+                        }
                     }
             );
         }
@@ -98,7 +111,7 @@
         if (mComponentHelper == null) {
             // This shouldn't happen, but is seen on occasion.
             // Bug filed against framework to take a look: http://b/141008541
-            SystemUIFactory.getInstance().getRootComponent().inject(
+            SystemUIFactory.getInstance().getSysUIComponent().inject(
                     SystemUIAppComponentFactory.this);
         }
         Activity activity = mComponentHelper.resolveActivity(className);
@@ -116,7 +129,7 @@
         if (mComponentHelper == null) {
             // This shouldn't happen, but does when a device is freshly formatted.
             // Bug filed against framework to take a look: http://b/141008541
-            SystemUIFactory.getInstance().getRootComponent().inject(
+            SystemUIFactory.getInstance().getSysUIComponent().inject(
                     SystemUIAppComponentFactory.this);
         }
         Service service = mComponentHelper.resolveService(className);
@@ -134,7 +147,7 @@
         if (mComponentHelper == null) {
             // This shouldn't happen, but does when a device is freshly formatted.
             // Bug filed against framework to take a look: http://b/141008541
-            SystemUIFactory.getInstance().getRootComponent().inject(
+            SystemUIFactory.getInstance().getSysUIComponent().inject(
                     SystemUIAppComponentFactory.this);
         }
         BroadcastReceiver receiver = mComponentHelper.resolveBroadcastReceiver(className);
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index c84701c..7dcec3d 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -32,7 +32,8 @@
 import android.util.TimingsTraceLog;
 
 import com.android.systemui.dagger.ContextComponentHelper;
-import com.android.systemui.dagger.SystemUIRootComponent;
+import com.android.systemui.dagger.GlobalRootComponent;
+import com.android.systemui.dagger.SysUIComponent;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.util.NotificationChannels;
 
@@ -57,7 +58,8 @@
     private SystemUI[] mServices;
     private boolean mServicesStarted;
     private SystemUIAppComponentFactory.ContextAvailableCallback mContextAvailableCallback;
-    private SystemUIRootComponent mRootComponent;
+    private GlobalRootComponent mRootComponent;
+    private SysUIComponent mSysUIComponent;
 
     public SystemUIApplication() {
         super();
@@ -75,8 +77,9 @@
         log.traceBegin("DependencyInjection");
         mContextAvailableCallback.onContextAvailable(this);
         mRootComponent = SystemUIFactory.getInstance().getRootComponent();
-        mComponentHelper = mRootComponent.getContextComponentHelper();
-        mBootCompleteCache = mRootComponent.provideBootCacheImpl();
+        mSysUIComponent = SystemUIFactory.getInstance().getSysUIComponent();
+        mComponentHelper = mSysUIComponent.getContextComponentHelper();
+        mBootCompleteCache = mSysUIComponent.provideBootCacheImpl();
         log.traceEnd();
 
         // Set the application theme that is inherited by all services. Note that setting the
@@ -172,7 +175,7 @@
             }
         }
 
-        final DumpManager dumpManager = mRootComponent.createDumpManager();
+        final DumpManager dumpManager = mSysUIComponent.createDumpManager();
 
         Log.v(TAG, "Starting SystemUI services for user " +
                 Process.myUserHandle().getIdentifier() + ".");
@@ -215,7 +218,7 @@
 
             dumpManager.registerDumpable(mServices[i].getClass().getName(), mServices[i]);
         }
-        mRootComponent.getInitController().executePostInitTasks();
+        mSysUIComponent.getInitController().executePostInitTasks();
         log.traceEnd();
 
         mServicesStarted = true;
@@ -224,7 +227,7 @@
     @Override
     public void onConfigurationChanged(Configuration newConfig) {
         if (mServicesStarted) {
-            mRootComponent.getConfigurationController().onConfigurationChanged(newConfig);
+            mSysUIComponent.getConfigurationController().onConfigurationChanged(newConfig);
             int len = mServices.length;
             for (int i = 0; i < len; i++) {
                 if (mServices[i] != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 1a15c0a..f5c3649 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -27,21 +27,15 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.ViewMediatorCallback;
-import com.android.systemui.bubbles.BubbleController;
-import com.android.systemui.dagger.DaggerSystemUIRootComponent;
-import com.android.systemui.dagger.SystemUIRootComponent;
+import com.android.systemui.dagger.DaggerGlobalRootComponent;
+import com.android.systemui.dagger.GlobalRootComponent;
+import com.android.systemui.dagger.SysUIComponent;
+import com.android.systemui.dagger.WMComponent;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider;
-import com.android.systemui.statusbar.NotificationListener;
-import com.android.systemui.statusbar.NotificationMediaManager;
-import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
-import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.KeyguardBouncer;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.NotificationIconAreaController;
-import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import java.util.concurrent.Executor;
@@ -53,7 +47,9 @@
     private static final String TAG = "SystemUIFactory";
 
     static SystemUIFactory mFactory;
-    private SystemUIRootComponent mRootComponent;
+    private GlobalRootComponent mRootComponent;
+    private WMComponent mWMComponent;
+    private SysUIComponent mSysUIComponent;
 
     public static <T extends SystemUIFactory> T getInstance() {
         return (T) mFactory;
@@ -88,24 +84,31 @@
     public SystemUIFactory() {}
 
     private void init(Context context) {
-        mRootComponent = buildSystemUIRootComponent(context);
+        mRootComponent = buildGlobalRootComponent(context);
+        mWMComponent = mRootComponent.getWMComponentBuilder().build();
+        // TODO: use WMComponent to pass APIs into the SysUIComponent.
+        mSysUIComponent = mRootComponent.getSysUIComponent().build();
 
         // Every other part of our codebase currently relies on Dependency, so we
         // really need to ensure the Dependency gets initialized early on.
-        Dependency dependency = mRootComponent.createDependency();
+        Dependency dependency = mSysUIComponent.createDependency();
         dependency.start();
     }
 
-    protected SystemUIRootComponent buildSystemUIRootComponent(Context context) {
-        return DaggerSystemUIRootComponent.builder()
+    protected GlobalRootComponent buildGlobalRootComponent(Context context) {
+        return DaggerGlobalRootComponent.builder()
                 .context(context)
                 .build();
     }
 
-    public SystemUIRootComponent getRootComponent() {
+    public GlobalRootComponent getRootComponent() {
         return mRootComponent;
     }
 
+    public SysUIComponent getSysUIComponent() {
+        return mSysUIComponent;
+    }
+
     /** Returns the list of system UI components that should be started. */
     public String[] getSystemUIServiceComponents(Resources resources) {
         return resources.getStringArray(R.array.config_systemUIServiceComponents);
@@ -139,17 +142,4 @@
                 Dependency.get(KeyguardUpdateMonitor.class), bypassController,
                 new Handler(Looper.getMainLooper()));
     }
-
-    public NotificationIconAreaController createNotificationIconAreaController(Context context,
-            StatusBar statusBar,
-            NotificationWakeUpCoordinator wakeUpCoordinator,
-            KeyguardBypassController keyguardBypassController,
-            StatusBarStateController statusBarStateController) {
-        return new NotificationIconAreaController(context, statusBar, statusBarStateController,
-                wakeUpCoordinator, keyguardBypassController,
-                Dependency.get(NotificationMediaManager.class),
-                Dependency.get(NotificationListener.class),
-                Dependency.get(DozeParameters.class),
-                Dependency.get(BubbleController.class));
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/UiOffloadThread.java b/packages/SystemUI/src/com/android/systemui/UiOffloadThread.java
index d5a46de..e26dc7f 100644
--- a/packages/SystemUI/src/com/android/systemui/UiOffloadThread.java
+++ b/packages/SystemUI/src/com/android/systemui/UiOffloadThread.java
@@ -16,18 +16,19 @@
 
 package com.android.systemui;
 
+import com.android.systemui.dagger.SysUISingleton;
+
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Thread that offloads work from the UI thread but that is still perceptible to the user, so the
  * priority is the same as the main thread.
  */
-@Singleton
+@SysUISingleton
 public class UiOffloadThread {
 
     private final ExecutorService mExecutorService = Executors.newSingleThreadExecutor();
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java b/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java
index fe07ab6..123d678 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java
@@ -22,8 +22,7 @@
 import android.view.Display;
 
 import com.android.internal.annotations.VisibleForTesting;
-
-import javax.inject.Singleton;
+import com.android.systemui.dagger.SysUISingleton;
 
 /**
  * A class to control {@link MagnificationModeSwitch}. It should show the button UI with following
@@ -33,7 +32,7 @@
  *   <li> The magnification scale is changed by a user.</li>
  * <ol>
  */
-@Singleton
+@SysUISingleton
 public class ModeSwitchesController {
 
     private final DisplayIdIndexSupplier<MagnificationModeSwitch> mSwitchSupplier;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index 0135e4c..e49a5be 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -51,18 +51,18 @@
 import com.android.internal.util.ScreenshotHelper;
 import com.android.systemui.Dependency;
 import com.android.systemui.SystemUI;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.statusbar.phone.StatusBar;
 
 import java.util.Locale;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Class to register system actions with accessibility framework.
  */
-@Singleton
+@SysUISingleton
 public class SystemActions extends SystemUI {
     private static final String TAG = "SystemActions";
 
@@ -130,8 +130,6 @@
 
     private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
 
-    private Recents mRecents;
-    private StatusBar mStatusBar;
     private SystemActionsBroadcastReceiver mReceiver;
     private Locale mLocale;
     private AccessibilityManager mA11yManager;
@@ -139,8 +137,6 @@
     @Inject
     public SystemActions(Context context) {
         super(context);
-        mRecents = Dependency.get(Recents.class);
-        mStatusBar = Dependency.get(StatusBar.class);
         mReceiver = new SystemActionsBroadcastReceiver();
         mLocale = mContext.getResources().getConfiguration().getLocales().get(0);
         mA11yManager = (AccessibilityManager) mContext.getSystemService(
@@ -317,15 +313,15 @@
     }
 
     private void handleRecents() {
-        mRecents.toggleRecentApps();
+        Dependency.get(Recents.class).toggleRecentApps();
     }
 
     private void handleNotifications() {
-        mStatusBar.animateExpandNotificationsPanel();
+        Dependency.get(StatusBar.class).animateExpandNotificationsPanel();
     }
 
     private void handleQuickSettings() {
-        mStatusBar.animateExpandSettingsPanel(null);
+        Dependency.get(StatusBar.class).animateExpandSettingsPanel(null);
     }
 
     private void handlePowerDialog() {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
index d5f74a8..f601c52 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
@@ -33,11 +33,11 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.systemui.SystemUI;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.statusbar.CommandQueue;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Class to handle the interaction with
@@ -45,7 +45,7 @@
  * {@link AccessibilityManager#setWindowMagnificationConnection(IWindowMagnificationConnection)}
  * when {@code IStatusBar#requestWindowMagnificationConnection(boolean)} is called.
  */
-@Singleton
+@SysUISingleton
 public class WindowMagnification extends SystemUI implements WindowMagnifierCallback,
         CommandQueue.Callbacks {
     private static final String TAG = "WindowMagnification";
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
index 8187a223..eeb3b28 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
@@ -33,6 +33,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.util.Assert;
@@ -44,7 +45,6 @@
 import java.util.Set;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Controller to keep track of applications that have requested access to given App Ops
@@ -52,7 +52,7 @@
  * It can be subscribed to with callbacks. Additionally, it passes on the information to
  * NotificationPresenter to be displayed to the user.
  */
-@Singleton
+@SysUISingleton
 public class AppOpsControllerImpl implements AppOpsController,
         AppOpsManager.OnOpActiveChangedInternalListener,
         AppOpsManager.OnOpNotedListener, Dumpable {
diff --git a/packages/SystemUI/src/com/android/systemui/appops/PermissionFlagsCache.kt b/packages/SystemUI/src/com/android/systemui/appops/PermissionFlagsCache.kt
index 45ed78f..3fd838b 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/PermissionFlagsCache.kt
+++ b/packages/SystemUI/src/com/android/systemui/appops/PermissionFlagsCache.kt
@@ -19,11 +19,11 @@
 import android.content.pm.PackageManager
 import android.os.UserHandle
 import androidx.annotation.WorkerThread
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.util.Assert
 import java.util.concurrent.Executor
 import javax.inject.Inject
-import javax.inject.Singleton
 
 private data class PermissionFlagKey(
     val permission: String,
@@ -37,7 +37,7 @@
  * After a specific `{permission, package, uid}` has been requested, updates to it will be tracked,
  * and changes to the uid will trigger new requests (in the background).
  */
-@Singleton
+@SysUISingleton
 class PermissionFlagsCache @Inject constructor(
     private val packageManager: PackageManager,
     @Background private val executor: Executor
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java
index 7ae3e73..4390d51 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java
@@ -33,9 +33,10 @@
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.shared.system.QuickStepContract;
-import com.android.systemui.statusbar.phone.NavigationModeController;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -45,7 +46,6 @@
 import javax.inject.Inject;
 import javax.inject.Named;
 import javax.inject.Provider;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 
@@ -55,7 +55,7 @@
  * Controls when visual handles for Assistant gesture affordance should be shown or hidden using an
  * {@link AssistHandleBehavior}.
  */
-@Singleton
+@SysUISingleton
 public final class AssistHandleBehaviorController implements AssistHandleCallbacks, Dumpable {
 
     private static final String TAG = "AssistHandleBehavior";
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleLikeHomeBehavior.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleLikeHomeBehavior.java
index ccca447..5d8ec4b 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleLikeHomeBehavior.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleLikeHomeBehavior.java
@@ -21,6 +21,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.systemui.assist.AssistHandleBehaviorController.BehaviorController;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -29,7 +30,6 @@
 import java.io.PrintWriter;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 
@@ -37,7 +37,7 @@
  * Assistant Handle behavior that makes Assistant handles show/hide when the home handle is
  * shown/hidden, respectively.
  */
-@Singleton
+@SysUISingleton
 final class AssistHandleLikeHomeBehavior implements BehaviorController {
 
     private final StatusBarStateController.StateListener mStatusBarStateListener =
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleOffBehavior.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleOffBehavior.java
index df913f9..86d3254 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleOffBehavior.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleOffBehavior.java
@@ -19,12 +19,12 @@
 import android.content.Context;
 
 import com.android.systemui.assist.AssistHandleBehaviorController.BehaviorController;
+import com.android.systemui.dagger.SysUISingleton;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /** Assistant handle behavior that hides the Assistant handles. */
-@Singleton
+@SysUISingleton
 final class AssistHandleOffBehavior implements BehaviorController {
 
     @Inject
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java
index 8e49d58..1b2e4c6 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java
@@ -36,6 +36,7 @@
 import com.android.systemui.BootCompleteCache;
 import com.android.systemui.assist.AssistHandleBehaviorController.BehaviorController;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -54,7 +55,6 @@
 
 import javax.inject.Inject;
 import javax.inject.Named;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 
@@ -63,7 +63,7 @@
  * shows the handles when on lockscreen, and shows the handles temporarily when changing tasks or
  * entering overview.
  */
-@Singleton
+@SysUISingleton
 final class AssistHandleReminderExpBehavior implements BehaviorController {
 
     private static final String LEARNING_TIME_ELAPSED_KEY = "reminder_exp_learning_time_elapsed";
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleViewController.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleViewController.java
index 5010f31..f19e53f 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleViewController.java
@@ -30,7 +30,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.CornerHandleView;
 import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.NavigationBarTransitions;
+import com.android.systemui.navigationbar.NavigationBarTransitions;
 
 /**
  * A class for managing Assistant handle show, hide and animation.
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistLogger.kt b/packages/SystemUI/src/com/android/systemui/assist/AssistLogger.kt
index 08edad3..4d0fd43 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistLogger.kt
@@ -28,11 +28,11 @@
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.assist.AssistantInvocationEvent.Companion.deviceStateFromLegacyDeviceState
 import com.android.systemui.assist.AssistantInvocationEvent.Companion.eventFromLegacyInvocationType
+import com.android.systemui.dagger.SysUISingleton
 import javax.inject.Inject
-import javax.inject.Singleton
 
 /** Class for reporting events related to Assistant sessions. */
-@Singleton
+@SysUISingleton
 open class AssistLogger @Inject constructor(
     protected val context: Context,
     protected val uiEventLogger: UiEventLogger,
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
index 6d179f2..e85cafa 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
@@ -46,6 +46,7 @@
 import com.android.settingslib.applications.InterestingConfigChanges;
 import com.android.systemui.R;
 import com.android.systemui.assist.ui.DefaultUiController;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.statusbar.CommandQueue;
@@ -53,14 +54,13 @@
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 
 /**
  * Class to manage everything related to assist in SystemUI.
  */
-@Singleton
+@SysUISingleton
 public class AssistManager {
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistModule.java b/packages/SystemUI/src/com/android/systemui/assist/AssistModule.java
index 6f5a17d..ef43f87 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistModule.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistModule.java
@@ -25,13 +25,13 @@
 import androidx.slice.Clock;
 
 import com.android.internal.app.AssistUtils;
-import com.android.systemui.statusbar.NavigationBarController;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.navigationbar.NavigationBarController;
 
 import java.util.EnumMap;
 import java.util.Map;
 
 import javax.inject.Named;
-import javax.inject.Singleton;
 
 import dagger.Module;
 import dagger.Provides;
@@ -44,7 +44,7 @@
     static final String UPTIME_NAME = "uptime";
 
     @Provides
-    @Singleton
+    @SysUISingleton
     @Named(ASSIST_HANDLE_THREAD_NAME)
     static Handler provideBackgroundHandler() {
         final HandlerThread backgroundHandlerThread =
@@ -54,7 +54,7 @@
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static Map<AssistHandleBehavior, AssistHandleBehaviorController.BehaviorController>
             provideAssistHandleBehaviorControllerMap(
                     AssistHandleOffBehavior offBehavior,
@@ -76,13 +76,13 @@
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static AssistUtils provideAssistUtils(Context context) {
         return new AssistUtils(context);
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     @Named(UPTIME_NAME)
     static Clock provideSystemClock() {
         return SystemClock::uptimeMillis;
diff --git a/packages/SystemUI/src/com/android/systemui/assist/DeviceConfigHelper.java b/packages/SystemUI/src/com/android/systemui/assist/DeviceConfigHelper.java
index 86b7c74..4d5c44c 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/DeviceConfigHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/DeviceConfigHelper.java
@@ -22,17 +22,18 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.systemui.dagger.SysUISingleton;
+
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Wrapper class for retrieving System UI device configuration values.
  *
  * Can be mocked in tests for ease of testing the effects of particular values.
  */
-@Singleton
+@SysUISingleton
 public class DeviceConfigHelper {
 
     @Inject
diff --git a/packages/SystemUI/src/com/android/systemui/assist/PhoneStateMonitor.java b/packages/SystemUI/src/com/android/systemui/assist/PhoneStateMonitor.java
index 257ad50..50d559b 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/PhoneStateMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/PhoneStateMonitor.java
@@ -30,6 +30,7 @@
 import com.android.systemui.BootCompleteCache;
 import com.android.systemui.Dependency;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.PackageManagerWrapper;
@@ -42,12 +43,11 @@
 import java.util.Optional;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 
 /** Class to monitor and report the state of the phone. */
-@Singleton
+@SysUISingleton
 public final class PhoneStateMonitor {
 
     public static final int PHONE_STATE_AOD1 = 1;
diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
index 05f3617..1d90096 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
@@ -41,18 +41,18 @@
 import com.android.systemui.assist.AssistLogger;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.assist.AssistantSessionEvent;
-import com.android.systemui.statusbar.NavigationBarController;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.navigationbar.NavigationBarController;
 
 import java.util.Locale;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Default UiController implementation. Shows white edge lights along the bottom of the phone,
  * expanding from the corners to meet in the center.
  */
-@Singleton
+@SysUISingleton
 public class DefaultUiController implements AssistManager.UiController {
 
     private static final String TAG = "DefaultUiController";
diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/InvocationLightsView.java b/packages/SystemUI/src/com/android/systemui/assist/ui/InvocationLightsView.java
index e5121a8..ac39ed5 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/ui/InvocationLightsView.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/ui/InvocationLightsView.java
@@ -33,9 +33,9 @@
 import com.android.settingslib.Utils;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
-import com.android.systemui.statusbar.NavigationBarController;
-import com.android.systemui.statusbar.phone.NavigationBarFragment;
-import com.android.systemui.statusbar.phone.NavigationBarTransitions;
+import com.android.systemui.navigationbar.NavigationBarController;
+import com.android.systemui.navigationbar.NavigationBar;
+import com.android.systemui.navigationbar.NavigationBarTransitions;
 
 import java.util.ArrayList;
 
@@ -284,7 +284,7 @@
                 return;
             }
 
-            NavigationBarFragment navBar = controller.getDefaultNavigationBarFragment();
+            NavigationBar navBar = controller.getDefaultNavigationBar();
             if (navBar == null) {
                 return;
             }
@@ -301,7 +301,7 @@
                 return;
             }
 
-            NavigationBarFragment navBar = controller.getDefaultNavigationBarFragment();
+            NavigationBar navBar = controller.getDefaultNavigationBar();
             if (navBar == null) {
                 return;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 361ea67..ea18b11 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -49,25 +49,28 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.SomeArgs;
 import com.android.systemui.SystemUI;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.doze.DozeReceiver;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.CommandQueue;
 
 import java.util.List;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Receives messages sent from {@link com.android.server.biometrics.BiometricService} and shows the
  * appropriate biometric UI (e.g. BiometricDialogView).
  */
-@Singleton
+@SysUISingleton
 public class AuthController extends SystemUI implements CommandQueue.Callbacks,
-        AuthDialogCallback {
+        AuthDialogCallback, DozeReceiver {
 
-    private static final String TAG = "BiometricPrompt/AuthController";
+    private static final String TAG = "AuthController";
     private static final boolean DEBUG = true;
 
     private final CommandQueue mCommandQueue;
+    private final StatusBarStateController mStatusBarStateController;
     private final Injector mInjector;
 
     // TODO: These should just be saved from onSaveState
@@ -77,6 +80,7 @@
 
     private Handler mHandler = new Handler(Looper.getMainLooper());
     private WindowManager mWindowManager;
+    @Nullable
     private UdfpsController mUdfpsController;
     @VisibleForTesting
     IActivityTaskManager mActivityTaskManager;
@@ -143,6 +147,13 @@
     };
 
     @Override
+    public void dozeTimeTick() {
+        if (mUdfpsController != null) {
+            mUdfpsController.dozeTimeTick();
+        }
+    }
+
+    @Override
     public void onTryAgainPressed() {
         if (mReceiver == null) {
             Log.e(TAG, "onTryAgainPressed: Receiver is null");
@@ -251,14 +262,17 @@
     }
 
     @Inject
-    public AuthController(Context context, CommandQueue commandQueue) {
-        this(context, commandQueue, new Injector());
+    public AuthController(Context context, CommandQueue commandQueue,
+            StatusBarStateController statusBarStateController) {
+        this(context, commandQueue, statusBarStateController, new Injector());
     }
 
     @VisibleForTesting
-    AuthController(Context context, CommandQueue commandQueue, Injector injector) {
+    AuthController(Context context, CommandQueue commandQueue,
+            StatusBarStateController statusBarStateController, Injector injector) {
         super(context);
         mCommandQueue = commandQueue;
+        mStatusBarStateController = statusBarStateController;
         mInjector = injector;
 
         IntentFilter filter = new IntentFilter();
@@ -280,7 +294,7 @@
                     fpm.getSensorProperties();
             for (FingerprintSensorProperties props : fingerprintSensorProperties) {
                 if (props.sensorType == FingerprintSensorProperties.TYPE_UDFPS) {
-                    mUdfpsController = new UdfpsController(mContext, mWindowManager);
+                    mUdfpsController = new UdfpsController(mContext, mStatusBarStateController);
                     break;
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 739c2b1..82fb808 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -16,23 +16,32 @@
 
 package com.android.systemui.biometrics;
 
+import android.annotation.NonNull;
 import android.annotation.SuppressLint;
+import android.content.ContentResolver;
 import android.content.Context;
+import android.content.res.TypedArray;
 import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.IFingerprintService;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.Handler;
 import android.os.Looper;
-import android.os.RemoteException;
+import android.os.PowerManager;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.TextUtils;
 import android.util.Log;
+import android.util.MathUtils;
+import android.util.Spline;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.WindowManager;
-import android.widget.LinearLayout;
 
+import com.android.internal.BrightnessSynchronizer;
 import com.android.systemui.R;
+import com.android.systemui.doze.DozeReceiver;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
 
 import java.io.FileWriter;
 import java.io.IOException;
@@ -41,19 +50,34 @@
  * Shows and hides the under-display fingerprint sensor (UDFPS) overlay, handles UDFPS touch events,
  * and coordinates triggering of the high-brightness mode (HBM).
  */
-class UdfpsController {
+class UdfpsController implements DozeReceiver {
     private static final String TAG = "UdfpsController";
+    // Gamma approximation for the sRGB color space.
+    private static final float DISPLAY_GAMMA = 2.2f;
 
-    private final Context mContext;
     private final FingerprintManager mFingerprintManager;
     private final WindowManager mWindowManager;
+    private final ContentResolver mContentResolver;
     private final Handler mHandler;
-
-    private UdfpsView mView;
-    private WindowManager.LayoutParams mLayoutParams;
-    private String mHbmPath;
-    private String mHbmEnableCommand;
-    private String mHbmDisableCommand;
+    private final WindowManager.LayoutParams mLayoutParams;
+    private final UdfpsView mView;
+    // Debugfs path to control the high-brightness mode.
+    private final String mHbmPath;
+    private final String mHbmEnableCommand;
+    private final String mHbmDisableCommand;
+    private final boolean mHbmSupported;
+    // Brightness in nits in the high-brightness mode.
+    private final float mHbmNits;
+    // A spline mapping from the device's backlight value, normalized to the range [0, 1.0], to a
+    // brightness in nits.
+    private final Spline mBacklightToNitsSpline;
+    // A spline mapping from a value in nits to a backlight value of a hypothetical panel whose
+    // maximum backlight value corresponds to our panel's high-brightness mode.
+    // The output is normalized to the range [0, 1.0].
+    private Spline mNitsToHbmBacklightSpline;
+    // Default non-HBM backlight value normalized to the range [0, 1.0]. Used as a fallback when the
+    // actual brightness value cannot be retrieved.
+    private final float mDefaultBrightness;
     private boolean mIsOverlayShowing;
 
     public class UdfpsOverlayController extends IUdfpsOverlayController.Stub {
@@ -66,26 +90,33 @@
         public void hideUdfpsOverlay() {
             UdfpsController.this.hideUdfpsOverlay();
         }
+
+        @Override
+        public void setDebugMessage(String message) {
+            mView.setDebugMessage(message);
+        }
     }
 
     @SuppressLint("ClickableViewAccessibility")
     private final UdfpsView.OnTouchListener mOnTouchListener = (v, event) -> {
+        UdfpsView view = (UdfpsView) v;
+        final boolean isFingerDown = view.isScrimShowing();
         switch (event.getAction()) {
             case MotionEvent.ACTION_DOWN:
             case MotionEvent.ACTION_MOVE:
-                boolean isValidTouch = mView.isValidTouch(event.getX(), event.getY(),
+                final boolean isValidTouch = view.isValidTouch(event.getX(), event.getY(),
                         event.getPressure());
-                if (!mView.isFingerDown() && isValidTouch) {
+                if (!isFingerDown && isValidTouch) {
                     onFingerDown((int) event.getX(), (int) event.getY(), event.getTouchMinor(),
                             event.getTouchMajor());
-                } else if (mView.isFingerDown() && !isValidTouch) {
+                } else if (isFingerDown && !isValidTouch) {
                     onFingerUp();
                 }
                 return true;
 
             case MotionEvent.ACTION_UP:
             case MotionEvent.ACTION_CANCEL:
-                if (mView.isFingerDown()) {
+                if (isFingerDown) {
                     onFingerUp();
                 }
                 return true;
@@ -95,94 +126,200 @@
         }
     };
 
-    UdfpsController(Context context, WindowManager windowManager) {
-        mContext = context;
+    UdfpsController(@NonNull Context context,
+            @NonNull StatusBarStateController statusBarStateController) {
         mFingerprintManager = context.getSystemService(FingerprintManager.class);
-        mWindowManager = windowManager;
+        mWindowManager = context.getSystemService(WindowManager.class);
+        mContentResolver = context.getContentResolver();
         mHandler = new Handler(Looper.getMainLooper());
-        start();
-    }
+        mLayoutParams = createLayoutParams(context);
 
-    private void start() {
-        Log.v(TAG, "start");
+        mView = (UdfpsView) LayoutInflater.from(context).inflate(R.layout.udfps_view, null, false);
 
-        Point displaySize = new Point();
-        mWindowManager.getDefaultDisplay().getRealSize(displaySize);
+        mHbmPath = context.getResources().getString(R.string.udfps_hbm_sysfs_path);
+        mHbmEnableCommand = context.getResources().getString(R.string.udfps_hbm_enable_command);
+        mHbmDisableCommand = context.getResources().getString(R.string.udfps_hbm_disable_command);
+
+        mHbmSupported = !TextUtils.isEmpty(mHbmPath);
+        mView.setHbmSupported(mHbmSupported);
+        statusBarStateController.addCallback(mView);
+
+        // This range only consists of the minimum and maximum values, which only cover
+        // non-high-brightness mode.
+        float[] nitsRange = toFloatArray(context.getResources().obtainTypedArray(
+                com.android.internal.R.array.config_screenBrightnessNits));
+
+        // The last value of this range corresponds to the high-brightness mode.
+        float[] nitsAutoBrightnessValues = toFloatArray(context.getResources().obtainTypedArray(
+                com.android.internal.R.array.config_autoBrightnessDisplayValuesNits));
+
+        mHbmNits = nitsAutoBrightnessValues[nitsAutoBrightnessValues.length - 1];
+        float[] hbmNitsRange = {nitsRange[0], mHbmNits};
+
+        // This range only consists of the minimum and maximum backlight values, which only apply
+        // in non-high-brightness mode.
+        float[] normalizedBacklightRange = normalizeBacklightRange(
+                context.getResources().getIntArray(
+                        com.android.internal.R.array.config_screenBrightnessBacklight));
+
+        mBacklightToNitsSpline = Spline.createSpline(normalizedBacklightRange, nitsRange);
+        mNitsToHbmBacklightSpline = Spline.createSpline(hbmNitsRange, normalizedBacklightRange);
+        mDefaultBrightness = obtainDefaultBrightness(context);
+
         // TODO(b/160025856): move to the "dump" method.
-        Log.v(TAG, "UdfpsController | display size: " + displaySize.x + "x"
-                + displaySize.y);
-
-        mLayoutParams = new WindowManager.LayoutParams(
-                displaySize.x,
-                displaySize.y,
-                // TODO(b/152419866): Use the UDFPS window type when it becomes available.
-                WindowManager.LayoutParams.TYPE_BOOT_PROGRESS,
-                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
-                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
-                PixelFormat.TRANSLUCENT);
-        mLayoutParams.setTitle(TAG);
-        mLayoutParams.windowAnimations = 0;
-
-        LinearLayout layout = new LinearLayout(mContext);
-        layout.setLayoutParams(mLayoutParams);
-        mView = (UdfpsView) LayoutInflater.from(mContext).inflate(R.layout.udfps_view, layout,
-                false);
-        mView.setOnTouchListener(mOnTouchListener);
-
-        mHbmPath = mContext.getResources().getString(R.string.udfps_hbm_sysfs_path);
-        mHbmEnableCommand = mContext.getResources().getString(R.string.udfps_hbm_enable_command);
-        mHbmDisableCommand = mContext.getResources().getString(R.string.udfps_hbm_disable_command);
+        Log.v(TAG, String.format("ctor | mNitsRange: [%f, %f]", nitsRange[0], nitsRange[1]));
+        Log.v(TAG, String.format("ctor | mHbmNitsRange: [%f, %f]", hbmNitsRange[0],
+                hbmNitsRange[1]));
+        Log.v(TAG, String.format("ctor | mNormalizedBacklightRange: [%f, %f]",
+                normalizedBacklightRange[0], normalizedBacklightRange[1]));
 
         mFingerprintManager.setUdfpsOverlayController(new UdfpsOverlayController());
         mIsOverlayShowing = false;
     }
 
+    @Override
+    public void dozeTimeTick() {
+        mView.dozeTimeTick();
+    }
+
     private void showUdfpsOverlay() {
         mHandler.post(() -> {
-            Log.v(TAG, "showUdfpsOverlay | adding window");
             if (!mIsOverlayShowing) {
                 try {
+                    Log.v(TAG, "showUdfpsOverlay | adding window");
                     mWindowManager.addView(mView, mLayoutParams);
                     mIsOverlayShowing = true;
+                    mView.setOnTouchListener(mOnTouchListener);
                 } catch (RuntimeException e) {
                     Log.e(TAG, "showUdfpsOverlay | failed to add window", e);
                 }
+            } else {
+                Log.v(TAG, "showUdfpsOverlay | the overlay is already showing");
             }
         });
     }
 
     private void hideUdfpsOverlay() {
-        onFingerUp();
         mHandler.post(() -> {
-            Log.v(TAG, "hideUdfpsOverlay | removing window");
             if (mIsOverlayShowing) {
+                Log.v(TAG, "hideUdfpsOverlay | removing window");
+                mView.setOnTouchListener(null);
+                // Reset the controller back to its starting state.
+                onFingerUp();
                 mWindowManager.removeView(mView);
                 mIsOverlayShowing = false;
+            } else {
+                Log.v(TAG, "hideUdfpsOverlay | the overlay is already hidden");
             }
         });
     }
 
+    // Returns a value in the range of [0, 255].
+    private int computeScrimOpacity() {
+        // Backlight setting can be NaN, -1.0f, and [0.0f, 1.0f].
+        float backlightSetting = Settings.System.getFloatForUser(mContentResolver,
+                Settings.System.SCREEN_BRIGHTNESS_FLOAT, mDefaultBrightness,
+                UserHandle.USER_CURRENT);
+
+        // Constrain the backlight setting to [0.0f, 1.0f].
+        float backlightValue = MathUtils.constrain(backlightSetting,
+                PowerManager.BRIGHTNESS_MIN,
+                PowerManager.BRIGHTNESS_MAX);
+
+        // Interpolate the backlight value to nits.
+        float nits = mBacklightToNitsSpline.interpolate(backlightValue);
+
+        // Interpolate nits to a backlight value for a panel with enabled HBM.
+        float interpolatedHbmBacklightValue = mNitsToHbmBacklightSpline.interpolate(nits);
+
+        float gammaCorrectedHbmBacklightValue = (float) Math.pow(interpolatedHbmBacklightValue,
+                1.0f / DISPLAY_GAMMA);
+        float scrimOpacity = PowerManager.BRIGHTNESS_MAX - gammaCorrectedHbmBacklightValue;
+
+        // Interpolate the opacity value from [0.0f, 1.0f] to [0, 255].
+        return BrightnessSynchronizer.brightnessFloatToInt(scrimOpacity);
+    }
+
     private void onFingerDown(int x, int y, float minor, float major) {
+        mView.setScrimAlpha(computeScrimOpacity());
+        mView.showScrimAndDot();
         try {
-            FileWriter fw = new FileWriter(mHbmPath);
-            fw.write(mHbmEnableCommand);
-            fw.close();
+            if (mHbmSupported) {
+                FileWriter fw = new FileWriter(mHbmPath);
+                fw.write(mHbmEnableCommand);
+                fw.close();
+            }
+            mFingerprintManager.onFingerDown(x, y, minor, major);
         } catch (IOException e) {
+            mView.hideScrimAndDot();
             Log.e(TAG, "onFingerDown | failed to enable HBM: " + e.getMessage());
         }
-        mView.onFingerDown();
-        mFingerprintManager.onFingerDown(x, y, minor, major);
     }
 
     private void onFingerUp() {
         mFingerprintManager.onFingerUp();
-        mView.onFingerUp();
-        try {
-            FileWriter fw = new FileWriter(mHbmPath);
-            fw.write(mHbmDisableCommand);
-            fw.close();
-        } catch (IOException e) {
-            Log.e(TAG, "onFingerUp | failed to disable HBM: " + e.getMessage());
+        // Hiding the scrim before disabling HBM results in less noticeable flicker.
+        mView.hideScrimAndDot();
+        if (mHbmSupported) {
+            try {
+                FileWriter fw = new FileWriter(mHbmPath);
+                fw.write(mHbmDisableCommand);
+                fw.close();
+            } catch (IOException e) {
+                mView.showScrimAndDot();
+                Log.e(TAG, "onFingerUp | failed to disable HBM: " + e.getMessage());
+            }
         }
     }
+
+    private static WindowManager.LayoutParams createLayoutParams(Context context) {
+        Point displaySize = new Point();
+        context.getDisplay().getRealSize(displaySize);
+        // TODO(b/160025856): move to the "dump" method.
+        Log.v(TAG, "createLayoutParams | display size: " + displaySize.x + "x"
+                + displaySize.y);
+        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+                displaySize.x,
+                displaySize.y,
+                // TODO(b/152419866): Use the UDFPS window type when it becomes available.
+                WindowManager.LayoutParams.TYPE_BOOT_PROGRESS,
+                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                        | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
+                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                        | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
+                PixelFormat.TRANSLUCENT);
+        lp.setTitle(TAG);
+        lp.setFitInsetsTypes(0);
+        return lp;
+    }
+
+    private static float obtainDefaultBrightness(Context context) {
+        PowerManager powerManager = context.getSystemService(PowerManager.class);
+        if (powerManager == null) {
+            Log.e(TAG, "PowerManager is unavailable. Can't obtain default brightness.");
+            return 0f;
+        }
+        return MathUtils.constrain(powerManager.getBrightnessConstraint(
+                PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DEFAULT), PowerManager.BRIGHTNESS_MIN,
+                PowerManager.BRIGHTNESS_MAX);
+    }
+
+    private static float[] toFloatArray(TypedArray array) {
+        final int n = array.length();
+        float[] vals = new float[n];
+        for (int i = 0; i < n; i++) {
+            vals[i] = array.getFloat(i, PowerManager.BRIGHTNESS_OFF_FLOAT);
+        }
+        array.recycle();
+        return vals;
+    }
+
+    private static float[] normalizeBacklightRange(int[] backlight) {
+        final int n = backlight.length;
+        float[] normalizedBacklight = new float[n];
+        for (int i = 0; i < n; i++) {
+            normalizedBacklight[i] = BrightnessSynchronizer.brightnessIntToFloat(backlight[i]);
+        }
+        return normalizedBacklight;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
index 8190550..d7e9138 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.biometrics;
 
+import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
+
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
@@ -23,34 +25,57 @@
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.util.MathUtils;
 import android.view.View;
 import android.view.ViewTreeObserver;
 
 import com.android.systemui.R;
+import com.android.systemui.doze.DozeReceiver;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
 
 /**
  * A full screen view with a configurable illumination dot and scrim.
  */
-public class UdfpsView extends View {
+public class UdfpsView extends View implements DozeReceiver,
+        StatusBarStateController.StateListener {
     private static final String TAG = "UdfpsView";
 
+    // Values in pixels.
+    private static final float SENSOR_SHADOW_RADIUS = 2.0f;
+    private static final float SENSOR_OUTLINE_WIDTH = 2.0f;
+
+    private static final int DEBUG_TEXT_SIZE_PX = 32;
+
     private final Rect mScrimRect;
     private final Paint mScrimPaint;
+    private final Paint mDebugTextPaint;
 
-    private float mSensorX;
-    private float mSensorY;
     private final RectF mSensorRect;
     private final Paint mSensorPaint;
     private final float mSensorRadius;
-    private final float mSensorMarginBottom;
+    private final float mSensorCenterY;
     private final float mSensorTouchAreaCoefficient;
+    private final int mMaxBurnInOffsetX;
+    private final int mMaxBurnInOffsetY;
 
     private final Rect mTouchableRegion;
     private final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsListener;
 
-    private boolean mIsFingerDown;
+    // This is calculated from the screen's dimensions at runtime, as opposed to mSensorCenterY,
+    // which is defined in layout.xml
+    private float mSensorCenterX;
+
+    // AOD anti-burn-in offsets
+    private float mInterpolatedDarkAmount;
+    private float mBurnInOffsetX;
+    private float mBurnInOffsetY;
+
+    private boolean mIsScrimShowing;
+    private boolean mHbmSupported;
+    private String mDebugMessage;
 
     public UdfpsView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -61,7 +86,7 @@
             if (!a.hasValue(R.styleable.UdfpsView_sensorRadius)) {
                 throw new IllegalArgumentException("UdfpsView must contain sensorRadius");
             }
-            if (!a.hasValue(R.styleable.UdfpsView_sensorMarginBottom)) {
+            if (!a.hasValue(R.styleable.UdfpsView_sensorCenterY)) {
                 throw new IllegalArgumentException("UdfpsView must contain sensorMarginBottom");
             }
             if (!a.hasValue(R.styleable.UdfpsView_sensorTouchAreaCoefficient)) {
@@ -69,22 +94,35 @@
                         "UdfpsView must contain sensorTouchAreaCoefficient");
             }
             mSensorRadius = a.getDimension(R.styleable.UdfpsView_sensorRadius, 0f);
-            mSensorMarginBottom = a.getDimension(R.styleable.UdfpsView_sensorMarginBottom, 0f);
+            mSensorCenterY = a.getDimension(R.styleable.UdfpsView_sensorCenterY, 0f);
             mSensorTouchAreaCoefficient = a.getFloat(
                     R.styleable.UdfpsView_sensorTouchAreaCoefficient, 0f);
         } finally {
             a.recycle();
         }
 
+        mMaxBurnInOffsetX = getResources()
+                .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x);
+        mMaxBurnInOffsetY = getResources()
+                .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
 
         mScrimRect = new Rect();
         mScrimPaint = new Paint(0 /* flags */);
-        mScrimPaint.setARGB(110 /* a */, 0 /* r */, 0 /* g */, 0 /* b */);
+        mScrimPaint.setColor(Color.BLACK);
 
         mSensorRect = new RectF();
         mSensorPaint = new Paint(0 /* flags */);
+        mSensorPaint.setAntiAlias(true);
         mSensorPaint.setColor(Color.WHITE);
         mSensorPaint.setStyle(Paint.Style.STROKE);
+        mSensorPaint.setStrokeWidth(SENSOR_OUTLINE_WIDTH);
+        mSensorPaint.setShadowLayer(SENSOR_SHADOW_RADIUS, 0, 0, Color.BLACK);
+        mSensorPaint.setAntiAlias(true);
+
+        mDebugTextPaint = new Paint();
+        mDebugTextPaint.setAntiAlias(true);
+        mDebugTextPaint.setColor(Color.BLUE);
+        mDebugTextPaint.setTextSize(DEBUG_TEXT_SIZE_PX);
 
         mTouchableRegion = new Rect();
         mInsetsListener = internalInsetsInfo -> {
@@ -93,7 +131,30 @@
             internalInsetsInfo.touchableRegion.set(mTouchableRegion);
         };
 
-        mIsFingerDown = false;
+        mIsScrimShowing = false;
+    }
+
+    @Override
+    public void dozeTimeTick() {
+        updateAodPosition();
+    }
+
+    @Override
+    public void onDozeAmountChanged(float linear, float eased) {
+        mInterpolatedDarkAmount = eased;
+        updateAodPosition();
+    }
+
+    private void updateAodPosition() {
+        mBurnInOffsetX = MathUtils.lerp(0f,
+                getBurnInOffset(mMaxBurnInOffsetX * 2, true /* xAxis */)
+                        - mMaxBurnInOffsetX,
+                mInterpolatedDarkAmount);
+        mBurnInOffsetY = MathUtils.lerp(0f,
+                getBurnInOffset(mMaxBurnInOffsetY * 2, false /* xAxis */)
+                        - 0.5f * mMaxBurnInOffsetY,
+                mInterpolatedDarkAmount);
+        postInvalidate();
     }
 
     @Override
@@ -104,10 +165,11 @@
         final int h = getLayoutParams().height;
         final int w = getLayoutParams().width;
         mScrimRect.set(0 /* left */, 0 /* top */, w, h);
-        mSensorX = w / 2f;
-        mSensorY = h - mSensorMarginBottom - mSensorRadius;
-        mSensorRect.set(mSensorX - mSensorRadius, mSensorY - mSensorRadius,
-                mSensorX + mSensorRadius, mSensorY + mSensorRadius);
+        mSensorCenterX = w / 2f;
+        mSensorRect.set(mSensorCenterX - mSensorRadius, mSensorCenterY - mSensorRadius,
+                mSensorCenterX + mSensorRadius, mSensorCenterY + mSensorRadius);
+
+        // Sets mTouchableRegion with rounded up values from mSensorRect.
         mSensorRect.roundOut(mTouchableRegion);
 
         getViewTreeObserver().addOnComputeInternalInsetsListener(mInsetsListener);
@@ -123,32 +185,55 @@
     @Override
     protected void onDraw(Canvas canvas) {
         super.onDraw(canvas);
-        if (mIsFingerDown) {
+
+        if (mIsScrimShowing && mHbmSupported) {
+            // Only draw the scrim if HBM is supported.
             canvas.drawRect(mScrimRect, mScrimPaint);
         }
+
+        // Translation should affect everything but the scrim.
+        canvas.save();
+        canvas.translate(mBurnInOffsetX, mBurnInOffsetY);
+        if (!TextUtils.isEmpty(mDebugMessage)) {
+            canvas.drawText(mDebugMessage, 0, 160, mDebugTextPaint);
+        }
         canvas.drawOval(mSensorRect, mSensorPaint);
+        canvas.restore();
+    }
+
+    void setHbmSupported(boolean hbmSupported) {
+        mHbmSupported = hbmSupported;
+    }
+
+    void setDebugMessage(String message) {
+        mDebugMessage = message;
+        postInvalidate();
     }
 
     boolean isValidTouch(float x, float y, float pressure) {
-        return x > (mSensorX - mSensorRadius * mSensorTouchAreaCoefficient)
-                && x < (mSensorX + mSensorRadius * mSensorTouchAreaCoefficient)
-                && y > (mSensorY - mSensorRadius * mSensorTouchAreaCoefficient)
-                && y < (mSensorY + mSensorRadius * mSensorTouchAreaCoefficient);
+        return x > (mSensorCenterX - mSensorRadius * mSensorTouchAreaCoefficient)
+                && x < (mSensorCenterX + mSensorRadius * mSensorTouchAreaCoefficient)
+                && y > (mSensorCenterY - mSensorRadius * mSensorTouchAreaCoefficient)
+                && y < (mSensorCenterY + mSensorRadius * mSensorTouchAreaCoefficient);
     }
 
-    boolean isFingerDown() {
-        return mIsFingerDown;
+    void setScrimAlpha(int alpha) {
+        mScrimPaint.setAlpha(alpha);
     }
 
-    void onFingerDown() {
-        mIsFingerDown = true;
+    boolean isScrimShowing() {
+        return mIsScrimShowing;
+    }
+
+    void showScrimAndDot() {
+        mIsScrimShowing = true;
         mSensorPaint.setStyle(Paint.Style.FILL);
-        postInvalidate();
+        invalidate();
     }
 
-    void onFingerUp() {
-        mIsFingerDown = false;
+    void hideScrimAndDot() {
+        mIsScrimShowing = false;
         mSensorPaint.setStyle(Paint.Style.STROKE);
-        postInvalidate();
+        invalidate();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index c5639ea..9e9d85a 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -84,7 +84,6 @@
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.systemui.Dumpable;
-import com.android.systemui.bubbles.animation.StackAnimationController;
 import com.android.systemui.bubbles.dagger.BubbleModule;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.model.SysUiState;
@@ -96,6 +95,7 @@
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationRemoveInterceptor;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.ScrimView;
 import com.android.systemui.statusbar.notification.NotificationChannelHelper;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
@@ -109,7 +109,6 @@
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBar;
@@ -139,7 +138,8 @@
     @IntDef({DISMISS_USER_GESTURE, DISMISS_AGED, DISMISS_TASK_FINISHED, DISMISS_BLOCKED,
             DISMISS_NOTIF_CANCEL, DISMISS_ACCESSIBILITY_ACTION, DISMISS_NO_LONGER_BUBBLE,
             DISMISS_USER_CHANGED, DISMISS_GROUP_CANCELLED, DISMISS_INVALID_INTENT,
-            DISMISS_OVERFLOW_MAX_REACHED, DISMISS_SHORTCUT_REMOVED, DISMISS_PACKAGE_REMOVED})
+            DISMISS_OVERFLOW_MAX_REACHED, DISMISS_SHORTCUT_REMOVED, DISMISS_PACKAGE_REMOVED,
+            DISMISS_NO_BUBBLE_UP})
     @Target({FIELD, LOCAL_VARIABLE, PARAMETER})
     @interface DismissReason {}
 
@@ -156,6 +156,7 @@
     static final int DISMISS_OVERFLOW_MAX_REACHED = 11;
     static final int DISMISS_SHORTCUT_REMOVED = 12;
     static final int DISMISS_PACKAGE_REMOVED = 13;
+    static final int DISMISS_NO_BUBBLE_UP = 14;
 
     private final Context mContext;
     private final NotificationEntryManager mNotificationEntryManager;
@@ -1262,8 +1263,18 @@
             rankingMap.getRanking(key, mTmpRanking);
             boolean isActiveBubble = mBubbleData.hasAnyBubbleWithKey(key);
             if (isActiveBubble && !mTmpRanking.canBubble()) {
-                mBubbleData.dismissBubbleWithKey(entry.getKey(),
-                        BubbleController.DISMISS_BLOCKED);
+                // If this entry is no longer allowed to bubble, dismiss with the BLOCKED reason.
+                // This means that the app or channel's ability to bubble has been revoked.
+                mBubbleData.dismissBubbleWithKey(
+                        key, BubbleController.DISMISS_BLOCKED);
+            } else if (isActiveBubble
+                    && !mNotificationInterruptStateProvider.shouldBubbleUp(entry)) {
+                // If this entry is allowed to bubble, but cannot currently bubble up, dismiss it.
+                // This happens when DND is enabled and configured to hide bubbles. Dismissing with
+                // the reason DISMISS_NO_BUBBLE_UP will retain the underlying notification, so that
+                // the bubble will be re-created if shouldBubbleUp returns true.
+                mBubbleData.dismissBubbleWithKey(
+                        key, BubbleController.DISMISS_NO_BUBBLE_UP);
             } else if (entry != null && mTmpRanking.isBubble() && !isActiveBubble) {
                 entry.setFlagBubble(true);
                 onEntryUpdated(entry);
@@ -1340,8 +1351,10 @@
                     mStackView.removeBubble(bubble);
                 }
 
-                // If the bubble is removed for user switching, leave the notification in place.
-                if (reason == DISMISS_USER_CHANGED) {
+                // Leave the notification in place if we're dismissing due to user switching, or
+                // because DND is suppressing the bubble. In both of those cases, we need to be able
+                // to restore the bubble from the notification later.
+                if (reason == DISMISS_USER_CHANGED || reason == DISMISS_NO_BUBBLE_UP) {
                     continue;
                 }
                 if (reason == DISMISS_NOTIF_CANCEL) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index 10f4385..5c6d16d 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -34,7 +34,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.R;
 import com.android.systemui.bubbles.BubbleController.DismissReason;
-import com.android.systemui.shared.system.SysUiStatsLog;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
@@ -52,12 +52,11 @@
 import java.util.function.Predicate;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Keeps track of active bubbles.
  */
-@Singleton
+@SysUISingleton
 public class BubbleData {
 
     private BubbleLoggerImpl mLogger = new BubbleLoggerImpl();
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt
index db64a13..f129d31 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt
@@ -18,25 +18,24 @@
 import android.annotation.SuppressLint
 import android.annotation.UserIdInt
 import android.content.pm.LauncherApps
+import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_CACHED
 import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC
 import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER
-import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_CACHED
 import android.os.UserHandle
 import android.util.Log
 import com.android.systemui.bubbles.storage.BubbleEntity
 import com.android.systemui.bubbles.storage.BubblePersistentRepository
 import com.android.systemui.bubbles.storage.BubbleVolatileRepository
+import com.android.systemui.dagger.SysUISingleton
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.cancelAndJoin
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.yield
-
 import javax.inject.Inject
-import javax.inject.Singleton
 
-@Singleton
+@SysUISingleton
 internal class BubbleDataRepository @Inject constructor(
     private val volatileRepository: BubbleVolatileRepository,
     private val persistentRepository: BubblePersistentRepository,
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
index 10d301d..eecc41c 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
@@ -25,23 +25,22 @@
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.bubbles.BubbleData;
 import com.android.systemui.bubbles.BubbleDataRepository;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.util.FloatingContentCoordinator;
 
-import javax.inject.Singleton;
-
 import dagger.Module;
 import dagger.Provides;
 
@@ -51,7 +50,7 @@
 
     /**
      */
-    @Singleton
+    @SysUISingleton
     @Provides
     static BubbleController newBubbleController(
             Context context,
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubblePersistentRepository.kt b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubblePersistentRepository.kt
index 5b4d8c7..f447965 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubblePersistentRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubblePersistentRepository.kt
@@ -18,13 +18,13 @@
 import android.content.Context
 import android.util.AtomicFile
 import android.util.Log
+import com.android.systemui.dagger.SysUISingleton
 import java.io.File
 import java.io.FileOutputStream
 import java.io.IOException
 import javax.inject.Inject
-import javax.inject.Singleton
 
-@Singleton
+@SysUISingleton
 class BubblePersistentRepository @Inject constructor(
     context: Context
 ) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleVolatileRepository.kt b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleVolatileRepository.kt
index 894970f..c6d5732 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleVolatileRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleVolatileRepository.kt
@@ -19,8 +19,8 @@
 import android.os.UserHandle
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.bubbles.ShortcutKey
+import com.android.systemui.dagger.SysUISingleton
 import javax.inject.Inject
-import javax.inject.Singleton
 
 private const val CAPACITY = 16
 
@@ -28,7 +28,7 @@
  * BubbleVolatileRepository holds the most updated snapshot of list of bubbles for in-memory
  * manipulation.
  */
-@Singleton
+@SysUISingleton
 class BubbleVolatileRepository @Inject constructor(
     private val launcherApps: LauncherApps
 ) {
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
index f35322b..83b6df3 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
@@ -31,6 +31,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.classifier.brightline.BrightLineFalsingManager;
 import com.android.systemui.classifier.brightline.FalsingDataProvider;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.dock.DockManager;
@@ -48,14 +49,13 @@
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Simple passthrough implementation of {@link FalsingManager} allowing plugins to swap in.
  *
  * {@link FalsingManagerImpl} is used when a Plugin is not loaded.
  */
-@Singleton
+@SysUISingleton
 public class FalsingManagerProxy implements FalsingManager, Dumpable {
 
     private static final String PROXIMITY_SENSOR_TAG = "FalsingManager";
diff --git a/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java b/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
index 3ca1f59..5b33428 100644
--- a/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
+++ b/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
@@ -28,6 +28,7 @@
 import com.android.internal.colorextraction.types.Tonal;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import java.io.FileDescriptor;
@@ -35,12 +36,11 @@
 import java.util.Arrays;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * ColorExtractor aware of wallpaper visibility
  */
-@Singleton
+@SysUISingleton
 public class SysuiColorExtractor extends ColorExtractor implements Dumpable,
         ConfigurationController.ConfigurationListener {
     private static final String TAG = "SysuiColorExtractor";
diff --git a/packages/SystemUI/src/com/android/systemui/controls/CustomIconCache.kt b/packages/SystemUI/src/com/android/systemui/controls/CustomIconCache.kt
index cca0f16..ed60849 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/CustomIconCache.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/CustomIconCache.kt
@@ -19,8 +19,8 @@
 import android.content.ComponentName
 import android.graphics.drawable.Icon
 import androidx.annotation.GuardedBy
+import com.android.systemui.dagger.SysUISingleton
 import javax.inject.Inject
-import javax.inject.Singleton
 
 /**
  * Icon cache for custom icons sent with controls.
@@ -28,7 +28,7 @@
  * It assumes that only one component can be current at the time, to minimize the number of icons
  * stored at a given time.
  */
-@Singleton
+@SysUISingleton
 class CustomIconCache @Inject constructor() {
 
     private var currentComponent: ComponentName? = null
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
index aa3e193..658f46e 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
@@ -28,14 +28,14 @@
 import android.service.controls.actions.ControlAction
 import android.util.Log
 import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.util.concurrency.DelayableExecutor
 import dagger.Lazy
 import java.util.concurrent.atomic.AtomicBoolean
 import javax.inject.Inject
-import javax.inject.Singleton
 
-@Singleton
+@SysUISingleton
 @VisibleForTesting
 open class ControlsBindingControllerImpl @Inject constructor(
     private val context: Context,
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
index 40c8c6b..495872f 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -42,6 +42,7 @@
 import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.globalactions.GlobalActionsDialog
@@ -52,18 +53,17 @@
 import java.util.concurrent.TimeUnit
 import java.util.function.Consumer
 import javax.inject.Inject
-import javax.inject.Singleton
 
-@Singleton
+@SysUISingleton
 class ControlsControllerImpl @Inject constructor (
-    private val context: Context,
-    @Background private val executor: DelayableExecutor,
-    private val uiController: ControlsUiController,
-    private val bindingController: ControlsBindingController,
-    private val listingController: ControlsListingController,
-    private val broadcastDispatcher: BroadcastDispatcher,
-    optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>,
-    dumpManager: DumpManager
+        private val context: Context,
+        @Background private val executor: DelayableExecutor,
+        private val uiController: ControlsUiController,
+        private val bindingController: ControlsBindingController,
+        private val listingController: ControlsListingController,
+        private val broadcastDispatcher: BroadcastDispatcher,
+        optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>,
+        dumpManager: DumpManager
 ) : Dumpable, ControlsController {
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
index 9a5b960..38a82f8 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
@@ -19,10 +19,10 @@
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.dagger.SysUISingleton
 import dagger.Lazy
 import java.util.Optional
 import javax.inject.Inject
-import javax.inject.Singleton
 
 /**
  * Pseudo-component to inject into classes outside `com.android.systemui.controls`.
@@ -30,7 +30,7 @@
  * If `featureEnabled` is false, all the optionals should be empty. The controllers will only be
  * instantiated if `featureEnabled` is true.
  */
-@Singleton
+@SysUISingleton
 class ControlsComponent @Inject constructor(
     @ControlsFeatureEnabled private val featureEnabled: Boolean,
     private val lazyControlsController: Lazy<ControlsController>,
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
index 4760d29..fbdeb30 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
@@ -33,13 +33,13 @@
 import com.android.systemui.controls.ui.ControlActionCoordinatorImpl
 import com.android.systemui.controls.ui.ControlsUiController
 import com.android.systemui.controls.ui.ControlsUiControllerImpl
+import com.android.systemui.dagger.SysUISingleton
 import dagger.Binds
 import dagger.BindsOptionalOf
 import dagger.Module
 import dagger.Provides
 import dagger.multibindings.ClassKey
 import dagger.multibindings.IntoMap
-import javax.inject.Singleton
 
 /**
  * Module for injecting classes in `com.android.systemui.controls`-
@@ -55,7 +55,7 @@
     companion object {
         @JvmStatic
         @Provides
-        @Singleton
+        @SysUISingleton
         @ControlsFeatureEnabled
         fun providesControlsFeatureEnabled(pm: PackageManager): Boolean {
             return pm.hasSystemFeature(PackageManager.FEATURE_CONTROLS)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
index 1cd9712..0d4439f 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
@@ -27,11 +27,11 @@
 import com.android.settingslib.applications.ServiceListing
 import com.android.settingslib.widget.CandidateInfo
 import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import java.util.concurrent.Executor
 import java.util.concurrent.atomic.AtomicInteger
 import javax.inject.Inject
-import javax.inject.Singleton
 
 private fun createServiceListing(context: Context): ServiceListing {
     return ServiceListing.Builder(context).apply {
@@ -52,7 +52,7 @@
  * * Has an intent-filter responding to [ControlsProviderService.CONTROLS_ACTION]
  * * Has the bind permission `android.permission.BIND_CONTROLS`
  */
-@Singleton
+@SysUISingleton
 class ControlsListingControllerImpl @VisibleForTesting constructor(
     private val context: Context,
     @Background private val backgroundExecutor: Executor,
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
index e15380b..ab82225 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
@@ -16,30 +16,29 @@
 
 package com.android.systemui.controls.ui
 
+import android.annotation.MainThread
 import android.app.Dialog
 import android.content.Context
 import android.content.Intent
 import android.content.pm.PackageManager
 import android.content.pm.ResolveInfo
-import android.annotation.MainThread
-import android.os.Vibrator
 import android.os.VibrationEffect
+import android.os.Vibrator
 import android.service.controls.Control
 import android.service.controls.actions.BooleanAction
 import android.service.controls.actions.CommandAction
 import android.service.controls.actions.FloatAction
 import android.util.Log
 import android.view.HapticFeedbackConstants
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.globalactions.GlobalActionsComponent
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.concurrency.DelayableExecutor
-
 import javax.inject.Inject
-import javax.inject.Singleton
 
-@Singleton
+@SysUISingleton
 class ControlActionCoordinatorImpl @Inject constructor(
     private val context: Context,
     private val bgExecutor: DelayableExecutor,
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 5f75c96..3710310 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -52,6 +52,7 @@
 import com.android.systemui.controls.management.ControlsFavoritingActivity
 import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.controls.management.ControlsProviderSelectorActivity
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.globalactions.GlobalActionsPopupMenu
@@ -62,11 +63,10 @@
 import java.text.Collator
 import java.util.function.Consumer
 import javax.inject.Inject
-import javax.inject.Singleton
 
 private data class ControlKey(val componentName: ComponentName, val controlId: String)
 
-@Singleton
+@SysUISingleton
 class ControlsUiControllerImpl @Inject constructor (
     val controlsController: Lazy<ControlsController>,
     val context: Context,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentResolver.java b/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentResolver.java
index f91d795..b41915b 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentResolver.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentResolver.java
@@ -27,12 +27,11 @@
 
 import javax.inject.Inject;
 import javax.inject.Provider;
-import javax.inject.Singleton;
 
 /**
  * Used during Service and Activity instantiation to make them injectable.
  */
-@Singleton
+@SysUISingleton
 public class ContextComponentResolver implements ContextComponentHelper {
     private final Map<Class<?>, Provider<Activity>> mActivityCreators;
     private final Map<Class<?>, Provider<Service>> mServiceCreators;
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
index 3d31070..5a77723 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
@@ -31,6 +31,7 @@
 import android.view.IWindowManager;
 import android.view.LayoutInflater;
 import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.UiEventLogger;
@@ -40,6 +41,8 @@
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.Prefs;
 import com.android.systemui.accessibility.ModeSwitchesController;
+import com.android.systemui.accessibility.SystemActions;
+import com.android.systemui.assist.AssistManager;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger;
 import com.android.systemui.dagger.qualifiers.Background;
@@ -47,25 +50,37 @@
 import com.android.systemui.doze.AlwaysOnDisplayPolicy;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.model.SysUiState;
+import com.android.systemui.navigationbar.NavigationBarController;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.PluginInitializerImpl;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.recents.Recents;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.shared.plugins.PluginManagerImpl;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
+import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.NavigationBarController;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.phone.AutoHideController;
 import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
+import com.android.systemui.statusbar.phone.ShadeController;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DataSaverController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.util.leak.LeakDetector;
 
+import java.util.Optional;
 import java.util.concurrent.Executor;
 
 import javax.inject.Named;
-import javax.inject.Singleton;
 
+import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
 
@@ -80,8 +95,9 @@
 @Module(includes = {NightDisplayListenerModule.class})
 public class DependencyProvider {
 
-    @Singleton
+    /** */
     @Provides
+    @SysUISingleton
     @Named(TIME_TICK_HANDLER_NAME)
     public Handler provideTimeTickHandler() {
         HandlerThread thread = new HandlerThread("TimeTick");
@@ -108,14 +124,16 @@
         return new Handler();
     }
 
-    @Singleton
+    /** */
     @Provides
+    @SysUISingleton
     public DataSaverController provideDataSaverController(NetworkController networkController) {
         return networkController.getDataSaverController();
     }
 
-    @Singleton
+    /** */
     @Provides
+    @SysUISingleton
     public DisplayMetrics provideDisplayMetrics(Context context, WindowManager windowManager) {
         DisplayMetrics displayMetrics = new DisplayMetrics();
         context.getDisplay().getMetrics(displayMetrics);
@@ -123,69 +141,116 @@
     }
 
     /** */
-    @Singleton
     @Provides
+    @SysUISingleton
     public INotificationManager provideINotificationManager() {
         return INotificationManager.Stub.asInterface(
                 ServiceManager.getService(Context.NOTIFICATION_SERVICE));
     }
 
     /** */
-    @Singleton
     @Provides
+    @SysUISingleton
     public LayoutInflater providerLayoutInflater(Context context) {
         return LayoutInflater.from(context);
     }
 
-    @Singleton
+    /** */
     @Provides
+    @SysUISingleton
     public LeakDetector provideLeakDetector() {
         return LeakDetector.create();
 
     }
 
-    @Singleton
+    /** */
     @Provides
+    @SysUISingleton
     public MetricsLogger provideMetricsLogger() {
         return new MetricsLogger();
     }
 
-    @Singleton
+    /** */
     @Provides
+    @SysUISingleton
     public PluginManager providePluginManager(Context context) {
         return new PluginManagerImpl(context, new PluginInitializerImpl());
     }
 
-    @Singleton
+    /** */
     @Provides
+    @SysUISingleton
     public NavigationBarController provideNavigationBarController(Context context,
-            @Main Handler mainHandler, CommandQueue commandQueue) {
-        return new NavigationBarController(context, mainHandler, commandQueue);
+            WindowManager windowManager,
+            Lazy<AssistManager> assistManagerLazy,
+            AccessibilityManager accessibilityManager,
+            AccessibilityManagerWrapper accessibilityManagerWrapper,
+            DeviceProvisionedController deviceProvisionedController,
+            MetricsLogger metricsLogger,
+            OverviewProxyService overviewProxyService,
+            NavigationModeController navigationModeController,
+            StatusBarStateController statusBarStateController,
+            SysUiState sysUiFlagsContainer,
+            BroadcastDispatcher broadcastDispatcher,
+            CommandQueue commandQueue,
+            Divider divider,
+            Optional<Recents> recentsOptional,
+            Lazy<StatusBar> statusBarLazy,
+            ShadeController shadeController,
+            NotificationRemoteInputManager notificationRemoteInputManager,
+            SystemActions systemActions,
+            @Main Handler mainHandler,
+            UiEventLogger uiEventLogger,
+            ConfigurationController configurationController) {
+        return new NavigationBarController(context,
+                windowManager,
+                assistManagerLazy,
+                accessibilityManager,
+                accessibilityManagerWrapper,
+                deviceProvisionedController,
+                metricsLogger,
+                overviewProxyService,
+                navigationModeController,
+                statusBarStateController,
+                sysUiFlagsContainer,
+                broadcastDispatcher,
+                commandQueue,
+                divider,
+                recentsOptional,
+                statusBarLazy,
+                shadeController,
+                notificationRemoteInputManager,
+                systemActions,
+                mainHandler,
+                uiEventLogger,
+                configurationController);
     }
 
-    @Singleton
+    /** */
     @Provides
+    @SysUISingleton
     public ConfigurationController provideConfigurationController(Context context) {
         return new ConfigurationControllerImpl(context);
     }
 
     /** */
-    @Singleton
+    @SysUISingleton
     @Provides
     public AutoHideController provideAutoHideController(Context context,
             @Main Handler mainHandler, IWindowManager iWindowManager) {
         return new AutoHideController(context, mainHandler, iWindowManager);
     }
 
-    @Singleton
+    /** */
     @Provides
+    @SysUISingleton
     public ActivityManagerWrapper provideActivityManagerWrapper() {
         return ActivityManagerWrapper.getInstance();
     }
 
     /** Provides and initializes the {#link BroadcastDispatcher} for SystemUI */
-    @Singleton
     @Provides
+    @SysUISingleton
     public BroadcastDispatcher providesBroadcastDispatcher(
             Context context,
             @Background Looper backgroundLooper,
@@ -199,8 +264,9 @@
         return bD;
     }
 
-    @Singleton
+    /** */
     @Provides
+    @SysUISingleton
     public DevicePolicyManagerWrapper provideDevicePolicyManagerWrapper() {
         return DevicePolicyManagerWrapper.getInstance();
     }
@@ -230,22 +296,28 @@
     }
 
     /** */
-    @Singleton
     @Provides
+    public SystemActions providesSystemActions(Context context) {
+        return new SystemActions(context);
+    }
+
+    /** */
+    @Provides
+    @SysUISingleton
     public Choreographer providesChoreographer() {
         return Choreographer.getInstance();
     }
 
     /** Provides an instance of {@link com.android.internal.logging.UiEventLogger} */
-    @Singleton
     @Provides
+    @SysUISingleton
     static UiEventLogger provideUiEventLogger() {
         return new UiEventLoggerImpl();
     }
 
     /** */
-    @Singleton
     @Provides
+    @SysUISingleton
     public ModeSwitchesController providesModeSwitchesController(Context context) {
         return new ModeSwitchesController(context);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java b/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java
new file mode 100644
index 0000000..553655b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java
@@ -0,0 +1,78 @@
+/*
+ * 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.dagger;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.hardware.display.AmbientDisplayConfiguration;
+import android.util.DisplayMetrics;
+import android.view.Choreographer;
+
+import com.android.systemui.Prefs;
+import com.android.systemui.dagger.qualifiers.Main;
+
+import javax.inject.Singleton;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Supplies globally scoped instances.
+ *
+ * Providers in this module will be accessible to both WMComponent and SysUIComponent scoped
+ * classes. They are in here because they are either needed globally or are inherently universal
+ * to the application.
+ *
+ * Note that just because a class might be used by both WM and SysUI does not necessarily mean that
+ * it should got into this module. If WM and SysUI might need the class for different purposes
+ * or different semantics, it may make sense to ask them to supply their own. Something like
+ * threading and concurrency provide a good example. Both components need
+ * Threads/Handlers/Executors, but they need separate instances of them in many cases.
+ *
+ * Please use discretion when adding things to the global scope.
+ */
+@Module
+public class GlobalModule {
+    /** */
+    @Provides
+    @Main
+    public SharedPreferences provideSharePreferences(Context context) {
+        return Prefs.get(context);
+    }
+
+    /** */
+    @Provides
+    public AmbientDisplayConfiguration provideAmbientDisplayConfiguration(Context context) {
+        return new AmbientDisplayConfiguration(context);
+    }
+
+    /** */
+    @Provides
+    @Singleton
+    public Choreographer providesChoreographer() {
+        return Choreographer.getInstance();
+    }
+
+    /** */
+    @Provides
+    @Singleton
+    public DisplayMetrics provideDisplayMetrics(Context context) {
+        DisplayMetrics displayMetrics = new DisplayMetrics();
+        context.getDisplay().getMetrics(displayMetrics);
+        return displayMetrics;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
new file mode 100644
index 0000000..3d7c8ad4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
@@ -0,0 +1,55 @@
+/*
+ * 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.dagger;
+
+import android.content.Context;
+
+import javax.inject.Singleton;
+
+import dagger.BindsInstance;
+import dagger.Component;
+
+/**
+ * Root component for Dagger injection.
+ */
+@Singleton
+@Component(modules = {
+        SysUISubcomponentModule.class,
+        WMModule.class})
+public interface GlobalRootComponent {
+
+    /**
+     * Builder for a GlobalRootComponent.
+     */
+    @Component.Builder
+    interface Builder {
+        @BindsInstance
+        Builder context(Context context);
+
+        GlobalRootComponent build();
+    }
+
+    /**
+     * Builder for a WMComponent.
+     */
+    WMComponent.Builder getWMComponentBuilder();
+
+    /**
+     * Builder for a SysuiComponent.
+     */
+    SysUIComponent.Builder getSysUIComponent();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
similarity index 65%
rename from packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java
rename to packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 9fdbb6d..7281faf1 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -16,34 +16,24 @@
 
 package com.android.systemui.dagger;
 
-import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
-
-import android.content.ContentProvider;
-import android.content.Context;
-
 import com.android.systemui.BootCompleteCacheImpl;
 import com.android.systemui.Dependency;
 import com.android.systemui.InitController;
 import com.android.systemui.SystemUIAppComponentFactory;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.keyguard.KeyguardSliceProvider;
 import com.android.systemui.onehanded.dagger.OneHandedModule;
 import com.android.systemui.pip.phone.dagger.PipModule;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.util.InjectionInflationController;
 
-import javax.inject.Named;
-import javax.inject.Singleton;
-
-import dagger.BindsInstance;
-import dagger.Component;
+import dagger.Subcomponent;
 
 /**
- * Root component for Dagger injection.
+ * Dagger Subcomponent for Core SysUI.
  */
-@Singleton
-@Component(modules = {
+@SysUISingleton
+@Subcomponent(modules = {
         DefaultComponentBinder.class,
         DependencyProvider.class,
         DependencyBinder.class,
@@ -53,70 +43,54 @@
         SystemUIBinder.class,
         SystemUIModule.class,
         SystemUIDefaultModule.class})
-public interface SystemUIRootComponent {
+public interface SysUIComponent {
 
     /**
-     * Builder for a SystemUIRootComponent.
+     * Builder for a SysUIComponent.
      */
-    @Component.Builder
+    @Subcomponent.Builder
     interface Builder {
-        @BindsInstance
-        Builder context(Context context);
-
-        SystemUIRootComponent build();
+        SysUIComponent build();
     }
 
     /**
      * Provides a BootCompleteCache.
      */
-    @Singleton
+    @SysUISingleton
     BootCompleteCacheImpl provideBootCacheImpl();
 
     /**
      * Creates a ContextComponentHelper.
      */
-    @Singleton
+    @SysUISingleton
     ConfigurationController getConfigurationController();
 
     /**
      * Creates a ContextComponentHelper.
      */
-    @Singleton
+    @SysUISingleton
     ContextComponentHelper getContextComponentHelper();
 
     /**
      * Main dependency providing module.
      */
-    @Singleton
+    @SysUISingleton
     Dependency createDependency();
 
     /** */
-    @Singleton
+    @SysUISingleton
     DumpManager createDumpManager();
 
     /**
-     * FragmentCreator generates all Fragments that need injection.
-     */
-    @Singleton
-    FragmentService.FragmentCreator createFragmentCreator();
-
-
-    /**
      * Creates a InitController.
      */
-    @Singleton
+    @SysUISingleton
     InitController getInitController();
 
     /**
-     * ViewCreator generates all Views that need injection.
+     * ViewInstanceCreator generates all Views that need injection.
      */
-    InjectionInflationController.ViewCreator createViewCreator();
-
-    /**
-     * Whether notification long press is allowed.
-     */
-    @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME)
-    boolean allowNotificationLongPressName();
+    InjectionInflationController.ViewInstanceCreator.Factory createViewInstanceCreatorFactory();
 
     /**
      * Member injection into the supplied argument.
@@ -126,10 +100,5 @@
     /**
      * Member injection into the supplied argument.
      */
-    void inject(ContentProvider contentProvider);
-
-    /**
-     * Member injection into the supplied argument.
-     */
     void inject(KeyguardSliceProvider keyguardSliceProvider);
 }
diff --git a/media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl b/packages/SystemUI/src/com/android/systemui/dagger/SysUISingleton.java
similarity index 64%
copy from media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl
copy to packages/SystemUI/src/com/android/systemui/dagger/SysUISingleton.java
index b1f99e6..a4b3342 100644
--- a/media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUISingleton.java
@@ -14,17 +14,20 @@
  * limitations under the License.
  */
 
-package android.media;
+package com.android.systemui.dagger;
 
-import android.media.AudioDeviceAttributes;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Scope;
 
 /**
- * AIDL for AudioService to signal audio strategy-preferred device updates.
- *
- * {@hide}
+ * Scope annotation for singleton items within the SysUIComponent.
  */
-oneway interface IStrategyPreferredDeviceDispatcher {
-
-    void dispatchPrefDeviceChanged(int strategyId, in AudioDeviceAttributes device);
-
+@Documented
+@Retention(RUNTIME)
+@Scope
+public @interface SysUISingleton {
 }
diff --git a/tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUISubcomponentModule.java
similarity index 73%
copy from tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java
copy to packages/SystemUI/src/com/android/systemui/dagger/SysUISubcomponentModule.java
index 09ef472..aacc693 100644
--- a/tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUISubcomponentModule.java
@@ -13,3 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
+package com.android.systemui.dagger;
+
+import dagger.Module;
+
+/**
+ * Dagger module for including the WMComponent.
+ */
+@Module(subcomponents = {SysUIComponent.class})
+public abstract class SysUISubcomponentModule {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
index 251ce13..7fe9faf 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
@@ -43,6 +43,7 @@
 import android.hardware.display.DisplayManager;
 import android.media.AudioManager;
 import android.media.MediaRouter2Manager;
+import android.media.session.MediaSessionManager;
 import android.net.ConnectivityManager;
 import android.net.NetworkScoreManager;
 import android.net.wifi.WifiManager;
@@ -71,8 +72,6 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.shared.system.PackageManagerWrapper;
 
-import javax.inject.Singleton;
-
 import dagger.Module;
 import dagger.Provides;
 
@@ -82,49 +81,49 @@
 @Module
 public class SystemServicesModule {
     @Provides
-    @Singleton
+    @SysUISingleton
     static AccessibilityManager provideAccessibilityManager(Context context) {
         return context.getSystemService(AccessibilityManager.class);
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static ActivityManager provideActivityManager(Context context) {
         return context.getSystemService(ActivityManager.class);
     }
 
-    @Singleton
+    @SysUISingleton
     @Provides
     static AlarmManager provideAlarmManager(Context context) {
         return context.getSystemService(AlarmManager.class);
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static AudioManager provideAudioManager(Context context) {
         return context.getSystemService(AudioManager.class);
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static ColorDisplayManager provideColorDisplayManager(Context context) {
         return context.getSystemService(ColorDisplayManager.class);
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static ConnectivityManager provideConnectivityManagager(Context context) {
         return context.getSystemService(ConnectivityManager.class);
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static ContentResolver provideContentResolver(Context context) {
         return context.getContentResolver();
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static DevicePolicyManager provideDevicePolicyManager(Context context) {
         return context.getSystemService(DevicePolicyManager.class);
     }
@@ -136,44 +135,44 @@
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static DisplayManager provideDisplayManager(Context context) {
         return context.getSystemService(DisplayManager.class);
     }
 
-    @Singleton
+    @SysUISingleton
     @Provides
     static IActivityManager provideIActivityManager() {
         return ActivityManager.getService();
     }
 
-    @Singleton
+    @SysUISingleton
     @Provides
     static IActivityTaskManager provideIActivityTaskManager() {
         return ActivityTaskManager.getService();
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static IBatteryStats provideIBatteryStats() {
         return IBatteryStats.Stub.asInterface(
                 ServiceManager.getService(BatteryStats.SERVICE_NAME));
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static IDreamManager provideIDreamManager() {
         return IDreamManager.Stub.asInterface(
                 ServiceManager.checkService(DreamService.DREAM_SERVICE));
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static IPackageManager provideIPackageManager() {
         return IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
     }
 
-    @Singleton
+    @SysUISingleton
     @Provides
     static IStatusBarService provideIStatusBarService() {
         return IStatusBarService.Stub.asInterface(
@@ -187,32 +186,32 @@
                 ServiceManager.getService(Context.WALLPAPER_SERVICE));
     }
 
-    @Singleton
+    @SysUISingleton
     @Provides
     static IWindowManager provideIWindowManager() {
         return WindowManagerGlobal.getWindowManagerService();
     }
 
-    @Singleton
+    @SysUISingleton
     @Provides
     static KeyguardManager provideKeyguardManager(Context context) {
         return context.getSystemService(KeyguardManager.class);
     }
 
-    @Singleton
+    @SysUISingleton
     @Provides
     static LatencyTracker provideLatencyTracker(Context context) {
         return LatencyTracker.getInstance(context);
     }
 
-    @Singleton
+    @SysUISingleton
     @Provides
     static LauncherApps provideLauncherApps(Context context) {
         return context.getSystemService(LauncherApps.class);
     }
 
     @SuppressLint("MissingPermission")
-    @Singleton
+    @SysUISingleton
     @Provides
     @Nullable
     static LocalBluetoothManager provideLocalBluetoothController(Context context,
@@ -226,31 +225,36 @@
     }
 
     @Provides
-    @Singleton
+    static MediaSessionManager provideMediaSessionManager(Context context) {
+        return context.getSystemService(MediaSessionManager.class);
+    }
+
+    @Provides
+    @SysUISingleton
     static NetworkScoreManager provideNetworkScoreManager(Context context) {
         return context.getSystemService(NetworkScoreManager.class);
     }
 
-    @Singleton
+    @SysUISingleton
     @Provides
     static NotificationManager provideNotificationManager(Context context) {
         return context.getSystemService(NotificationManager.class);
     }
 
-    @Singleton
+    @SysUISingleton
     @Provides
     static PackageManager providePackageManager(Context context) {
         return context.getPackageManager();
     }
 
-    @Singleton
+    @SysUISingleton
     @Provides
     static PackageManagerWrapper providePackageManagerWrapper() {
         return PackageManagerWrapper.getInstance();
     }
 
     /** */
-    @Singleton
+    @SysUISingleton
     @Provides
     static PowerManager providePowerManager(Context context) {
         return context.getSystemService(PowerManager.class);
@@ -263,51 +267,51 @@
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static SensorManager providesSensorManager(Context context) {
         return context.getSystemService(SensorManager.class);
     }
 
-    @Singleton
+    @SysUISingleton
     @Provides
     static SensorPrivacyManager provideSensorPrivacyManager(Context context) {
         return context.getSystemService(SensorPrivacyManager.class);
     }
 
-    @Singleton
+    @SysUISingleton
     @Provides
     static ShortcutManager provideShortcutManager(Context context) {
         return context.getSystemService(ShortcutManager.class);
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     @Nullable
     static TelecomManager provideTelecomManager(Context context) {
         return context.getSystemService(TelecomManager.class);
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static TelephonyManager provideTelephonyManager(Context context) {
         return context.getSystemService(TelephonyManager.class);
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static TrustManager provideTrustManager(Context context) {
         return context.getSystemService(TrustManager.class);
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     @Nullable
     static Vibrator provideVibrator(Context context) {
         return context.getSystemService(Vibrator.class);
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static UserManager provideUserManager(Context context) {
         return context.getSystemService(UserManager.class);
     }
@@ -318,20 +322,20 @@
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     @Nullable
     static WifiManager provideWifiManager(Context context) {
         return context.getSystemService(WifiManager.class);
     }
 
-    @Singleton
+    @SysUISingleton
     @Provides
     static WindowManager provideWindowManager(Context context) {
         return context.getSystemService(WindowManager.class);
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static RoleManager provideRoleManager(Context context) {
         return context.getSystemService(RoleManager.class);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
index 803e56d..3b225d5 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
@@ -29,6 +29,7 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dock.DockManagerImpl;
 import com.android.systemui.doze.DozeHost;
@@ -44,12 +45,14 @@
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.phone.DozeServiceHost;
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowControllerImpl;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.ShadeControllerImpl;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -59,10 +62,9 @@
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedControllerImpl;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.wmshell.WindowManagerShellModule;
+import com.android.systemui.wmshell.WMShellModule;
 
 import javax.inject.Named;
-import javax.inject.Singleton;
 
 import dagger.Binds;
 import dagger.Module;
@@ -72,10 +74,14 @@
  * A dagger module for injecting default implementations of components of System UI that may be
  * overridden by the System UI implementation.
  */
-@Module(includes = {DividerModule.class, QSModule.class, WindowManagerShellModule.class})
+@Module(includes = {
+            DividerModule.class,
+            QSModule.class,
+            WMShellModule.class
+        })
 public abstract class SystemUIDefaultModule {
 
-    @Singleton
+    @SysUISingleton
     @Provides
     @Named(LEAK_REPORT_EMAIL_NAME)
     @Nullable
@@ -91,19 +97,29 @@
             NotificationLockscreenUserManagerImpl notificationLockscreenUserManager);
 
     @Provides
-    @Singleton
-    static BatteryController provideBatteryController(Context context,
-            EnhancedEstimates enhancedEstimates, PowerManager powerManager,
-            BroadcastDispatcher broadcastDispatcher, @Main Handler mainHandler,
+    @SysUISingleton
+    static BatteryController provideBatteryController(
+            Context context,
+            EnhancedEstimates enhancedEstimates,
+            PowerManager powerManager,
+            BroadcastDispatcher broadcastDispatcher,
+            DemoModeController demoModeController,
+            @Main Handler mainHandler,
             @Background Handler bgHandler) {
-        BatteryController bC = new BatteryControllerImpl(context, enhancedEstimates, powerManager,
-                broadcastDispatcher, mainHandler, bgHandler);
+        BatteryController bC = new BatteryControllerImpl(
+                context,
+                enhancedEstimates,
+                powerManager,
+                broadcastDispatcher,
+                demoModeController,
+                mainHandler,
+                bgHandler);
         bC.init();
         return bC;
     }
 
     @Binds
-    @Singleton
+    @SysUISingleton
     public abstract QSFactory bindQSFactory(QSFactoryImpl qsFactoryImpl);
 
     @Binds
@@ -116,14 +132,14 @@
     @Binds
     abstract ShadeController provideShadeController(ShadeControllerImpl shadeController);
 
-    @Singleton
+    @SysUISingleton
     @Provides
     @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME)
     static boolean provideAllowNotificationLongPress() {
         return true;
     }
 
-    @Singleton
+    @SysUISingleton
     @Provides
     static HeadsUpManagerPhone provideHeadsUpManagerPhone(
             Context context,
@@ -139,7 +155,7 @@
     abstract HeadsUpManager bindHeadsUpManagerPhone(HeadsUpManagerPhone headsUpManagerPhone);
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static Recents provideRecents(Context context, RecentsImplementation recentsImplementation,
             CommandQueue commandQueue) {
         return new Recents(context, recentsImplementation, commandQueue);
@@ -154,5 +170,9 @@
             StatusBarKeyguardViewManager statusBarKeyguardViewManager);
 
     @Binds
+    abstract NotificationShadeWindowController bindNotificationShadeController(
+            NotificationShadeWindowControllerImpl notificationShadeWindowController);
+
+    @Binds
     abstract DozeHost provideDozeHost(DozeServiceHost dozeServiceHost);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index f774358..abfc2ab 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -24,8 +24,10 @@
 import com.android.systemui.BootCompleteCache;
 import com.android.systemui.BootCompleteCacheImpl;
 import com.android.systemui.assist.AssistModule;
+import com.android.systemui.demomode.dagger.DemoModeModule;
 import com.android.systemui.doze.dagger.DozeComponent;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.log.dagger.LogModule;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -50,8 +52,6 @@
 import com.android.systemui.util.time.SystemClock;
 import com.android.systemui.util.time.SystemClockImpl;
 
-import javax.inject.Singleton;
-
 import dagger.Binds;
 import dagger.BindsOptionalOf;
 import dagger.Module;
@@ -64,6 +64,7 @@
 @Module(includes = {
             AssistModule.class,
             ConcurrencyModule.class,
+            DemoModeModule.class,
             LogModule.class,
             PeopleHubModule.class,
             SensorModule.class,
@@ -74,7 +75,8 @@
                 NotificationRowComponent.class,
                 DozeComponent.class,
                 ExpandableNotificationRowComponent.class,
-                NotificationShelfComponent.class})
+                NotificationShelfComponent.class,
+                FragmentService.FragmentCreator.class})
 public abstract class SystemUIModule {
 
     @Binds
@@ -85,7 +87,7 @@
     public abstract ContextComponentHelper bindComponentHelper(
             ContextComponentResolver componentHelper);
 
-    @Singleton
+    @SysUISingleton
     @Provides
     @Nullable
     static KeyguardLiftController provideKeyguardLiftController(
@@ -106,7 +108,7 @@
     public abstract NotificationRowBinder bindNotificationRowBinder(
             NotificationRowBinderImpl notificationRowBinder);
 
-    @Singleton
+    @SysUISingleton
     @Provides
     static SysUiState provideSysUiState() {
         return new SysUiState();
@@ -127,7 +129,7 @@
     @BindsOptionalOf
     abstract StatusBar optionalStatusBar();
 
-    @Singleton
+    @SysUISingleton
     @Binds
     abstract SystemClock bindSystemClock(SystemClockImpl systemClock);
 }
diff --git a/media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
similarity index 60%
copy from media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl
copy to packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index b1f99e6..929b61a 100644
--- a/media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -14,17 +14,22 @@
  * limitations under the License.
  */
 
-package android.media;
+package com.android.systemui.dagger;
 
-import android.media.AudioDeviceAttributes;
+import dagger.Subcomponent;
 
 /**
- * AIDL for AudioService to signal audio strategy-preferred device updates.
- *
- * {@hide}
+ * Dagger Subcomponent for WindowManager.
  */
-oneway interface IStrategyPreferredDeviceDispatcher {
+@WMSingleton
+@Subcomponent(modules = {})
+public interface WMComponent {
 
-    void dispatchPrefDeviceChanged(int strategyId, in AudioDeviceAttributes device);
-
+    /**
+     * Builder for a WMComponent.
+     */
+    @Subcomponent.Builder
+    interface Builder {
+        WMComponent build();
+    }
 }
diff --git a/tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java b/packages/SystemUI/src/com/android/systemui/dagger/WMModule.java
similarity index 75%
copy from tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java
copy to packages/SystemUI/src/com/android/systemui/dagger/WMModule.java
index 09ef472..2894780 100644
--- a/tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMModule.java
@@ -13,3 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
+package com.android.systemui.dagger;
+
+import dagger.Module;
+
+/**
+ * Dagger module for including the WMComponent.
+ */
+@Module(subcomponents = {WMComponent.class})
+public abstract class WMModule {
+}
diff --git a/media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl b/packages/SystemUI/src/com/android/systemui/dagger/WMSingleton.java
similarity index 64%
copy from media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl
copy to packages/SystemUI/src/com/android/systemui/dagger/WMSingleton.java
index b1f99e6..7292b9e 100644
--- a/media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMSingleton.java
@@ -14,17 +14,20 @@
  * limitations under the License.
  */
 
-package android.media;
+package com.android.systemui.dagger;
 
-import android.media.AudioDeviceAttributes;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Scope;
 
 /**
- * AIDL for AudioService to signal audio strategy-preferred device updates.
- *
- * {@hide}
+ * Scope annotation for singleton items within the WMComponent.
  */
-oneway interface IStrategyPreferredDeviceDispatcher {
-
-    void dispatchPrefDeviceChanged(int strategyId, in AudioDeviceAttributes device);
-
+@Documented
+@Retention(RUNTIME)
+@Scope
+public @interface WMSingleton {
 }
diff --git a/packages/SystemUI/src/com/android/systemui/demomode/DemoMode.java b/packages/SystemUI/src/com/android/systemui/demomode/DemoMode.java
new file mode 100644
index 0000000..672ade2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/demomode/DemoMode.java
@@ -0,0 +1,70 @@
+/*
+ * 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.demomode;
+
+import com.google.android.collect.Lists;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Interface defining what it means to implement DemoMode. A DemoMode implementation should
+ * register with DemoModeController, providing a list of commands for wish to listen.
+ *
+ * If you only need to listen to commands, but *do not* care about demo mode state changes, you
+ * can implement DemoModeCommandReceiver instead
+ */
+public interface DemoMode extends DemoModeCommandReceiver {
+
+    List<String> NO_COMMANDS = new ArrayList<>();
+
+    /** Provide a set of commands to listen to, only acceptable values are the COMMAND_* keys */
+    default List<String> demoCommands() {
+        return NO_COMMANDS;
+    }
+
+    String ACTION_DEMO = "com.android.systemui.demo";
+
+    String EXTRA_COMMAND = "command";
+
+    /** Enter and exit are non-register-able; override started/finished to observe these states */
+    String COMMAND_ENTER = "enter";
+    String COMMAND_EXIT = "exit";
+
+    /** Observable commands to register a listener for  */
+    String COMMAND_CLOCK = "clock";
+    String COMMAND_BATTERY = "battery";
+    String COMMAND_NETWORK = "network";
+    String COMMAND_BARS = "bars";
+    String COMMAND_STATUS = "status";
+    String COMMAND_NOTIFICATIONS = "notifications";
+    String COMMAND_VOLUME = "volume";
+    String COMMAND_OPERATOR = "operator";
+
+    /** New keys need to be added here */
+    List<String> COMMANDS = Lists.newArrayList(
+            COMMAND_BARS,
+            COMMAND_BATTERY,
+            COMMAND_CLOCK,
+            COMMAND_NETWORK,
+            COMMAND_NOTIFICATIONS,
+            COMMAND_OPERATOR,
+            COMMAND_STATUS,
+            COMMAND_VOLUME
+    );
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/demomode/DemoModeAvailabilityTracker.kt b/packages/SystemUI/src/com/android/systemui/demomode/DemoModeAvailabilityTracker.kt
new file mode 100644
index 0000000..16f4150
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/demomode/DemoModeAvailabilityTracker.kt
@@ -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.demomode
+
+import android.content.Context
+import android.database.ContentObserver
+import android.os.Handler
+import android.os.Looper
+import android.provider.Settings
+
+/**
+ * Class to track the availability of [DemoMode]. Use this class to track the availability and
+ * on/off state for DemoMode
+ *
+ * This class works by wrapping a content observer for the relevant keys related to DemoMode
+ * availability and current on/off state, and triggering callbacks.
+ */
+abstract class DemoModeAvailabilityTracker(val context: Context) {
+    var isInDemoMode = false
+    var isDemoModeAvailable = false
+
+    init {
+        isInDemoMode = checkIsDemoModeOn()
+        isDemoModeAvailable = checkIsDemoModeAllowed()
+    }
+
+    fun startTracking() {
+        val resolver = context.contentResolver
+        resolver.registerContentObserver(
+                Settings.Global.getUriFor(DEMO_MODE_ALLOWED), false, allowedObserver)
+        resolver.registerContentObserver(
+                Settings.Global.getUriFor(DEMO_MODE_ON), false, onObserver)
+    }
+
+    fun stopTracking() {
+        val resolver = context.contentResolver
+        resolver.unregisterContentObserver(allowedObserver)
+        resolver.unregisterContentObserver(onObserver)
+    }
+
+    abstract fun onDemoModeAvailabilityChanged()
+    abstract fun onDemoModeStarted()
+    abstract fun onDemoModeFinished()
+
+    private fun checkIsDemoModeAllowed(): Boolean {
+        return Settings.Global
+                .getInt(context.contentResolver, DEMO_MODE_ALLOWED, 0) != 0
+    }
+
+    private fun checkIsDemoModeOn(): Boolean {
+        return Settings.Global.getInt(context.contentResolver, DEMO_MODE_ON, 0) != 0
+    }
+
+    private val allowedObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {
+        override fun onChange(selfChange: Boolean) {
+            val allowed = checkIsDemoModeAllowed()
+            if (DEBUG) {
+                android.util.Log.d(TAG, "onChange: DEMO_MODE_ALLOWED changed: $allowed")
+            }
+
+            if (isDemoModeAvailable == allowed) {
+                return
+            }
+
+            isDemoModeAvailable = allowed
+            onDemoModeAvailabilityChanged()
+        }
+    }
+
+    private val onObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {
+        override fun onChange(selfChange: Boolean) {
+            val on = checkIsDemoModeOn()
+
+            if (DEBUG) {
+                android.util.Log.d(TAG, "onChange: DEMO_MODE_ON changed: $on")
+            }
+
+            if (isInDemoMode == on) {
+                return
+            }
+
+            isInDemoMode = on
+            if (on) {
+                onDemoModeStarted()
+            } else {
+                onDemoModeFinished()
+            }
+        }
+    }
+}
+
+private const val TAG = "DemoModeAvailabilityTracker"
+private const val DEMO_MODE_ALLOWED = "sysui_demo_allowed"
+private const val DEMO_MODE_ON = "sysui_tuner_demo_on"
+private const val DEBUG = false
diff --git a/packages/SystemUI/src/com/android/systemui/demomode/DemoModeCommandReceiver.java b/packages/SystemUI/src/com/android/systemui/demomode/DemoModeCommandReceiver.java
new file mode 100644
index 0000000..609536a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/demomode/DemoModeCommandReceiver.java
@@ -0,0 +1,35 @@
+/*
+ * 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.demomode;
+
+import android.os.Bundle;
+
+/** Defines the basic DemoMode command interface  */
+public interface DemoModeCommandReceiver {
+    /** Override point to observe demo mode commands  */
+    void dispatchDemoCommand(String command, Bundle args);
+
+    /**
+     * Demo mode starts due to receiving [COMMAND_ENTER] or receiving any other demo mode command
+     * while [DEMO_MODE_ALLOWED] is true but demo mode is off
+     */
+    default void onDemoModeStarted() {}
+
+    /** Demo mode exited  */
+    default void onDemoModeFinished() {}
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt b/packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt
new file mode 100644
index 0000000..c76f556
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt
@@ -0,0 +1,249 @@
+/*
+ * 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.demomode
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.Bundle
+import android.os.UserHandle
+import android.util.Log
+import com.android.systemui.Dumpable
+import com.android.systemui.demomode.DemoMode.ACTION_DEMO
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.policy.CallbackController
+import com.android.systemui.util.Assert
+import com.android.systemui.util.settings.GlobalSettings
+import java.io.FileDescriptor
+import java.io.PrintWriter
+
+/**
+ * Handles system broadcasts for [DemoMode]
+ *
+ * Injected via [DemoModeModule]
+ */
+class DemoModeController constructor(
+    private val context: Context,
+    private val dumpManager: DumpManager,
+    private val globalSettings: GlobalSettings
+) : CallbackController<DemoMode>, Dumpable {
+
+    // Var updated when the availability tracker changes, or when we enter/exit demo mode in-process
+    var isInDemoMode = false
+
+    var isAvailable = false
+        get() = tracker.isDemoModeAvailable
+
+    private var initialized = false
+
+    private val receivers = mutableListOf<DemoMode>()
+    private val receiverMap: Map<String, MutableList<DemoMode>>
+
+    init {
+        val m = mutableMapOf<String, MutableList<DemoMode>>()
+        DemoMode.COMMANDS.map { command ->
+            m.put(command, mutableListOf())
+        }
+        receiverMap = m
+    }
+
+    fun initialize() {
+        if (initialized) {
+            throw IllegalStateException("Already initialized")
+        }
+
+        initialized = true
+
+        dumpManager.registerDumpable(TAG, this)
+
+        // Due to DemoModeFragment running in systemui:tuner process, we have to observe for
+        // content changes to know if the setting turned on or off
+        tracker.startTracking()
+
+        // TODO: We should probably exit demo mode if we booted up with it on
+        isInDemoMode = tracker.isInDemoMode
+
+        val demoFilter = IntentFilter()
+        demoFilter.addAction(ACTION_DEMO)
+        context.registerReceiverAsUser(broadcastReceiver, UserHandle.ALL, demoFilter,
+                android.Manifest.permission.DUMP, null)
+    }
+
+    override fun addCallback(listener: DemoMode) {
+        Assert.isMainThread()
+
+        // Register this listener for its commands
+        val commands = listener.demoCommands()
+
+        commands.forEach { command ->
+            if (!receiverMap.containsKey(command)) {
+                throw IllegalStateException("Command ($command) not recognized. " +
+                        "See DemoMode.java for valid commands")
+            }
+
+            receiverMap[command]!!.add(listener)
+        }
+
+        receivers.add(listener)
+        if (isInDemoMode) {
+            listener.onDemoModeStarted()
+        }
+    }
+
+    override fun removeCallback(listener: DemoMode) {
+        Assert.isMainThread()
+
+        listener.demoCommands().forEach { command ->
+            receiverMap[command]!!.remove(listener)
+        }
+
+        receivers.remove(listener)
+    }
+
+    private fun setIsDemoModeAllowed(enabled: Boolean) {
+        // Turn off demo mode if it was on
+        if (isInDemoMode && !enabled) {
+            requestFinishDemoMode()
+        }
+    }
+
+    private fun enterDemoMode() {
+        isInDemoMode = true
+        Assert.isMainThread()
+        receivers.forEach { r ->
+            r.onDemoModeStarted()
+        }
+    }
+
+    private fun exitDemoMode() {
+        isInDemoMode = false
+        Assert.isMainThread()
+        receivers.forEach { r ->
+            r.onDemoModeFinished()
+        }
+    }
+
+    fun dispatchDemoCommand(command: String, args: Bundle) {
+        Assert.isMainThread()
+
+        if (DEBUG) {
+            Log.d(TAG, "dispatchDemoCommand: $command, args=$args")
+        }
+
+        if (!isAvailable) {
+            return
+        }
+
+        if (command == DemoMode.COMMAND_ENTER) {
+            enterDemoMode()
+        } else if (command == DemoMode.COMMAND_EXIT) {
+            exitDemoMode()
+        } else if (!isInDemoMode) {
+            enterDemoMode()
+        }
+
+        // See? demo mode is easy now, you just notify the listeners when their command is called
+        receiverMap[command]!!.forEach { receiver ->
+            receiver.dispatchDemoCommand(command, args)
+        }
+    }
+
+    override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
+        pw.println("DemoModeController state -")
+        pw.println("  isInDemoMode=$isInDemoMode")
+        pw.println("  isDemoModeAllowed=$isAvailable")
+        pw.print("  receivers=[")
+        receivers.forEach { recv ->
+            pw.print(" ${recv.javaClass.simpleName}")
+        }
+        pw.println(" ]")
+        pw.println("  receiverMap= [")
+        receiverMap.keys.forEach { command ->
+            pw.print("    $command : [")
+            val recvs = receiverMap[command]!!.map { receiver ->
+                receiver.javaClass.simpleName
+            }.joinToString(",")
+            pw.println("$recvs ]")
+        }
+    }
+
+    private val tracker = object : DemoModeAvailabilityTracker(context) {
+        override fun onDemoModeAvailabilityChanged() {
+            setIsDemoModeAllowed(isDemoModeAvailable)
+        }
+
+        override fun onDemoModeStarted() {
+            if (this@DemoModeController.isInDemoMode != isInDemoMode) {
+                enterDemoMode()
+            }
+        }
+
+        override fun onDemoModeFinished() {
+            if (this@DemoModeController.isInDemoMode != isInDemoMode) {
+                exitDemoMode()
+            }
+        }
+    }
+
+    private val broadcastReceiver = object : BroadcastReceiver() {
+        override fun onReceive(context: Context, intent: Intent) {
+            if (DEBUG) {
+                Log.v(TAG, "onReceive: $intent")
+            }
+
+            val action = intent.action
+            if (!ACTION_DEMO.equals(action)) {
+                return
+            }
+
+            val bundle = intent.extras ?: return
+            val command = bundle.getString("command", "").trim().toLowerCase()
+            if (command.length == 0) {
+                return
+            }
+
+            try {
+                dispatchDemoCommand(command, bundle)
+            } catch (t: Throwable) {
+                Log.w(TAG, "Error running demo command, intent=$intent $t")
+            }
+        }
+    }
+
+    fun requestSetDemoModeAllowed(allowed: Boolean) {
+        setGlobal(DEMO_MODE_ALLOWED, if (allowed) 1 else 0)
+    }
+
+    fun requestStartDemoMode() {
+        setGlobal(DEMO_MODE_ON, 1)
+    }
+
+    fun requestFinishDemoMode() {
+        setGlobal(DEMO_MODE_ON, 0)
+    }
+
+    private fun setGlobal(key: String, value: Int) {
+        globalSettings.putInt(key, value)
+    }
+}
+
+private const val TAG = "DemoModeController"
+private const val DEMO_MODE_ALLOWED = "sysui_demo_allowed"
+private const val DEMO_MODE_ON = "sysui_tuner_demo_on"
+
+private const val DEBUG = false
diff --git a/packages/SystemUI/src/com/android/systemui/demomode/dagger/DemoModeModule.java b/packages/SystemUI/src/com/android/systemui/demomode/dagger/DemoModeModule.java
new file mode 100644
index 0000000..de9affb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/demomode/dagger/DemoModeModule.java
@@ -0,0 +1,45 @@
+/*
+ * 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.demomode.dagger;
+
+import android.content.Context;
+
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.demomode.DemoModeController;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.util.settings.GlobalSettings;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Dagger module providing {@link DemoModeController}
+ */
+@Module
+public abstract class DemoModeModule {
+    /** Provides DemoModeController */
+    @Provides
+    @SysUISingleton
+    static DemoModeController provideDemoModeController(
+            Context context,
+            DumpManager dumpManager,
+            GlobalSettings globalSettings) {
+        DemoModeController dmc = new DemoModeController(context, dumpManager, globalSettings);
+        dmc.initialize();
+        return /*run*/dmc;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dock/DockManagerImpl.java b/packages/SystemUI/src/com/android/systemui/dock/DockManagerImpl.java
index efc3905..8f39975 100644
--- a/packages/SystemUI/src/com/android/systemui/dock/DockManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/dock/DockManagerImpl.java
@@ -16,10 +16,11 @@
 
 package com.android.systemui.dock;
 
-import javax.inject.Inject;
-import javax.inject.Singleton;
+import com.android.systemui.dagger.SysUISingleton;
 
-@Singleton
+import javax.inject.Inject;
+
+@SysUISingleton
 public class DockManagerImpl implements DockManager {
 
     @Inject
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index a311a45..99d2651 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -24,6 +24,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
 
 import java.io.FileDescriptor;
@@ -32,14 +33,13 @@
 import java.lang.annotation.RetentionPolicy;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Logs doze events for debugging and triaging purposes. Logs are dumped in bugreports or on demand:
  *      adb shell dumpsys activity service com.android.systemui/.SystemUIService \
  *      dependency DumpController DozeLog,DozeStats
  */
-@Singleton
+@SysUISingleton
 public class DozeLog implements Dumpable {
     private final DozeLogger mLogger;
 
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
index 19b0ea1..1a1cc07 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
@@ -33,6 +33,9 @@
 
 import javax.inject.Inject;
 
+import dagger.Reusable;
+
+@Reusable  // Don't create multiple DozeServices.
 public class DozeService extends DreamService
         implements DozeMachine.Service, RequestDoze, PluginListener<DozeServicePlugin> {
     private static final String TAG = "DozeService";
@@ -57,7 +60,7 @@
         setWindowless(true);
 
         mPluginManager.addPluginListener(this, DozeServicePlugin.class, false /* allowMultiple */);
-        DozeComponent dozeComponent = mDozeComponentBuilder.build(this);
+        DozeComponent dozeComponent = mDozeComponentBuilder.build();
         mDozeMachine = dozeComponent.getDozeMachine();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeComponent.java b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeComponent.java
index e925927..2472854 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeComponent.java
@@ -19,7 +19,6 @@
 import com.android.systemui.doze.DozeMachine;
 import com.android.systemui.doze.DozeService;
 
-import dagger.BindsInstance;
 import dagger.Subcomponent;
 
 /**
@@ -31,7 +30,7 @@
     /** Simple Builder for {@link DozeComponent}. */
     @Subcomponent.Factory
     interface Builder {
-        DozeComponent build(@BindsInstance  DozeService dozeService);
+        DozeComponent build();
     }
 
     /** Supply a {@link DozeMachine}. */
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
index bbb7750..bfa4780 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
@@ -18,11 +18,11 @@
 
 import android.util.ArrayMap
 import com.android.systemui.Dumpable
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
 import java.io.FileDescriptor
 import java.io.PrintWriter
 import javax.inject.Inject
-import javax.inject.Singleton
 
 /**
  * Maintains a registry of things that should be dumped when a bug report is taken
@@ -33,7 +33,7 @@
  *
  * See [DumpHandler] for more information on how and when this information is dumped.
  */
-@Singleton
+@SysUISingleton
 class DumpManager @Inject constructor() {
     private val dumpables: MutableMap<String, RegisteredDumpable<Dumpable>> = ArrayMap()
     private val buffers: MutableMap<String, RegisteredDumpable<LogBuffer>> = ArrayMap()
diff --git a/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt b/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt
index 603cb67..0eab1af 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt
@@ -18,6 +18,7 @@
 
 import android.content.Context
 import android.util.Log
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.util.io.Files
 import com.android.systemui.util.time.SystemClock
@@ -33,7 +34,6 @@
 import java.util.Locale
 import java.util.concurrent.TimeUnit
 import javax.inject.Inject
-import javax.inject.Singleton
 
 /**
  * Dumps all [LogBuffer]s to a file
@@ -41,7 +41,7 @@
  * Intended for emergencies, i.e. we're about to crash. This file can then be read at a later date
  * (usually in a bug report).
  */
-@Singleton
+@SysUISingleton
 class LogBufferEulogizer(
     private val dumpManager: DumpManager,
     private val systemClock: SystemClock,
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
index ae20076..0673879 100644
--- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
+++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
@@ -21,9 +21,8 @@
 import android.view.View;
 
 import com.android.systemui.Dumpable;
-import com.android.systemui.dagger.SystemUIRootComponent;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.qs.QSFragment;
-import com.android.systemui.statusbar.phone.NavigationBarFragment;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import java.io.FileDescriptor;
@@ -32,7 +31,6 @@
 import java.lang.reflect.Modifier;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 import dagger.Subcomponent;
 
@@ -40,7 +38,7 @@
  * Holds a map of root views to FragmentHostStates and generates them as needed.
  * Also dispatches the configuration changes to all current FragmentHostStates.
  */
-@Singleton
+@SysUISingleton
 public class FragmentService implements Dumpable {
 
     private static final String TAG = "FragmentService";
@@ -61,9 +59,9 @@
             };
 
     @Inject
-    public FragmentService(SystemUIRootComponent rootComponent,
+    public FragmentService(FragmentCreator.Factory fragmentCreatorFactory,
             ConfigurationController configurationController) {
-        mFragmentCreator = rootComponent.createFragmentCreator();
+        mFragmentCreator = fragmentCreatorFactory.build();
         initInjectionMap();
         configurationController.addCallback(mConfigurationListener);
     }
@@ -121,10 +119,11 @@
      */
     @Subcomponent
     public interface FragmentCreator {
-        /**
-         * Inject a NavigationBarFragment.
-         */
-        NavigationBarFragment createNavigationBarFragment();
+        /** Factory for creating a FragmentCreator. */
+        @Subcomponent.Factory
+        interface Factory {
+            FragmentCreator build();
+        }
         /**
          * Inject a QSFragment.
          */
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
index b29c5b0..86c8565 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
@@ -20,6 +20,7 @@
 
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.SystemUI;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.GlobalActions;
 import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
 import com.android.systemui.statusbar.CommandQueue;
@@ -30,12 +31,11 @@
 
 import javax.inject.Inject;
 import javax.inject.Provider;
-import javax.inject.Singleton;
 
 /**
  * Manages power menu plugins and communicates power menu actions to the StatusBar.
  */
-@Singleton
+@SysUISingleton
 public class GlobalActionsComponent extends SystemUI implements Callbacks, GlobalActionsManager {
 
     private final CommandQueue mCommandQueue;
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index ef51abb..d213ac1 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -132,7 +132,7 @@
 import com.android.systemui.plugins.GlobalActionsPanelPlugin;
 import com.android.systemui.settings.CurrentUserContextTracker;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.EmergencyDialerConstants;
@@ -148,6 +148,7 @@
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
+import javax.inject.Provider;
 
 /**
  * Helper to show the global actions dialog.  Each item is an {@link Action} that may show depending
@@ -401,7 +402,7 @@
                         if (mDialog != null) {
                             if (!mDialog.isShowingControls() && shouldShowControls()) {
                                 mDialog.showControls(mControlsUiControllerOptional.get());
-                            } else if (shouldShowLockMessage()) {
+                            } else if (shouldShowLockMessage(mDialog)) {
                                 mDialog.showLockMessage();
                             }
                         }
@@ -698,19 +699,17 @@
         mPowerAdapter = new MyPowerOptionsAdapter();
 
         mDepthController.setShowingHomeControls(true);
-        GlobalActionsPanelPlugin.PanelViewController walletViewController =
-                getWalletViewController();
         ControlsUiController uiController = null;
         if (mControlsUiControllerOptional.isPresent() && shouldShowControls()) {
             uiController = mControlsUiControllerOptional.get();
         }
         ActionsDialog dialog = new ActionsDialog(mContext, mAdapter, mOverflowAdapter,
-                walletViewController, mDepthController, mSysuiColorExtractor,
+                this::getWalletViewController, mDepthController, mSysuiColorExtractor,
                 mStatusBarService, mNotificationShadeWindowController,
                 controlsAvailable(), uiController,
                 mSysUiState, this::onRotate, mKeyguardShowing, mPowerAdapter);
 
-        if (shouldShowLockMessage()) {
+        if (shouldShowLockMessage(dialog)) {
             dialog.showLockMessage();
         }
         dialog.setCanceledOnTouchOutside(false); // Handled by the custom class.
@@ -2124,7 +2123,8 @@
         private MultiListLayout mGlobalActionsLayout;
         private Drawable mBackgroundDrawable;
         private final SysuiColorExtractor mColorExtractor;
-        private final GlobalActionsPanelPlugin.PanelViewController mWalletViewController;
+        private final Provider<GlobalActionsPanelPlugin.PanelViewController> mWalletFactory;
+        @Nullable private GlobalActionsPanelPlugin.PanelViewController mWalletViewController;
         private boolean mKeyguardShowing;
         private boolean mShowing;
         private float mScrimAlpha;
@@ -2144,7 +2144,7 @@
         private TextView mLockMessage;
 
         ActionsDialog(Context context, MyAdapter adapter, MyOverflowAdapter overflowAdapter,
-                GlobalActionsPanelPlugin.PanelViewController walletViewController,
+                Provider<GlobalActionsPanelPlugin.PanelViewController> walletFactory,
                 NotificationShadeDepthController depthController,
                 SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService,
                 NotificationShadeWindowController notificationShadeWindowController,
@@ -2165,6 +2165,7 @@
             mSysUiState = sysuiState;
             mOnRotateCallback = onRotateCallback;
             mKeyguardShowing = keyguardShowing;
+            mWalletFactory = walletFactory;
 
             // Window initialization
             Window window = getWindow();
@@ -2187,7 +2188,6 @@
             window.getAttributes().setFitInsetsTypes(0 /* types */);
             setTitle(R.string.global_actions);
 
-            mWalletViewController = walletViewController;
             initializeLayout();
         }
 
@@ -2200,8 +2200,13 @@
             mControlsUiController.show(mControlsView, this::dismissForControlsActivity);
         }
 
+        private boolean isWalletViewAvailable() {
+            return mWalletViewController != null && mWalletViewController.getPanelContent() != null;
+        }
+
         private void initializeWalletView() {
-            if (mWalletViewController == null || mWalletViewController.getPanelContent() == null) {
+            mWalletViewController = mWalletFactory.get();
+            if (!isWalletViewAvailable()) {
                 return;
             }
 
@@ -2507,6 +2512,8 @@
         private void dismissWallet() {
             if (mWalletViewController != null) {
                 mWalletViewController.onDismissed();
+                // The wallet controller should not be re-used after being dismissed.
+                mWalletViewController = null;
             }
         }
 
@@ -2648,18 +2655,12 @@
                 && !mControlsServiceInfos.isEmpty();
     }
 
-    private boolean walletViewAvailable() {
-        GlobalActionsPanelPlugin.PanelViewController walletViewController =
-                getWalletViewController();
-        return walletViewController != null && walletViewController.getPanelContent() != null;
-    }
-
-    private boolean shouldShowLockMessage() {
+    private boolean shouldShowLockMessage(ActionsDialog dialog) {
         boolean isLockedAfterBoot = mLockPatternUtils.getStrongAuthForUser(getCurrentUser().id)
                 == STRONG_AUTH_REQUIRED_AFTER_BOOT;
         return !mKeyguardStateController.isUnlocked()
                 && (!mShowLockScreenCardsAndControls || isLockedAfterBoot)
-                && (controlsAvailable() || walletViewAvailable());
+                && (controlsAvailable() || dialog.isWalletViewAvailable());
     }
 
     private void onPowerMenuLockScreenSettingsChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/DismissCallbackRegistry.java b/packages/SystemUI/src/com/android/systemui/keyguard/DismissCallbackRegistry.java
index f6f3b99..d1bbc33 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/DismissCallbackRegistry.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/DismissCallbackRegistry.java
@@ -17,18 +17,18 @@
 package com.android.systemui.keyguard;
 
 import com.android.internal.policy.IKeyguardDismissCallback;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 
 import java.util.ArrayList;
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Registry holding the current set of {@link IKeyguardDismissCallback}s.
  */
-@Singleton
+@SysUISingleton
 public class DismissCallbackRegistry {
 
     private final ArrayList<DismissCallbackWrapper> mDismissCallbacks = new ArrayList<>();
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java
index 4a33590..f527775 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java
@@ -19,13 +19,14 @@
 import android.os.Handler;
 import android.os.Message;
 
+import com.android.systemui.dagger.SysUISingleton;
+
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Dispatches the lifecycles keyguard gets from WindowManager on the main thread.
  */
-@Singleton
+@SysUISingleton
 public class KeyguardLifecyclesDispatcher {
 
     static final int SCREEN_TURNING_ON = 0;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index 3a37c0f..daef2c5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -52,7 +52,6 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.SystemUIAppComponentFactory;
-import com.android.systemui.SystemUIFactory;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.StatusBarState;
@@ -72,6 +71,9 @@
 
 /**
  * Simple Slice provider that shows the current date.
+ *
+ * Injection is handled by {@link SystemUIAppComponentFactory} +
+ * {@link com.android.systemui.dagger.GlobalRootComponent#inject(KeyguardSliceProvider)}.
  */
 public class KeyguardSliceProvider extends SliceProvider implements
         NextAlarmController.NextAlarmChangeCallback, ZenModeController.Callback,
@@ -298,7 +300,8 @@
     @Override
     public boolean onCreateSliceProvider() {
         mContextAvailableCallback.onContextAvailable(getContext());
-        inject();
+        mMediaWakeLock = new SettableWakeLock(WakeLock.createPartial(getContext(), "media"),
+                "media");
         synchronized (KeyguardSliceProvider.sInstanceLock) {
             KeyguardSliceProvider oldInstance = KeyguardSliceProvider.sInstance;
             if (oldInstance != null) {
@@ -319,13 +322,6 @@
     }
 
     @VisibleForTesting
-    protected void inject() {
-        SystemUIFactory.getInstance().getRootComponent().inject(this);
-        mMediaWakeLock = new SettableWakeLock(WakeLock.createPartial(getContext(), "media"),
-                "media");
-    }
-
-    @VisibleForTesting
     protected void onDestroy() {
         synchronized (KeyguardSliceProvider.sInstanceLock) {
             mNextAlarmController.removeCallback(this);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 75f4809..6214a64 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -85,7 +85,6 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
-import com.android.systemui.SystemUIFactory;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.dump.DumpManager;
@@ -94,7 +93,7 @@
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.phone.BiometricUnlockController;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.NavigationModeController;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.statusbar.phone.NotificationPanelViewController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.util.DeviceConfigProxy;
@@ -229,6 +228,7 @@
 
     /** TrustManager for letting it know when we change visibility */
     private final TrustManager mTrustManager;
+    private final InjectionInflationController mInjectionInflationController;
 
     /**
      * Used to keep the device awake while to ensure the keyguard finishes opening before
@@ -723,7 +723,8 @@
             @UiBackground Executor uiBgExecutor, PowerManager powerManager,
             TrustManager trustManager,
             DeviceConfigProxy deviceConfig,
-            NavigationModeController navigationModeController) {
+            NavigationModeController navigationModeController,
+            InjectionInflationController injectionInflationController) {
         super(context);
         mFalsingManager = falsingManager;
         mLockPatternUtils = lockPatternUtils;
@@ -734,6 +735,7 @@
         mUpdateMonitor = keyguardUpdateMonitor;
         mPM = powerManager;
         mTrustManager = trustManager;
+        mInjectionInflationController = injectionInflationController;
         dumpManager.registerDumpable(getClass().getName(), this);
         mDeviceConfig = deviceConfig;
         mShowHomeOverLockscreen = mDeviceConfig.getBoolean(
@@ -773,10 +775,8 @@
         mContext.registerReceiver(mDelayedLockBroadcastReceiver, delayedActionFilter,
                 SYSTEMUI_PERMISSION, null /* scheduler */);
 
-        InjectionInflationController injectionInflationController =
-                new InjectionInflationController(SystemUIFactory.getInstance().getRootComponent());
         mKeyguardDisplayManager = new KeyguardDisplayManager(mContext,
-                injectionInflationController);
+                mInjectionInflationController);
 
         mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java b/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java
index 834bca5..084e84a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java
@@ -19,17 +19,17 @@
 import android.os.Trace;
 
 import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.SysUISingleton;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Tracks the screen lifecycle.
  */
-@Singleton
+@SysUISingleton
 public class ScreenLifecycle extends Lifecycle<ScreenLifecycle.Observer> implements Dumpable {
 
     public static final int SCREEN_OFF = 0;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java b/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java
index d17f2f6..5161deb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java
@@ -20,6 +20,7 @@
 import android.os.Trace;
 
 import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.SysUISingleton;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -27,12 +28,11 @@
 import java.lang.annotation.RetentionPolicy;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Tracks the wakefulness lifecycle.
  */
-@Singleton
+@SysUISingleton
 public class WakefulnessLifecycle extends Lifecycle<WakefulnessLifecycle.Observer> implements
         Dumpable {
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 83c95b7..7c5dcd8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -24,19 +24,19 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardViewController;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.statusbar.phone.NavigationModeController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.util.DeviceConfigProxy;
+import com.android.systemui.util.InjectionInflationController;
 
 import java.util.concurrent.Executor;
 
-import javax.inject.Singleton;
-
 import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
@@ -50,7 +50,7 @@
      * Provides our instance of KeyguardViewMediator which is considered optional.
      */
     @Provides
-    @Singleton
+    @SysUISingleton
     public static KeyguardViewMediator newKeyguardViewMediator(
             Context context,
             FalsingManager falsingManager,
@@ -64,7 +64,8 @@
             TrustManager trustManager,
             @UiBackground Executor uiBgExecutor,
             DeviceConfigProxy deviceConfig,
-            NavigationModeController navigationModeController) {
+            NavigationModeController navigationModeController,
+            InjectionInflationController injectionInflationController) {
         return new KeyguardViewMediator(
                 context,
                 falsingManager,
@@ -78,6 +79,7 @@
                 powerManager,
                 trustManager,
                 deviceConfig,
-                navigationModeController);
+                navigationModeController,
+                injectionInflationController);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 16f76de..6db4086 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -20,6 +20,7 @@
 import android.os.Build;
 import android.os.Looper;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.log.LogBuffer;
@@ -27,8 +28,6 @@
 import com.android.systemui.log.LogcatEchoTrackerDebug;
 import com.android.systemui.log.LogcatEchoTrackerProd;
 
-import javax.inject.Singleton;
-
 import dagger.Module;
 import dagger.Provides;
 
@@ -39,7 +38,7 @@
 public class LogModule {
     /** Provides a logging buffer for doze-related logs. */
     @Provides
-    @Singleton
+    @SysUISingleton
     @DozeLog
     public static LogBuffer provideDozeLogBuffer(
             LogcatEchoTracker bufferFilter,
@@ -51,7 +50,7 @@
 
     /** Provides a logging buffer for all logs related to the data layer of notifications. */
     @Provides
-    @Singleton
+    @SysUISingleton
     @NotificationLog
     public static LogBuffer provideNotificationsLogBuffer(
             LogcatEchoTracker bufferFilter,
@@ -63,7 +62,7 @@
 
     /** Provides a logging buffer for all logs related to managing notification sections. */
     @Provides
-    @Singleton
+    @SysUISingleton
     @NotificationSectionLog
     public static LogBuffer provideNotificationSectionLogBuffer(
             LogcatEchoTracker bufferFilter,
@@ -75,7 +74,7 @@
 
     /** Provides a logging buffer for all logs related to the data layer of notifications. */
     @Provides
-    @Singleton
+    @SysUISingleton
     @NotifInteractionLog
     public static LogBuffer provideNotifInteractionLogBuffer(
             LogcatEchoTracker echoTracker,
@@ -87,7 +86,7 @@
 
     /** Provides a logging buffer for all logs related to Quick Settings. */
     @Provides
-    @Singleton
+    @SysUISingleton
     @QSLog
     public static LogBuffer provideQuickSettingsLogBuffer(
             LogcatEchoTracker bufferFilter,
@@ -99,7 +98,7 @@
 
     /** Provides a logging buffer for {@link com.android.systemui.broadcast.BroadcastDispatcher} */
     @Provides
-    @Singleton
+    @SysUISingleton
     @BroadcastDispatcherLog
     public static LogBuffer provideBroadcastDispatcherLogBuffer(
             LogcatEchoTracker bufferFilter,
@@ -111,7 +110,7 @@
 
     /** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */
     @Provides
-    @Singleton
+    @SysUISingleton
     public static LogcatEchoTracker provideLogcatEchoTracker(
             ContentResolver contentResolver,
             @Main Looper looper) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
index f9c6307..094ece2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.media
 
 import android.view.View
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.statusbar.StatusBarState
@@ -24,13 +25,12 @@
 import com.android.systemui.statusbar.notification.stack.MediaHeaderView
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import javax.inject.Inject
-import javax.inject.Singleton
 
 /**
  * A class that controls the media notifications on the lock screen, handles its visibility and
  * is responsible for the embedding of he media experience.
  */
-@Singleton
+@SysUISingleton
 class KeyguardMediaController @Inject constructor(
     private val mediaHost: MediaHost,
     private val bypassController: KeyguardBypassController,
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index 7f610d1..a003d83 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -10,20 +10,22 @@
 import android.view.View
 import android.view.ViewGroup
 import android.widget.LinearLayout
+import androidx.annotation.VisibleForTesting
 import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.qs.PageIndicator
-import com.android.systemui.statusbar.notification.VisualStabilityManager
+import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.Utils
 import com.android.systemui.util.animation.UniqueObjectHostView
 import com.android.systemui.util.animation.requiresRemeasuring
 import com.android.systemui.util.concurrency.DelayableExecutor
+import java.util.TreeMap
 import javax.inject.Inject
 import javax.inject.Provider
-import javax.inject.Singleton
 
 private const val TAG = "MediaCarouselController"
 private val settingsIntent = Intent().setAction(ACTION_MEDIA_CONTROLS_SETTINGS)
@@ -32,7 +34,7 @@
  * Class that is responsible for keeping the view carousel up to date.
  * This also handles changes in state and applies them to the media carousel like the expansion.
  */
-@Singleton
+@SysUISingleton
 class MediaCarouselController @Inject constructor(
     private val context: Context,
     private val mediaControlPanelFactory: Provider<MediaControlPanel>,
@@ -40,7 +42,7 @@
     private val mediaHostStatesManager: MediaHostStatesManager,
     private val activityStarter: ActivityStarter,
     @Main executor: DelayableExecutor,
-    mediaManager: MediaDataFilter,
+    mediaManager: MediaDataManager,
     configurationController: ConfigurationController,
     falsingManager: FalsingManager
 ) {
@@ -102,9 +104,7 @@
     private val mediaCarousel: MediaScrollView
     private val mediaCarouselScrollHandler: MediaCarouselScrollHandler
     val mediaFrame: ViewGroup
-    val mediaPlayers: MutableMap<String, MediaControlPanel> = mutableMapOf()
     private lateinit var settingsButton: View
-    private val mediaData: MutableMap<String, MediaData> = mutableMapOf()
     private val mediaContent: ViewGroup
     private val pageIndicator: PageIndicator
     private val visualStabilityCallback: VisualStabilityManager.Callback
@@ -122,7 +122,7 @@
         set(value) {
             if (field != value) {
                 field = value
-                for (player in mediaPlayers.values) {
+                for (player in MediaPlayerData.players()) {
                     player.setListening(field)
                 }
             }
@@ -155,6 +155,7 @@
         inflateSettingsButton()
         mediaContent = mediaCarousel.requireViewById(R.id.media_carousel)
         configurationController.addCallback(configListener)
+        // TODO (b/162832756): remove visual stability manager when migrating to new pipeline
         visualStabilityCallback = VisualStabilityManager.Callback {
             if (needsReordering) {
                 needsReordering = false
@@ -167,20 +168,18 @@
                 true /* persistent */)
         mediaManager.addListener(object : MediaDataManager.Listener {
             override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
-                oldKey?.let { mediaData.remove(it) }
                 if (!data.active && !Utils.useMediaResumption(context)) {
                     // This view is inactive, let's remove this! This happens e.g when dismissing /
                     // timing out a view. We still have the data around because resumption could
                     // be on, but we should save the resources and release this.
+                    oldKey?.let { MediaPlayerData.removeMediaPlayer(it) }
                     onMediaDataRemoved(key)
                 } else {
-                    mediaData.put(key, data)
                     addOrUpdatePlayer(key, oldKey, data)
                 }
             }
 
             override fun onMediaDataRemoved(key: String) {
-                mediaData.remove(key)
                 removePlayer(key)
             }
         })
@@ -223,53 +222,36 @@
     }
 
     private fun reorderAllPlayers() {
-        for (mediaPlayer in mediaPlayers.values) {
-            val view = mediaPlayer.view?.player
-            if (mediaPlayer.isPlaying && mediaContent.indexOfChild(view) != 0) {
-                mediaContent.removeView(view)
-                mediaContent.addView(view, 0)
+        mediaContent.removeAllViews()
+        for (mediaPlayer in MediaPlayerData.players()) {
+            mediaPlayer.view?.let {
+                mediaContent.addView(it.player)
             }
         }
         mediaCarouselScrollHandler.onPlayersChanged()
     }
 
     private fun addOrUpdatePlayer(key: String, oldKey: String?, data: MediaData) {
-        // If the key was changed, update entry
-        val oldData = mediaPlayers[oldKey]
-        if (oldData != null) {
-            val oldData = mediaPlayers.remove(oldKey)
-            mediaPlayers.put(key, oldData!!)?.let {
-                Log.wtf(TAG, "new key $key already exists when migrating from $oldKey")
-            }
-        }
-        var existingPlayer = mediaPlayers[key]
+        val existingPlayer = MediaPlayerData.getMediaPlayer(key, oldKey)
         if (existingPlayer == null) {
-            existingPlayer = mediaControlPanelFactory.get()
-            existingPlayer.attach(PlayerViewHolder.create(LayoutInflater.from(context),
-                    mediaContent))
-            existingPlayer.mediaViewController.sizeChangedListener = this::updateCarouselDimensions
-            mediaPlayers[key] = existingPlayer
+            var newPlayer = mediaControlPanelFactory.get()
+            newPlayer.attach(PlayerViewHolder.create(LayoutInflater.from(context), mediaContent))
+            newPlayer.mediaViewController.sizeChangedListener = this::updateCarouselDimensions
+            MediaPlayerData.addMediaPlayer(key, data, newPlayer)
             val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                     ViewGroup.LayoutParams.WRAP_CONTENT)
-            existingPlayer.view?.player?.setLayoutParams(lp)
-            existingPlayer.bind(data)
-            existingPlayer.setListening(currentlyExpanded)
-            updatePlayerToState(existingPlayer, noAnimation = true)
-            if (existingPlayer.isPlaying) {
-                mediaContent.addView(existingPlayer.view?.player, 0)
-            } else {
-                mediaContent.addView(existingPlayer.view?.player)
-            }
+            newPlayer.view?.player?.setLayoutParams(lp)
+            newPlayer.bind(data)
+            newPlayer.setListening(currentlyExpanded)
+            updatePlayerToState(newPlayer, noAnimation = true)
+            reorderAllPlayers()
         } else {
             existingPlayer.bind(data)
-            if (existingPlayer.isPlaying &&
-                    mediaContent.indexOfChild(existingPlayer.view?.player) != 0) {
-                if (visualStabilityManager.isReorderingAllowed) {
-                    mediaContent.removeView(existingPlayer.view?.player)
-                    mediaContent.addView(existingPlayer.view?.player, 0)
-                } else {
-                    needsReordering = true
-                }
+            MediaPlayerData.addMediaPlayer(key, data, existingPlayer)
+            if (visualStabilityManager.isReorderingAllowed) {
+                reorderAllPlayers()
+            } else {
+                needsReordering = true
             }
         }
         updatePageIndicator()
@@ -277,13 +259,13 @@
         mediaCarousel.requiresRemeasuring = true
         // Check postcondition: mediaContent should have the same number of children as there are
         // elements in mediaPlayers.
-        if (mediaPlayers.size != mediaContent.childCount) {
+        if (MediaPlayerData.players().size != mediaContent.childCount) {
             Log.wtf(TAG, "Size of players list and number of views in carousel are out of sync")
         }
     }
 
     private fun removePlayer(key: String) {
-        val removed = mediaPlayers.remove(key)
+        val removed = MediaPlayerData.removeMediaPlayer(key)
         removed?.apply {
             mediaCarouselScrollHandler.onPrePlayerRemoved(removed)
             mediaContent.removeView(removed.view?.player)
@@ -294,12 +276,7 @@
     }
 
     private fun recreatePlayers() {
-        // Note that this will scramble the order of players. Actively playing sessions will, at
-        // least, still be put in the front. If we want to maintain order, then more work is
-        // needed.
-        mediaData.forEach {
-            key, data ->
-            removePlayer(key)
+        MediaPlayerData.mediaData().forEach { (key, data) ->
             addOrUpdatePlayer(key = key, oldKey = null, data = data)
         }
     }
@@ -337,7 +314,7 @@
             currentStartLocation = startLocation
             currentEndLocation = endLocation
             currentTransitionProgress = progress
-            for (mediaPlayer in mediaPlayers.values) {
+            for (mediaPlayer in MediaPlayerData.players()) {
                 updatePlayerToState(mediaPlayer, immediately)
             }
             maybeResetSettingsCog()
@@ -386,7 +363,7 @@
     private fun updateCarouselDimensions() {
         var width = 0
         var height = 0
-        for (mediaPlayer in mediaPlayers.values) {
+        for (mediaPlayer in MediaPlayerData.players()) {
             val controller = mediaPlayer.mediaViewController
             // When transitioning the view to gone, the view gets smaller, but the translation
             // Doesn't, let's add the translation
@@ -448,7 +425,7 @@
             this.desiredLocation = desiredLocation
             this.desiredHostState = it
             currentlyExpanded = it.expansion > 0
-            for (mediaPlayer in mediaPlayers.values) {
+            for (mediaPlayer in MediaPlayerData.players()) {
                 if (animate) {
                     mediaPlayer.mediaViewController.animatePendingStateChange(
                             duration = duration,
@@ -470,7 +447,7 @@
     }
 
     fun closeGuts() {
-        mediaPlayers.values.forEach {
+        MediaPlayerData.players().forEach {
             it.closeGuts(true)
         }
     }
@@ -497,3 +474,50 @@
         }
     }
 }
+
+@VisibleForTesting
+internal object MediaPlayerData {
+    private data class MediaSortKey(
+        val data: MediaData,
+        val updateTime: Long = 0,
+        val isPlaying: Boolean = false
+    )
+
+    private val comparator =
+        compareByDescending<MediaSortKey> { it.isPlaying }
+        .thenByDescending { it.data.isLocalSession }
+        .thenByDescending { !it.data.resumption }
+        .thenByDescending { it.updateTime }
+
+    private val mediaPlayers = TreeMap<MediaSortKey, MediaControlPanel>(comparator)
+    private val mediaData: MutableMap<String, MediaSortKey> = mutableMapOf()
+
+    fun addMediaPlayer(key: String, data: MediaData, player: MediaControlPanel) {
+        removeMediaPlayer(key)
+        val sortKey = MediaSortKey(data, System.currentTimeMillis(), player.isPlaying())
+        mediaData.put(key, sortKey)
+        mediaPlayers.put(sortKey, player)
+    }
+
+    fun getMediaPlayer(key: String, oldKey: String?): MediaControlPanel? {
+        // If the key was changed, update entry
+        oldKey?.let {
+            if (it != key) {
+                mediaData.remove(it)?.let { sortKey -> mediaData.put(key, sortKey) }
+            }
+        }
+        return mediaData.get(key)?.let { mediaPlayers.get(it) }
+    }
+
+    fun removeMediaPlayer(key: String) = mediaData.remove(key)?.let { mediaPlayers.remove(it) }
+
+    fun mediaData() = mediaData.entries.map { e -> Pair(e.key, e.value.data) }
+
+    fun players() = mediaPlayers.values
+
+    @VisibleForTesting
+    fun clear() {
+        mediaData.clear()
+        mediaPlayers.clear()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
index dafc52a..d6a0268 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
@@ -82,6 +82,10 @@
      */
     var resumeAction: Runnable?,
     /**
+     * Local or remote playback
+     */
+    var isLocalSession: Boolean = true,
+    /**
      * Indicates that this player is a resumption player (ie. It only shows a play actions which
      * will start the app and start playing).
      */
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt
index d0642cc..aa3699e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt
@@ -17,65 +17,48 @@
 package com.android.systemui.media
 
 import javax.inject.Inject
-import javax.inject.Singleton
 
 /**
- * Combines updates from [MediaDataManager] with [MediaDeviceManager].
+ * Combines [MediaDataManager.Listener] events with [MediaDeviceManager.Listener] events.
  */
-@Singleton
-class MediaDataCombineLatest @Inject constructor(
-    private val dataSource: MediaDataManager,
-    private val deviceSource: MediaDeviceManager
-) {
+class MediaDataCombineLatest @Inject constructor() : MediaDataManager.Listener,
+        MediaDeviceManager.Listener {
+
     private val listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf()
     private val entries: MutableMap<String, Pair<MediaData?, MediaDeviceData?>> = mutableMapOf()
 
-    init {
-        dataSource.addListener(object : MediaDataManager.Listener {
-            override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
-                if (oldKey != null && oldKey != key && entries.contains(oldKey)) {
-                    entries[key] = data to entries.remove(oldKey)?.second
-                    update(key, oldKey)
-                } else {
-                    entries[key] = data to entries[key]?.second
-                    update(key, key)
-                }
-            }
-            override fun onMediaDataRemoved(key: String) {
-                remove(key)
-            }
-        })
-        deviceSource.addListener(object : MediaDeviceManager.Listener {
-            override fun onMediaDeviceChanged(
-                key: String,
-                oldKey: String?,
-                data: MediaDeviceData?
-            ) {
-                if (oldKey != null && oldKey != key && entries.contains(oldKey)) {
-                    entries[key] = entries.remove(oldKey)?.first to data
-                    update(key, oldKey)
-                } else {
-                    entries[key] = entries[key]?.first to data
-                    update(key, key)
-                }
-            }
-            override fun onKeyRemoved(key: String) {
-                remove(key)
-            }
-        })
+    override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
+        if (oldKey != null && oldKey != key && entries.contains(oldKey)) {
+            entries[key] = data to entries.remove(oldKey)?.second
+            update(key, oldKey)
+        } else {
+            entries[key] = data to entries[key]?.second
+            update(key, key)
+        }
     }
 
-    /**
-     * Get a map of all non-null data entries
-     */
-    fun getData(): Map<String, MediaData> {
-        return entries.filter {
-            (key, pair) -> pair.first != null && pair.second != null
-        }.mapValues {
-            (key, pair) -> pair.first!!.copy(device = pair.second)
+    override fun onMediaDataRemoved(key: String) {
+        remove(key)
+    }
+
+    override fun onMediaDeviceChanged(
+        key: String,
+        oldKey: String?,
+        data: MediaDeviceData?
+    ) {
+        if (oldKey != null && oldKey != key && entries.contains(oldKey)) {
+            entries[key] = entries.remove(oldKey)?.first to data
+            update(key, oldKey)
+        } else {
+            entries[key] = entries[key]?.first to data
+            update(key, key)
         }
     }
 
+    override fun onKeyRemoved(key: String) {
+        remove(key)
+    }
+
     /**
      * Add a listener for [MediaData] changes that has been combined with latest [MediaDeviceData].
      */
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
index 24ca970..0664a41 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
@@ -24,7 +24,6 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import java.util.concurrent.Executor
 import javax.inject.Inject
-import javax.inject.Singleton
 
 private const val TAG = "MediaDataFilter"
 private const val DEBUG = true
@@ -33,24 +32,24 @@
  * Filters data updates from [MediaDataCombineLatest] based on the current user ID, and handles user
  * switches (removing entries for the previous user, adding back entries for the current user)
  *
- * This is added downstream of [MediaDataManager] since we may still need to handle callbacks from
- * background users (e.g. timeouts) that UI classes should ignore.
- * Instead, UI classes should listen to this so they can stay in sync with the current user.
+ * This is added at the end of the pipeline since we may still need to handle callbacks from
+ * background users (e.g. timeouts).
  */
-@Singleton
 class MediaDataFilter @Inject constructor(
-    private val dataSource: MediaDataCombineLatest,
     private val broadcastDispatcher: BroadcastDispatcher,
     private val mediaResumeListener: MediaResumeListener,
-    private val mediaDataManager: MediaDataManager,
     private val lockscreenUserManager: NotificationLockscreenUserManager,
     @Main private val executor: Executor
 ) : MediaDataManager.Listener {
     private val userTracker: CurrentUserTracker
-    private val listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf()
+    private val _listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf()
+    internal val listeners: Set<MediaDataManager.Listener>
+        get() = _listeners.toSet()
+    internal lateinit var mediaDataManager: MediaDataManager
 
-    // The filtered mediaEntries, which will be a subset of all mediaEntries in MediaDataManager
-    private val mediaEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()
+    private val allEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()
+    // The filtered userEntries, which will be a subset of all userEntries in MediaDataManager
+    private val userEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()
 
     init {
         userTracker = object : CurrentUserTracker(broadcastDispatcher) {
@@ -60,31 +59,34 @@
             }
         }
         userTracker.startTracking()
-        dataSource.addListener(this)
     }
 
     override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
+        if (oldKey != null && oldKey != key) {
+            allEntries.remove(oldKey)
+        }
+        allEntries.put(key, data)
+
         if (!lockscreenUserManager.isCurrentProfile(data.userId)) {
             return
         }
 
-        if (oldKey != null) {
-            mediaEntries.remove(oldKey)
+        if (oldKey != null && oldKey != key) {
+            userEntries.remove(oldKey)
         }
-        mediaEntries.put(key, data)
+        userEntries.put(key, data)
 
         // Notify listeners
-        val listenersCopy = listeners.toSet()
-        listenersCopy.forEach {
+        listeners.forEach {
             it.onMediaDataLoaded(key, oldKey, data)
         }
     }
 
     override fun onMediaDataRemoved(key: String) {
-        mediaEntries.remove(key)?.let {
+        allEntries.remove(key)
+        userEntries.remove(key)?.let {
             // Only notify listeners if something actually changed
-            val listenersCopy = listeners.toSet()
-            listenersCopy.forEach {
+            listeners.forEach {
                 it.onMediaDataRemoved(key)
             }
         }
@@ -93,11 +95,11 @@
     @VisibleForTesting
     internal fun handleUserSwitched(id: Int) {
         // If the user changes, remove all current MediaData objects and inform listeners
-        val listenersCopy = listeners.toSet()
-        val keyCopy = mediaEntries.keys.toMutableList()
+        val listenersCopy = listeners
+        val keyCopy = userEntries.keys.toMutableList()
         // Clear the list first, to make sure callbacks from listeners if we have any entries
         // are up to date
-        mediaEntries.clear()
+        userEntries.clear()
         keyCopy.forEach {
             if (DEBUG) Log.d(TAG, "Removing $it after user change")
             listenersCopy.forEach { listener ->
@@ -105,10 +107,10 @@
             }
         }
 
-        dataSource.getData().forEach { (key, data) ->
+        allEntries.forEach { (key, data) ->
             if (lockscreenUserManager.isCurrentProfile(data.userId)) {
                 if (DEBUG) Log.d(TAG, "Re-adding $key after user change")
-                mediaEntries.put(key, data)
+                userEntries.put(key, data)
                 listenersCopy.forEach { listener ->
                     listener.onMediaDataLoaded(key, null, data)
                 }
@@ -121,7 +123,7 @@
      */
     fun onSwipeToDismiss() {
         if (DEBUG) Log.d(TAG, "Media carousel swiped away")
-        val mediaKeys = mediaEntries.keys.toSet()
+        val mediaKeys = userEntries.keys.toSet()
         mediaKeys.forEach {
             mediaDataManager.setTimedOut(it, timedOut = true)
         }
@@ -130,7 +132,7 @@
     /**
      * Are there any media notifications active?
      */
-    fun hasActiveMedia() = mediaEntries.any { it.value.active }
+    fun hasActiveMedia() = userEntries.any { it.value.active }
 
     /**
      * Are there any media entries we should display?
@@ -138,7 +140,7 @@
      * If resumption is disabled, we only want to show active players
      */
     fun hasAnyMedia() = if (mediaResumeListener.isResumptionEnabled()) {
-        mediaEntries.isNotEmpty()
+        userEntries.isNotEmpty()
     } else {
         hasActiveMedia()
     }
@@ -146,10 +148,10 @@
     /**
      * Add a listener for filtered [MediaData] changes
      */
-    fun addListener(listener: MediaDataManager.Listener) = listeners.add(listener)
+    fun addListener(listener: MediaDataManager.Listener) = _listeners.add(listener)
 
     /**
      * Remove a listener that was registered with addListener
      */
-    fun removeListener(listener: MediaDataManager.Listener) = listeners.remove(listener)
-}
\ No newline at end of file
+    fun removeListener(listener: MediaDataManager.Listener) = _listeners.remove(listener)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index e77e499..686531a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -31,6 +31,7 @@
 import android.graphics.drawable.Icon
 import android.media.MediaDescription
 import android.media.MediaMetadata
+import android.media.session.MediaController
 import android.media.session.MediaSession
 import android.net.Uri
 import android.os.UserHandle
@@ -41,6 +42,7 @@
 import com.android.systemui.Dumpable
 import com.android.systemui.R
 import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
@@ -55,7 +57,6 @@
 import java.io.PrintWriter
 import java.util.concurrent.Executor
 import javax.inject.Inject
-import javax.inject.Singleton
 
 // URI fields to try loading album art from
 private val ART_URIS = arrayOf(
@@ -88,7 +89,7 @@
 /**
  * A class that facilitates management and loading of Media Data, ready for binding.
  */
-@Singleton
+@SysUISingleton
 class MediaDataManager(
     private val context: Context,
     @Background private val backgroundExecutor: Executor,
@@ -98,13 +99,35 @@
     dumpManager: DumpManager,
     mediaTimeoutListener: MediaTimeoutListener,
     mediaResumeListener: MediaResumeListener,
+    mediaSessionBasedFilter: MediaSessionBasedFilter,
+    mediaDeviceManager: MediaDeviceManager,
+    mediaDataCombineLatest: MediaDataCombineLatest,
+    private val mediaDataFilter: MediaDataFilter,
     private val activityStarter: ActivityStarter,
     private var useMediaResumption: Boolean,
     private val useQsMediaPlayer: Boolean
 ) : Dumpable {
 
-    private val listeners: MutableSet<Listener> = mutableSetOf()
+    // Internal listeners are part of the internal pipeline. External listeners (those registered
+    // with [MediaDeviceManager.addListener]) receive events after they have propagated through
+    // the internal pipeline.
+    // Another way to think of the distinction between internal and external listeners is the
+    // following. Internal listeners are listeners that MediaDataManager depends on, and external
+    // listeners are listeners that depend on MediaDataManager.
+    // TODO(b/159539991#comment5): Move internal listeners to separate package.
+    private val internalListeners: MutableSet<Listener> = mutableSetOf()
     private val mediaEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()
+    internal var appsBlockedFromResume: MutableSet<String> = Utils.getBlockedMediaApps(context)
+        set(value) {
+            // Update list
+            appsBlockedFromResume.clear()
+            appsBlockedFromResume.addAll(value)
+
+            // Remove any existing resume players that are now blocked
+            appsBlockedFromResume.forEach {
+                removeAllForPackage(it)
+            }
+        }
 
     @Inject
     constructor(
@@ -116,9 +139,14 @@
         broadcastDispatcher: BroadcastDispatcher,
         mediaTimeoutListener: MediaTimeoutListener,
         mediaResumeListener: MediaResumeListener,
+        mediaSessionBasedFilter: MediaSessionBasedFilter,
+        mediaDeviceManager: MediaDeviceManager,
+        mediaDataCombineLatest: MediaDataCombineLatest,
+        mediaDataFilter: MediaDataFilter,
         activityStarter: ActivityStarter
     ) : this(context, backgroundExecutor, foregroundExecutor, mediaControllerFactory,
             broadcastDispatcher, dumpManager, mediaTimeoutListener, mediaResumeListener,
+            mediaSessionBasedFilter, mediaDeviceManager, mediaDataCombineLatest, mediaDataFilter,
             activityStarter, Utils.useMediaResumption(context), Utils.useQsMediaPlayer(context))
 
     private val appChangeReceiver = object : BroadcastReceiver() {
@@ -141,12 +169,26 @@
 
     init {
         dumpManager.registerDumpable(TAG, this)
+
+        // Initialize the internal processing pipeline. The listeners at the front of the pipeline
+        // are set as internal listeners so that they receive events. From there, events are
+        // propagated through the pipeline. The end of the pipeline is currently mediaDataFilter,
+        // so it is responsible for dispatching events to external listeners. To achieve this,
+        // external listeners that are registered with [MediaDataManager.addListener] are actually
+        // registered as listeners to mediaDataFilter.
+        addInternalListener(mediaTimeoutListener)
+        addInternalListener(mediaResumeListener)
+        addInternalListener(mediaSessionBasedFilter)
+        mediaSessionBasedFilter.addListener(mediaDeviceManager)
+        mediaSessionBasedFilter.addListener(mediaDataCombineLatest)
+        mediaDeviceManager.addListener(mediaDataCombineLatest)
+        mediaDataCombineLatest.addListener(mediaDataFilter)
+
+        // Set up links back into the pipeline for listeners that need to send events upstream.
         mediaTimeoutListener.timeoutCallback = { token: String, timedOut: Boolean ->
             setTimedOut(token, timedOut) }
-        addListener(mediaTimeoutListener)
-
         mediaResumeListener.setManager(this)
-        addListener(mediaResumeListener)
+        mediaDataFilter.mediaDataManager = this
 
         val suspendFilter = IntentFilter(Intent.ACTION_PACKAGES_SUSPENDED)
         broadcastDispatcher.registerReceiver(appChangeReceiver, suspendFilter, null, UserHandle.ALL)
@@ -184,10 +226,9 @@
 
     private fun removeAllForPackage(packageName: String) {
         Assert.isMainThread()
-        val listenersCopy = listeners.toSet()
         val toRemove = mediaEntries.filter { it.value.packageName == packageName }
         toRemove.forEach {
-            removeEntry(it.key, listenersCopy)
+            removeEntry(it.key)
         }
     }
 
@@ -247,12 +288,45 @@
     /**
      * Add a listener for changes in this class
      */
-    fun addListener(listener: Listener) = listeners.add(listener)
+    fun addListener(listener: Listener) {
+        // mediaDataFilter is the current end of the internal pipeline. Register external
+        // listeners as listeners to it.
+        mediaDataFilter.addListener(listener)
+    }
 
     /**
      * Remove a listener for changes in this class
      */
-    fun removeListener(listener: Listener) = listeners.remove(listener)
+    fun removeListener(listener: Listener) {
+        // Since mediaDataFilter is the current end of the internal pipelie, external listeners
+        // have been registered to it. So, they need to be removed from it too.
+        mediaDataFilter.removeListener(listener)
+    }
+
+    /**
+     * Add a listener for internal events.
+     */
+    private fun addInternalListener(listener: Listener) = internalListeners.add(listener)
+
+    /**
+     * Notify internal listeners of loaded event.
+     *
+     * External listeners registered with [addListener] will be notified after the event propagates
+     * through the internal listener pipeline.
+     */
+    private fun notifyMediaDataLoaded(key: String, oldKey: String?, info: MediaData) {
+        internalListeners.forEach { it.onMediaDataLoaded(key, oldKey, info) }
+    }
+
+    /**
+     * Notify internal listeners of removed event.
+     *
+     * External listeners registered with [addListener] will be notified after the event propagates
+     * through the internal listener pipeline.
+     */
+    private fun notifyMediaDataRemoved(key: String) {
+        internalListeners.forEach { it.onMediaDataRemoved(key) }
+    }
 
     /**
      * Called whenever the player has been paused or stopped for a while, or swiped from QQS.
@@ -270,16 +344,13 @@
         }
     }
 
-    private fun removeEntry(key: String, listenersCopy: Set<Listener>) {
+    private fun removeEntry(key: String) {
         mediaEntries.remove(key)
-        listenersCopy.forEach {
-            it.onMediaDataRemoved(key)
-        }
+        notifyMediaDataRemoved(key)
     }
 
     fun dismissMediaData(key: String, delay: Long) {
-        val listenersCopy = listeners.toSet()
-        foregroundExecutor.executeDelayed({ removeEntry(key, listenersCopy) }, delay)
+        foregroundExecutor.executeDelayed({ removeEntry(key) }, delay)
     }
 
     private fun loadMediaDataInBgForResumption(
@@ -331,7 +402,8 @@
     ) {
         val token = sbn.notification.extras.getParcelable(Notification.EXTRA_MEDIA_SESSION)
                 as MediaSession.Token?
-        val metadata = mediaControllerFactory.create(token).metadata
+        val mediaController = mediaControllerFactory.create(token)
+        val metadata = mediaController.metadata
 
         // Foreground and Background colors computed from album art
         val notif: Notification = sbn.notification
@@ -407,7 +479,7 @@
                 val runnable = if (action.actionIntent != null) {
                     Runnable {
                         if (action.isAuthenticationRequired()) {
-                            activityStarter.dismissKeyguardThenExecute ({
+                            activityStarter.dismissKeyguardThenExecute({
                                 var result = sendPendingIntent(action.actionIntent)
                                 result
                             }, {}, true)
@@ -426,6 +498,9 @@
             }
         }
 
+        val isLocalSession = mediaController.playbackInfo?.playbackType ==
+            MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL ?: true
+
         foregroundExecutor.execute {
             val resumeAction: Runnable? = mediaEntries[key]?.resumeAction
             val hasCheckedForResume = mediaEntries[key]?.hasCheckedForResume == true
@@ -433,8 +508,8 @@
             onMediaDataLoaded(key, oldKey, MediaData(sbn.normalizedUserId, true, bgColor, app,
                     smallIconDrawable, artist, song, artWorkIcon, actionIcons,
                     actionsToShowCollapsed, sbn.packageName, token, notif.contentIntent, null,
-                    active, resumeAction = resumeAction, notificationKey = key,
-                    hasCheckedForResume = hasCheckedForResume))
+                    active, resumeAction = resumeAction, isLocalSession = isLocalSession,
+                    notificationKey = key, hasCheckedForResume = hasCheckedForResume))
         }
     }
 
@@ -532,18 +607,16 @@
         if (mediaEntries.containsKey(key)) {
             // Otherwise this was removed already
             mediaEntries.put(key, data)
-            val listenersCopy = listeners.toSet()
-            listenersCopy.forEach {
-                it.onMediaDataLoaded(key, oldKey, data)
-            }
+            notifyMediaDataLoaded(key, oldKey, data)
         }
     }
 
     fun onNotificationRemoved(key: String) {
         Assert.isMainThread()
         val removed = mediaEntries.remove(key)
-        if (useMediaResumption && removed?.resumeAction != null) {
-            if (DEBUG) Log.d(TAG, "Not removing $key because resumable")
+        if (useMediaResumption && removed?.resumeAction != null &&
+                !isBlockedFromResume(removed?.packageName)) {
+            Log.d(TAG, "Not removing $key because resumable")
             // Move to resume key (aka package name) if that key doesn't already exist.
             val resumeAction = getResumeMediaAction(removed.resumeAction!!)
             val updated = removed.copy(token = null, actions = listOf(resumeAction),
@@ -551,34 +624,31 @@
             val pkg = removed?.packageName
             val migrate = mediaEntries.put(pkg, updated) == null
             // Notify listeners of "new" controls when migrating or removed and update when not
-            val listenersCopy = listeners.toSet()
             if (migrate) {
-                listenersCopy.forEach {
-                    it.onMediaDataLoaded(pkg, key, updated)
-                }
+                notifyMediaDataLoaded(pkg, key, updated)
             } else {
                 // Since packageName is used for the key of the resumption controls, it is
                 // possible that another notification has already been reused for the resumption
                 // controls of this package. In this case, rather than renaming this player as
                 // packageName, just remove it and then send a update to the existing resumption
                 // controls.
-                listenersCopy.forEach {
-                    it.onMediaDataRemoved(key)
-                }
-                listenersCopy.forEach {
-                    it.onMediaDataLoaded(pkg, pkg, updated)
-                }
+                notifyMediaDataRemoved(key)
+                notifyMediaDataLoaded(pkg, pkg, updated)
             }
             return
         }
         if (removed != null) {
-            val listenersCopy = listeners.toSet()
-            listenersCopy.forEach {
-                it.onMediaDataRemoved(key)
-            }
+            notifyMediaDataRemoved(key)
         }
     }
 
+    private fun isBlockedFromResume(packageName: String?): Boolean {
+        if (packageName == null) {
+            return true
+        }
+        return appsBlockedFromResume.contains(packageName)
+    }
+
     fun setMediaResumptionEnabled(isEnabled: Boolean) {
         if (useMediaResumption == isEnabled) {
             return
@@ -588,17 +658,31 @@
 
         if (!useMediaResumption) {
             // Remove any existing resume controls
-            val listenersCopy = listeners.toSet()
             val filtered = mediaEntries.filter { !it.value.active }
             filtered.forEach {
                 mediaEntries.remove(it.key)
-                listenersCopy.forEach { listener ->
-                    listener.onMediaDataRemoved(it.key)
-                }
+                notifyMediaDataRemoved(it.key)
             }
         }
     }
 
+    /**
+     * Invoked when the user has dismissed the media carousel
+     */
+    fun onSwipeToDismiss() = mediaDataFilter.onSwipeToDismiss()
+
+    /**
+     * Are there any media notifications active?
+     */
+    fun hasActiveMedia() = mediaDataFilter.hasActiveMedia()
+
+    /**
+     * Are there any media entries we should display?
+     * If resumption is enabled, this will include inactive players
+     * If resumption is disabled, we only want to show active players
+     */
+    fun hasAnyMedia() = mediaDataFilter.hasAnyMedia()
+
     interface Listener {
 
         /**
@@ -618,9 +702,11 @@
 
     override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
         pw.apply {
-            println("listeners: $listeners")
+            println("internalListeners: $internalListeners")
+            println("externalListeners: ${mediaDataFilter.listeners}")
             println("mediaEntries: $mediaEntries")
             println("useMediaResumption: $useMediaResumption")
+            println("appsBlockedFromResume: $appsBlockedFromResume")
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
index ae7f66b..2bc908b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
@@ -24,34 +24,32 @@
 import androidx.annotation.WorkerThread
 import com.android.settingslib.media.LocalMediaManager
 import com.android.settingslib.media.MediaDevice
+import com.android.systemui.Dumpable
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.Dumpable
 import com.android.systemui.dump.DumpManager
 import java.io.FileDescriptor
 import java.io.PrintWriter
 import java.util.concurrent.Executor
 import javax.inject.Inject
-import javax.inject.Singleton
 
 /**
  * Provides information about the route (ie. device) where playback is occurring.
  */
-@Singleton
 class MediaDeviceManager @Inject constructor(
     private val context: Context,
     private val localMediaManagerFactory: LocalMediaManagerFactory,
     private val mr2manager: MediaRouter2Manager,
     @Main private val fgExecutor: Executor,
     @Background private val bgExecutor: Executor,
-    private val mediaDataManager: MediaDataManager,
-    private val dumpManager: DumpManager
+    dumpManager: DumpManager
 ) : MediaDataManager.Listener, Dumpable {
+
     private val listeners: MutableSet<Listener> = mutableSetOf()
     private val entries: MutableMap<String, Entry> = mutableMapOf()
 
     init {
-        mediaDataManager.addListener(this)
         dumpManager.registerDumpable(javaClass.name, this)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
index 70f01d5..5475a00 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
@@ -27,6 +27,7 @@
 import android.view.ViewGroup
 import android.view.ViewGroupOverlay
 import com.android.systemui.Interpolators
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
@@ -37,7 +38,6 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.animation.UniqueObjectHostView
 import javax.inject.Inject
-import javax.inject.Singleton
 
 /**
  * Similarly to isShown but also excludes views that have 0 alpha
@@ -65,7 +65,7 @@
  * This manager is responsible for placement of the unique media view between the different hosts
  * and animate the positions of the views to achieve seamless transitions.
  */
-@Singleton
+@SysUISingleton
 class MediaHierarchyManager @Inject constructor(
     private val context: Context,
     private val statusBarStateController: SysuiStatusBarStateController,
@@ -389,6 +389,14 @@
         if (isCurrentlyInGuidedTransformation()) {
             return false
         }
+        // This is an invalid transition, and can happen when using the camera gesture from the
+        // lock screen. Disallow.
+        if (previousLocation == LOCATION_LOCKSCREEN &&
+            desiredLocation == LOCATION_QQS &&
+            statusbarState == StatusBarState.SHADE) {
+            return false
+        }
+
         if (currentLocation == LOCATION_QQS &&
                 previousLocation == LOCATION_LOCKSCREEN &&
                 (statusBarStateController.leaveOpenOnKeyguardHide() ||
@@ -604,8 +612,8 @@
             // When collapsing on the lockscreen, we want to remain in QS
             return LOCATION_QS
         }
-        if (location != LOCATION_LOCKSCREEN && desiredLocation == LOCATION_LOCKSCREEN
-                && !fullyAwake) {
+        if (location != LOCATION_LOCKSCREEN && desiredLocation == LOCATION_LOCKSCREEN &&
+                !fullyAwake) {
             // When unlocking from dozing / while waking up, the media shouldn't be transitioning
             // in an animated way. Let's keep it in the lockscreen until we're fully awake and
             // reattach it without an animation
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
index 3598719..ce184aa 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
@@ -14,7 +14,7 @@
 class MediaHost @Inject constructor(
     private val state: MediaHostStateHolder,
     private val mediaHierarchyManager: MediaHierarchyManager,
-    private val mediaDataFilter: MediaDataFilter,
+    private val mediaDataManager: MediaDataManager,
     private val mediaHostStatesManager: MediaHostStatesManager
 ) : MediaHostState by state {
     lateinit var hostView: UniqueObjectHostView
@@ -79,12 +79,12 @@
                 // be a delay until the views and the controllers are initialized, leaving us
                 // with either a blank view or the controllers not yet initialized and the
                 // measuring wrong
-                mediaDataFilter.addListener(listener)
+                mediaDataManager.addListener(listener)
                 updateViewVisibility()
             }
 
             override fun onViewDetachedFromWindow(v: View?) {
-                mediaDataFilter.removeListener(listener)
+                mediaDataManager.removeListener(listener)
             }
         })
 
@@ -113,9 +113,9 @@
 
     private fun updateViewVisibility() {
         visible = if (showsOnlyActiveMedia) {
-            mediaDataFilter.hasActiveMedia()
+            mediaDataManager.hasActiveMedia()
         } else {
-            mediaDataFilter.hasAnyMedia()
+            mediaDataManager.hasAnyMedia()
         }
         val newVisibility = if (visible) View.VISIBLE else View.GONE
         if (newVisibility != hostView.visibility) {
@@ -289,4 +289,4 @@
      * Get a copy of this view state, deepcopying all appropriate members
      */
     fun copy(): MediaHostState
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHostStatesManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHostStatesManager.kt
index d3954b7..ba7c167 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHostStatesManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHostStatesManager.kt
@@ -16,16 +16,16 @@
 
 package com.android.systemui.media
 
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.util.animation.MeasurementOutput
 import javax.inject.Inject
-import javax.inject.Singleton
 
 /**
  * A class responsible for managing all media host states of the various host locations and
  * coordinating the heights among different players. This class can be used to get the most up to
  * date state for any location.
  */
-@Singleton
+@SysUISingleton
 class MediaHostStatesManager @Inject constructor() {
 
     private val callbacks: MutableSet<Callback> = mutableSetOf()
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
index 4ec746f..c00b5e9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
@@ -29,20 +29,20 @@
 import android.service.media.MediaBrowserService
 import android.util.Log
 import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.tuner.TunerService
 import com.android.systemui.util.Utils
 import java.util.concurrent.ConcurrentLinkedQueue
 import java.util.concurrent.Executor
 import javax.inject.Inject
-import javax.inject.Singleton
 
 private const val TAG = "MediaResumeListener"
 
 private const val MEDIA_PREFERENCES = "media_control_prefs"
 private const val MEDIA_PREFERENCE_KEY = "browser_components_"
 
-@Singleton
+@SysUISingleton
 class MediaResumeListener @Inject constructor(
     private val context: Context,
     private val broadcastDispatcher: BroadcastDispatcher,
@@ -52,6 +52,7 @@
 
     private var useMediaResumption: Boolean = Utils.useMediaResumption(context)
     private val resumeComponents: ConcurrentLinkedQueue<ComponentName> = ConcurrentLinkedQueue()
+    private var blockedApps: MutableSet<String> = Utils.getBlockedMediaApps(context)
 
     private lateinit var mediaDataManager: MediaDataManager
 
@@ -114,6 +115,14 @@
                 mediaDataManager.setMediaResumptionEnabled(useMediaResumption)
             }
         }, Settings.Secure.MEDIA_CONTROLS_RESUME)
+
+        // Listen to changes in which apps are allowed to persist
+        tunerService.addTunable(object : TunerService.Tunable {
+            override fun onTuningChanged(key: String?, newValue: String?) {
+                blockedApps = Utils.getBlockedMediaApps(context)
+                mediaDataManager.appsBlockedFromResume = blockedApps
+            }
+        }, Settings.Secure.MEDIA_CONTROLS_RESUME_BLOCKED)
     }
 
     fun isResumptionEnabled() = useMediaResumption
@@ -144,8 +153,10 @@
         }
 
         resumeComponents.forEach {
-            val browser = ResumeMediaBrowser(context, mediaBrowserCallback, it)
-            browser.findRecentMedia()
+            if (!blockedApps.contains(it.packageName)) {
+                val browser = ResumeMediaBrowser(context, mediaBrowserCallback, it)
+                browser.findRecentMedia()
+            }
         }
     }
 
@@ -154,7 +165,8 @@
             // If this had been started from a resume state, disconnect now that it's live
             mediaBrowser?.disconnect()
             // If we don't have a resume action, check if we haven't already
-            if (data.resumeAction == null && !data.hasCheckedForResume) {
+            if (data.resumeAction == null && !data.hasCheckedForResume &&
+                    !blockedApps.contains(data.packageName)) {
                 // TODO also check for a media button receiver intended for restarting (b/154127084)
                 Log.d(TAG, "Checking for service component for " + data.packageName)
                 val pm = context.packageManager
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaSessionBasedFilter.kt b/packages/SystemUI/src/com/android/systemui/media/MediaSessionBasedFilter.kt
new file mode 100644
index 0000000..f01713f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaSessionBasedFilter.kt
@@ -0,0 +1,163 @@
+/*
+ * 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.media
+
+import android.content.ComponentName
+import android.content.Context
+import android.media.session.MediaController
+import android.media.session.MediaController.PlaybackInfo
+import android.media.session.MediaSession
+import android.media.session.MediaSessionManager
+import android.util.Log
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+private const val TAG = "MediaSessionBasedFilter"
+
+/**
+ * Filters media loaded events for local media sessions while an app is casting.
+ *
+ * When an app is casting there can be one remote media sessions and potentially more local media
+ * sessions. In this situation, there should only be a media object for the remote session. To
+ * achieve this, update events for the local session need to be filtered.
+ */
+class MediaSessionBasedFilter @Inject constructor(
+    context: Context,
+    private val sessionManager: MediaSessionManager,
+    @Main private val foregroundExecutor: Executor,
+    @Background private val backgroundExecutor: Executor
+) : MediaDataManager.Listener {
+
+    private val listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf()
+
+    // Keep track of MediaControllers for a given package to check if an app is casting and it
+    // filter loaded events for local sessions.
+    private val packageControllers: LinkedHashMap<String, MutableList<MediaController>> =
+            LinkedHashMap()
+
+    // Keep track of the key used for the session tokens. This information is used to know when
+    // dispatch a removed event so that a media object for a local session will be removed.
+    private val keyedTokens: MutableMap<String, MutableList<MediaSession.Token>> = mutableMapOf()
+
+    private val sessionListener = object : MediaSessionManager.OnActiveSessionsChangedListener {
+        override fun onActiveSessionsChanged(controllers: List<MediaController>) {
+            handleControllersChanged(controllers)
+        }
+    }
+
+    init {
+        backgroundExecutor.execute {
+            val name = ComponentName(context, NotificationListenerWithPlugins::class.java)
+            sessionManager.addOnActiveSessionsChangedListener(sessionListener, name)
+            handleControllersChanged(sessionManager.getActiveSessions(name))
+        }
+    }
+
+    /**
+     * Add a listener for filtered [MediaData] changes
+     */
+    fun addListener(listener: MediaDataManager.Listener) = listeners.add(listener)
+
+    /**
+     * Remove a listener that was registered with addListener
+     */
+    fun removeListener(listener: MediaDataManager.Listener) = listeners.remove(listener)
+
+    /**
+     * May filter loaded events by not passing them along to listeners.
+     *
+     * If an app has only one session with playback type PLAYBACK_TYPE_REMOTE, then assuming that
+     * the app is casting. Sometimes apps will send redundant updates to a local session with
+     * playback type PLAYBACK_TYPE_LOCAL. These updates should be filtered to improve the usability
+     * of the media controls.
+     */
+    override fun onMediaDataLoaded(key: String, oldKey: String?, info: MediaData) {
+        backgroundExecutor.execute {
+            val isMigration = oldKey != null && key != oldKey
+            if (isMigration) {
+                keyedTokens.remove(oldKey)?.let { removed -> keyedTokens.put(key, removed) }
+            }
+            if (info.token != null) {
+                keyedTokens.get(key)?.let {
+                    tokens ->
+                    tokens.add(info.token)
+                } ?: run {
+                    val tokens = mutableListOf(info.token)
+                    keyedTokens.put(key, tokens)
+                }
+            }
+            // Determine if an app is casting by checking if it has a session with playback type
+            // PLAYBACK_TYPE_REMOTE.
+            val remoteControllers = packageControllers.get(info.packageName)?.filter {
+                it.playbackInfo?.playbackType == PlaybackInfo.PLAYBACK_TYPE_REMOTE
+            }
+            // Limiting search to only apps with a single remote session.
+            val remote = if (remoteControllers?.size == 1) remoteControllers.firstOrNull() else null
+            if (isMigration || remote == null || remote.sessionToken == info.token) {
+                // Not filtering in this case. Passing the event along to listeners.
+                dispatchMediaDataLoaded(key, oldKey, info)
+            } else {
+                // Filtering this event because the app is casting and the loaded events is for a
+                // local session.
+                Log.d(TAG, "filtering key=$key local=${info.token} remote=${remote?.sessionToken}")
+                // If the local session uses a different notification key, then lets go a step
+                // farther and dismiss the media data so that media controls for the local session
+                // don't hang around while casting.
+                if (!keyedTokens.get(key)!!.contains(remote.sessionToken)) {
+                    dispatchMediaDataRemoved(key)
+                }
+            }
+        }
+    }
+
+    override fun onMediaDataRemoved(key: String) {
+        // Queue on background thread to ensure ordering of loaded and removed events is maintained.
+        backgroundExecutor.execute {
+            keyedTokens.remove(key)
+            dispatchMediaDataRemoved(key)
+        }
+    }
+
+    private fun dispatchMediaDataLoaded(key: String, oldKey: String?, info: MediaData) {
+        foregroundExecutor.execute {
+            listeners.toSet().forEach { it.onMediaDataLoaded(key, oldKey, info) }
+        }
+    }
+
+    private fun dispatchMediaDataRemoved(key: String) {
+        foregroundExecutor.execute {
+            listeners.toSet().forEach { it.onMediaDataRemoved(key) }
+        }
+    }
+
+    private fun handleControllersChanged(controllers: List<MediaController>) {
+        packageControllers.clear()
+        controllers.forEach {
+            controller ->
+            packageControllers.get(controller.packageName)?.let {
+                tokens ->
+                tokens.add(controller)
+            } ?: run {
+                val tokens = mutableListOf(controller)
+                packageControllers.put(controller.packageName, tokens)
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
index 8662aac..fdbff98 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
@@ -20,12 +20,12 @@
 import android.media.session.PlaybackState
 import android.os.SystemProperties
 import android.util.Log
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
 import com.android.systemui.util.concurrency.DelayableExecutor
 import java.util.concurrent.TimeUnit
 import javax.inject.Inject
-import javax.inject.Singleton
 
 private const val DEBUG = true
 private const val TAG = "MediaTimeout"
@@ -35,7 +35,7 @@
 /**
  * Controller responsible for keeping track of playback states and expiring inactive streams.
  */
-@Singleton
+@SysUISingleton
 class MediaTimeoutListener @Inject constructor(
     private val mediaControllerFactory: MediaControllerFactory,
     @Main private val mainExecutor: DelayableExecutor
diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
index ccf58ba..5dce093 100644
--- a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
+++ b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
@@ -22,6 +22,7 @@
 import android.util.Log;
 
 import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.shared.system.QuickStepContract;
 
 import java.io.FileDescriptor;
@@ -29,13 +30,11 @@
 import java.util.ArrayList;
 import java.util.List;
 
-import javax.inject.Singleton;
-
 /**
  * Contains sysUi state flags and notifies registered
  * listeners whenever changes happen.
  */
-@Singleton
+@SysUISingleton
 public class SysUiState implements Dumpable {
 
     private static final String TAG = SysUiState.class.getSimpleName();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
similarity index 86%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index fd653b4..6c8a23b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -1,18 +1,20 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * 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
+ * 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.
+ * 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.phone;
+package com.android.systemui.navigationbar;
 
 import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT;
 import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
@@ -21,6 +23,7 @@
 import static android.app.StatusBarManager.WindowType;
 import static android.app.StatusBarManager.WindowVisibleState;
 import static android.app.StatusBarManager.windowStateToString;
+import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.containsType;
 import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
@@ -78,17 +81,16 @@
 import android.util.Log;
 import android.view.Display;
 import android.view.Gravity;
+import android.view.IWindowManager;
 import android.view.InsetsState.InternalInsetsType;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.Surface;
 import android.view.View;
-import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowManager;
-import android.view.WindowManager.LayoutParams;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener;
@@ -102,6 +104,7 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.util.LatencyTracker;
 import com.android.internal.view.AppearanceRegion;
+import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.accessibility.SystemActions;
 import com.android.systemui.assist.AssistHandleViewController;
@@ -109,8 +112,12 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.fragments.FragmentHostManager;
-import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
 import com.android.systemui.model.SysUiState;
+import com.android.systemui.navigationbar.buttons.ButtonDispatcher;
+import com.android.systemui.navigationbar.buttons.KeyButtonView;
+import com.android.systemui.navigationbar.buttons.RotationContextButton;
+import com.android.systemui.navigationbar.gestural.QuickswitchOrientedNavHandle;
+import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.recents.Recents;
@@ -123,29 +130,28 @@
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
-import com.android.systemui.statusbar.phone.ContextualButton.ContextButtonListener;
+import com.android.systemui.statusbar.phone.AutoHideController;
+import com.android.systemui.statusbar.phone.BarTransitions;
+import com.android.systemui.statusbar.phone.LightBarController;
+import com.android.systemui.statusbar.phone.ShadeController;
+import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
+import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.KeyButtonView;
-import com.android.systemui.util.LifecycleFragment;
 
-import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.List;
 import java.util.Locale;
 import java.util.Optional;
 import java.util.function.Consumer;
 
-import javax.inject.Inject;
-
 import dagger.Lazy;
 
 /**
- * Fragment containing the NavigationBarFragment. Contains logic for what happens
- * on clicks and view states of the nav bar.
+ * Contains logic for a navigation bar view.
  */
-public class NavigationBarFragment extends LifecycleFragment implements Callbacks,
-        NavigationModeController.ModeChangedListener, DisplayManager.DisplayListener {
+public class NavigationBar implements View.OnAttachStateChangeListener,
+        Callbacks, NavigationModeController.ModeChangedListener, DisplayManager.DisplayListener {
 
     public static final String TAG = "NavigationBar";
     private static final boolean DEBUG = false;
@@ -158,35 +164,41 @@
     private static final int LOCK_TO_APP_GESTURE_TOLERENCE = 200;
     private static final long AUTODIM_TIMEOUT_MS = 2250;
 
+    private final Context mContext;
+    private final WindowManager mWindowManager;
+    private final AccessibilityManager mAccessibilityManager;
     private final AccessibilityManagerWrapper mAccessibilityManagerWrapper;
-    protected final AssistManager mAssistManager;
-    private SysUiState mSysUiFlagsContainer;
-    private final MetricsLogger mMetricsLogger;
     private final DeviceProvisionedController mDeviceProvisionedController;
     private final StatusBarStateController mStatusBarStateController;
+    private final MetricsLogger mMetricsLogger;
+    private final Lazy<AssistManager> mAssistManagerLazy;
+    private final SysUiState mSysUiFlagsContainer;
+    private final Lazy<StatusBar> mStatusBarLazy;
+    private final ShadeController mShadeController;
+    private final NotificationRemoteInputManager mNotificationRemoteInputManager;
+    private final OverviewProxyService mOverviewProxyService;
     private final NavigationModeController mNavigationModeController;
+    private final BroadcastDispatcher mBroadcastDispatcher;
+    private final CommandQueue mCommandQueue;
+    private final Divider mDivider;
+    private final Optional<Recents> mRecentsOptional;
+    private final SystemActions mSystemActions;
+    private final Handler mHandler;
+    private final UiEventLogger mUiEventLogger;
 
-    protected NavigationBarView mNavigationBarView = null;
+    private Bundle mSavedState;
+    private NavigationBarView mNavigationBarView = null;
 
     private @WindowVisibleState int mNavigationBarWindowState = WINDOW_STATE_SHOWING;
 
     private int mNavigationIconHints = 0;
     private @TransitionMode int mNavigationBarMode;
-    private AccessibilityManager mAccessibilityManager;
     private ContentResolver mContentResolver;
     private boolean mAssistantAvailable;
 
     private int mDisabledFlags1;
     private int mDisabledFlags2;
-    private final Lazy<StatusBar> mStatusBarLazy;
-    private final ShadeController mShadeController;
-    private final NotificationRemoteInputManager mNotificationRemoteInputManager;
-    private final Divider mDivider;
-    private final Optional<Recents> mRecentsOptional;
-    private WindowManager mWindowManager;
-    private final CommandQueue mCommandQueue;
     private long mLastLockToAppLongPress;
-    private final SystemActions mSystemActions;
 
     private Locale mLocale;
     private int mLayoutDirection;
@@ -202,10 +214,6 @@
     private LightBarController mLightBarController;
     private AutoHideController mAutoHideController;
 
-    private OverviewProxyService mOverviewProxyService;
-
-    private final BroadcastDispatcher mBroadcastDispatcher;
-
     @VisibleForTesting
     public int mDisplayId;
     private boolean mIsOnDefaultDisplay;
@@ -226,7 +234,6 @@
     private int mStartingQuickSwitchRotation = -1;
     private int mCurrentRotation;
     private ViewTreeObserver.OnGlobalLayoutListener mOrientationHandleGlobalLayoutListener;
-    private UiEventLogger mUiEventLogger;
     private boolean mShowOrientedHandleForImmersiveMode;
 
     @com.android.internal.annotations.VisibleForTesting
@@ -251,7 +258,6 @@
     @Nullable
     private AssistHandleViewController mAssistHandlerViewController;
 
-    private final Handler mHandler;
 
     private final AutoHideUiElement mAutoHideUiElement = new AutoHideUiElement() {
         @Override
@@ -307,7 +313,7 @@
 
         @Override
         public void startAssistant(Bundle bundle) {
-            mAssistManager.startAssist(bundle);
+            mAssistManagerLazy.get().startAssist(bundle);
         }
 
         @Override
@@ -360,7 +366,7 @@
             new Handler(Looper.getMainLooper())) {
         @Override
         public void onChange(boolean selfChange, Uri uri) {
-            boolean available = mAssistManager
+            boolean available = mAssistManagerLazy.get()
                     .getAssistInfoForUser(UserHandle.USER_CURRENT) != null;
             if (mAssistantAvailable != available) {
                 sendAssistantAvailability(available);
@@ -369,34 +375,6 @@
         }
     };
 
-    private static class NavBarViewAttachedListener implements View.OnAttachStateChangeListener {
-        private NavigationBarFragment mFragment;
-        private FragmentListener mListener;
-
-        NavBarViewAttachedListener(NavigationBarFragment fragment, FragmentListener listener) {
-            mFragment = fragment;
-            mListener = listener;
-        }
-
-        @Override
-        public void onViewAttachedToWindow(View v) {
-            final FragmentHostManager fragmentHost = FragmentHostManager.get(v);
-            fragmentHost.getFragmentManager().beginTransaction()
-                    .replace(R.id.navigation_bar_frame, mFragment, TAG)
-                    .commit();
-            fragmentHost.addTagListener(TAG, mListener);
-            mFragment = null;
-        }
-
-        @Override
-        public void onViewDetachedFromWindow(View v) {
-            final FragmentHostManager fragmentHost = FragmentHostManager.get(v);
-            fragmentHost.removeTagListener(TAG, mListener);
-            FragmentHostManager.removeAndDestroy(v);
-            v.removeOnAttachStateChangeListener(this);
-        }
-    }
-
     private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener =
             new DeviceConfig.OnPropertiesChangedListener() {
         @Override
@@ -416,10 +394,14 @@
         }
     };
 
-    @Inject
-    public NavigationBarFragment(AccessibilityManagerWrapper accessibilityManagerWrapper,
-            DeviceProvisionedController deviceProvisionedController, MetricsLogger metricsLogger,
-            AssistManager assistManager, OverviewProxyService overviewProxyService,
+    public NavigationBar(Context context,
+            WindowManager windowManager,
+            Lazy<AssistManager> assistManagerLazy,
+            AccessibilityManager accessibilityManager,
+            AccessibilityManagerWrapper accessibilityManagerWrapper,
+            DeviceProvisionedController deviceProvisionedController,
+            MetricsLogger metricsLogger,
+            OverviewProxyService overviewProxyService,
             NavigationModeController navigationModeController,
             StatusBarStateController statusBarStateController,
             SysUiState sysUiFlagsContainer,
@@ -431,16 +413,18 @@
             SystemActions systemActions,
             @Main Handler mainHandler,
             UiEventLogger uiEventLogger) {
+        mContext = context;
+        mWindowManager = windowManager;
+        mAccessibilityManager = accessibilityManager;
         mAccessibilityManagerWrapper = accessibilityManagerWrapper;
         mDeviceProvisionedController = deviceProvisionedController;
         mStatusBarStateController = statusBarStateController;
         mMetricsLogger = metricsLogger;
-        mAssistManager = assistManager;
+        mAssistManagerLazy = assistManagerLazy;
         mSysUiFlagsContainer = sysUiFlagsContainer;
         mStatusBarLazy = statusBarLazy;
         mShadeController = shadeController;
         mNotificationRemoteInputManager = notificationRemoteInputManager;
-        mAssistantAvailable = mAssistManager.getAssistInfoForUser(UserHandle.USER_CURRENT) != null;
         mOverviewProxyService = overviewProxyService;
         mNavigationModeController = navigationModeController;
         mNavBarMode = navigationModeController.addListener(this);
@@ -453,25 +437,53 @@
         mUiEventLogger = uiEventLogger;
     }
 
-    // ----- Fragment Lifecycle Callbacks -----
+    public View getView() {
+        return mNavigationBarView;
+    }
 
-    @Override
-    public void onCreate(@Nullable Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        mCommandQueue.observe(getLifecycle(), this);
-        mWindowManager = getContext().getSystemService(WindowManager.class);
-        mAccessibilityManager = getContext().getSystemService(AccessibilityManager.class);
-        mContentResolver = getContext().getContentResolver();
+    public View createView(Bundle savedState) {
+        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+                WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT,
+                WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
+                WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
+                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                        | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+                        | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
+                        | WindowManager.LayoutParams.FLAG_SLIPPERY,
+                PixelFormat.TRANSLUCENT);
+        lp.token = new Binder();
+        lp.setTitle("NavigationBar" + mContext.getDisplayId());
+        lp.accessibilityTitle = mContext.getString(R.string.nav_bar);
+        lp.windowAnimations = 0;
+        lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
+
+        LayoutInflater layoutInflater = LayoutInflater.from(mContext);
+        NavigationBarFrame frame = (NavigationBarFrame) layoutInflater.inflate(
+                R.layout.navigation_bar_window, null);
+        View barView = layoutInflater.inflate(R.layout.navigation_bar, frame);
+        barView.addOnAttachStateChangeListener(this);
+
+        if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + barView);
+        mContext.getSystemService(WindowManager.class).addView(frame, lp);
+        mDisplayId = mContext.getDisplayId();
+        mIsOnDefaultDisplay = mDisplayId == DEFAULT_DISPLAY;
+
+        mCommandQueue.addCallback(this);
+        mAssistantAvailable = mAssistManagerLazy.get()
+                .getAssistInfoForUser(UserHandle.USER_CURRENT) != null;
+        mContentResolver = mContext.getContentResolver();
         mContentResolver.registerContentObserver(
                 Settings.Secure.getUriFor(Settings.Secure.ASSISTANT),
                 false /* notifyForDescendants */, mAssistContentObserver, UserHandle.USER_ALL);
 
-        if (savedInstanceState != null) {
-            mDisabledFlags1 = savedInstanceState.getInt(EXTRA_DISABLE_STATE, 0);
-            mDisabledFlags2 = savedInstanceState.getInt(EXTRA_DISABLE2_STATE, 0);
-            mAppearance = savedInstanceState.getInt(EXTRA_APPEARANCE, 0);
-            mTransientShown = savedInstanceState.getBoolean(EXTRA_TRANSIENT_STATE, false);
+        if (savedState != null) {
+            mDisabledFlags1 = savedState.getInt(EXTRA_DISABLE_STATE, 0);
+            mDisabledFlags2 = savedState.getInt(EXTRA_DISABLE2_STATE, 0);
+            mAppearance = savedState.getInt(EXTRA_APPEARANCE, 0);
+            mTransientShown = savedState.getBoolean(EXTRA_TRANSIENT_STATE, false);
         }
+        mSavedState = savedState;
         mAccessibilityManagerWrapper.addCallback(mAccessibilityListener);
 
         // Respect the latest disabled-flags.
@@ -486,12 +498,16 @@
 
         mIsCurrentUserSetup = mDeviceProvisionedController.isCurrentUserSetup();
         mDeviceProvisionedController.addCallback(mUserSetupListener);
+
+        return barView;
     }
 
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
+    public void destroyView() {
+        mCommandQueue.removeCallback(this);
+        mContext.getSystemService(WindowManager.class).removeViewImmediate(
+                mNavigationBarView.getRootView());
         mNavigationModeController.removeListener(this);
+
         mAccessibilityManagerWrapper.removeCallback(mAccessibilityListener);
         mContentResolver.unregisterContentObserver(mAssistContentObserver);
         mDeviceProvisionedController.removeCallback(mUserSetupListener);
@@ -500,28 +516,15 @@
     }
 
     @Override
-    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
-            Bundle savedInstanceState) {
-        return inflater.inflate(R.layout.navigation_bar, container, false);
-    }
-
-    @Override
-    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
-        super.onViewCreated(view, savedInstanceState);
-        mNavigationBarView = (NavigationBarView) view;
-        final Display display = view.getDisplay();
-        // It may not have display when running unit test.
-        if (display != null) {
-            mDisplayId = display.getDisplayId();
-            mIsOnDefaultDisplay = mDisplayId == Display.DEFAULT_DISPLAY;
-        }
-
+    public void onViewAttachedToWindow(View v) {
+        final Display display = v.getDisplay();
+        mNavigationBarView = v.findViewById(R.id.navigation_bar_view);
         mNavigationBarView.setComponents(mStatusBarLazy.get().getPanelController());
         mNavigationBarView.setDisabledFlags(mDisabledFlags1);
         mNavigationBarView.setOnVerticalChangedListener(this::onVerticalChanged);
         mNavigationBarView.setOnTouchListener(this::onNavigationTouch);
-        if (savedInstanceState != null) {
-            mNavigationBarView.getLightTransitionsController().restoreState(savedInstanceState);
+        if (mSavedState != null) {
+            mNavigationBarView.getLightTransitionsController().restoreState(mSavedState);
         }
         mNavigationBarView.setNavigationIconHints(mNavigationIconHints);
         mNavigationBarView.setWindowVisible(isNavBarWindowVisible());
@@ -561,11 +564,32 @@
         }
 
         initSecondaryHomeHandleForRotation();
+
+        // Unfortunately, we still need it because status bar needs LightBarController
+        // before notifications creation. We cannot directly use getLightBarController()
+        // from NavigationBarFragment directly.
+        LightBarController lightBarController = mIsOnDefaultDisplay
+                ? Dependency.get(LightBarController.class)
+                : new LightBarController(mContext,
+                        Dependency.get(DarkIconDispatcher.class),
+                        Dependency.get(BatteryController.class),
+                        Dependency.get(NavigationModeController.class));
+        setLightBarController(lightBarController);
+
+        // TODO(b/118592525): to support multi-display, we start to add something which is
+        //                    per-display, while others may be global. I think it's time to
+        //                    add a new class maybe named DisplayDependency to solve
+        //                    per-display Dependency problem.
+        AutoHideController autoHideController = mIsOnDefaultDisplay
+                ? Dependency.get(AutoHideController.class)
+                : new AutoHideController(mContext, mHandler,
+                        Dependency.get(IWindowManager.class));
+        setAutoHideController(autoHideController);
+        restoreAppearanceAndTransientState();
     }
 
     @Override
-    public void onDestroyView() {
-        super.onDestroyView();
+    public void onViewDetachedFromWindow(View v) {
         if (mNavigationBarView != null) {
             if (mIsOnDefaultDisplay) {
                 mNavigationBarView.getBarTransitions()
@@ -573,13 +597,13 @@
                 mAssistHandlerViewController = null;
             }
             mNavigationBarView.getBarTransitions().destroy();
-            mNavigationBarView.getLightTransitionsController().destroy(getContext());
+            mNavigationBarView.getLightTransitionsController().destroy(mContext);
         }
         mOverviewProxyService.removeCallback(mOverviewProxyListener);
         mBroadcastDispatcher.unregisterReceiver(mBroadcastReceiver);
         if (mOrientationHandle != null) {
             resetSecondaryHandle();
-            getContext().getSystemService(DisplayManager.class).unregisterDisplayListener(this);
+            mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this);
             getBarTransitions().removeDarkIntensityListener(mOrientationHandleIntensityListener);
             mWindowManager.removeView(mOrientationHandle);
             mOrientationHandle.getViewTreeObserver().removeOnGlobalLayoutListener(
@@ -590,9 +614,8 @@
         mOrientationHandle = null;
     }
 
-    @Override
+    // TODO: Remove this when we update nav bar recreation
     public void onSaveInstanceState(Bundle outState) {
-        super.onSaveInstanceState(outState);
         outState.putInt(EXTRA_DISABLE_STATE, mDisabledFlags1);
         outState.putInt(EXTRA_DISABLE2_STATE, mDisabledFlags2);
         outState.putInt(EXTRA_APPEARANCE, mAppearance);
@@ -602,10 +625,11 @@
         }
     }
 
-    @Override
+    /**
+     * Called when a non-reloading configuration change happens and we need to update.
+     */
     public void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        final Locale locale = getContext().getResources().getConfiguration().locale;
+        final Locale locale = mContext.getResources().getConfiguration().locale;
         final int ld = TextUtils.getLayoutDirectionFromLocale(locale);
         if (!locale.equals(mLocale) || ld != mLayoutDirection) {
             if (DEBUG) {
@@ -625,10 +649,10 @@
             return;
         }
 
-        getContext().getSystemService(DisplayManager.class)
+        mContext.getSystemService(DisplayManager.class)
                 .registerDisplayListener(this, new Handler(Looper.getMainLooper()));
 
-        mOrientationHandle = new QuickswitchOrientedNavHandle(getContext());
+        mOrientationHandle = new QuickswitchOrientedNavHandle(mContext);
         mOrientationHandle.setId(R.id.secondary_home_handle);
 
         getBarTransitions().addDarkIntensityListener(mOrientationHandleIntensityListener);
@@ -640,7 +664,7 @@
                         | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
                         | WindowManager.LayoutParams.FLAG_SLIPPERY,
                 PixelFormat.TRANSLUCENT);
-        mOrientationParams.setTitle("SecondaryHomeHandle" + getContext().getDisplayId());
+        mOrientationParams.setTitle("SecondaryHomeHandle" + mContext.getDisplayId());
         mOrientationParams.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION;
         mWindowManager.addView(mOrientationHandle, mOrientationParams);
         mOrientationHandle.setVisibility(View.GONE);
@@ -729,23 +753,20 @@
         return delta;
     }
 
-    @Override
-    public void dump(String prefix, FileDescriptor fd, PrintWriter pw, String[] args) {
-        if (mNavigationBarView != null) {
-            pw.print("  mNavigationBarWindowState=");
-            pw.println(windowStateToString(mNavigationBarWindowState));
-            pw.print("  mNavigationBarMode=");
-            pw.println(BarTransitions.modeToString(mNavigationBarMode));
-            dumpBarTransitions(pw, "mNavigationBarView", mNavigationBarView.getBarTransitions());
-        }
+    public void dump(PrintWriter pw) {
+        pw.println("NavigationBar (displayId=" + mDisplayId + "):");
+        pw.println("  mStartingQuickSwitchRotation=" + mStartingQuickSwitchRotation);
+        pw.println("  mCurrentRotation=" + mCurrentRotation);
 
-        pw.print("  mStartingQuickSwitchRotation=" + mStartingQuickSwitchRotation);
-        pw.print("  mCurrentRotation=" + mCurrentRotation);
-        pw.print("  mNavigationBarView=");
-        if (mNavigationBarView == null) {
-            pw.println("null");
+        if (mNavigationBarView != null) {
+            pw.println("  mNavigationBarWindowState="
+                    + windowStateToString(mNavigationBarWindowState));
+            pw.println("  mNavigationBarMode="
+                    + BarTransitions.modeToString(mNavigationBarMode));
+            dumpBarTransitions(pw, "mNavigationBarView", mNavigationBarView.getBarTransitions());
+            mNavigationBarView.dump(pw);
         } else {
-            mNavigationBarView.dump(fd, pw, args);
+            pw.print("  mNavigationBarView=null");
         }
     }
 
@@ -834,15 +855,18 @@
         rotationButtonController.onRotationProposal(rotation, winRotation, isValid);
     }
 
-    /** Restores the appearance and the transient saved state to {@link NavigationBarFragment}. */
+    /** Restores the appearance and the transient saved state to {@link NavigationBar}. */
     public void restoreAppearanceAndTransientState() {
         final int barMode = barMode(mTransientShown, mAppearance);
         mNavigationBarMode = barMode;
         checkNavBarModes();
-        mAutoHideController.touchAutoHide();
-
-        mLightBarController.onNavigationBarAppearanceChanged(mAppearance, true /* nbModeChanged */,
-                barMode, false /* navbarColorManagedByIme */);
+        if (mAutoHideController != null) {
+            mAutoHideController.touchAutoHide();
+        }
+        if (mLightBarController != null) {
+            mLightBarController.onNavigationBarAppearanceChanged(mAppearance,
+                    true /* nbModeChanged */, barMode, false /* navbarColorManagedByIme */);
+        }
     }
 
     @Override
@@ -859,8 +883,10 @@
             }
             nbModeChanged = updateBarMode(barMode(mTransientShown, appearance));
         }
-        mLightBarController.onNavigationBarAppearanceChanged(appearance, nbModeChanged,
-                mNavigationBarMode, navbarColorManagedByIme);
+        if (mLightBarController != null) {
+            mLightBarController.onNavigationBarAppearanceChanged(appearance, nbModeChanged,
+                    mNavigationBarMode, navbarColorManagedByIme);
+        }
     }
 
     @Override
@@ -903,7 +929,7 @@
             mNavigationBarView.onTransientStateChanged(mTransientShown);
         }
         final int barMode = barMode(mTransientShown, mAppearance);
-        if (updateBarMode(barMode)) {
+        if (updateBarMode(barMode) && mLightBarController != null) {
             mLightBarController.onNavigationBarModeChanged(barMode);
         }
     }
@@ -917,7 +943,9 @@
             }
             mNavigationBarMode = barMode;
             checkNavBarModes();
-            mAutoHideController.touchAutoHide();
+            if (mAutoHideController != null) {
+                mAutoHideController.touchAutoHide();
+            }
             return true;
         }
         return false;
@@ -1053,7 +1081,7 @@
             case MotionEvent.ACTION_DOWN:
                 mHomeBlockedThisTouch = false;
                 TelecomManager telecomManager =
-                        getContext().getSystemService(TelecomManager.class);
+                        mContext.getSystemService(TelecomManager.class);
                 if (telecomManager != null && telecomManager.isRinging()) {
                     if (mStatusBarLazy.get().isKeyguardShowing()) {
                         Log.i(TAG, "Ignoring HOME; there's a ringing incoming call. " +
@@ -1076,7 +1104,9 @@
     }
 
     private boolean onNavigationTouch(View v, MotionEvent event) {
-        mAutoHideController.checkUserAutoHide(event);
+        if (mAutoHideController != null) {
+            mAutoHideController.checkUserAutoHide(event);
+        }
         return false;
     }
 
@@ -1094,7 +1124,7 @@
         Bundle args  = new Bundle();
         args.putInt(
                 AssistManager.INVOCATION_TYPE_KEY, AssistManager.INVOCATION_HOME_BUTTON_LONG_PRESS);
-        mAssistManager.startAssist(args);
+        mAssistManagerLazy.get().startAssist(args);
         mStatusBarLazy.get().awakenDreams();
 
         if (mNavigationBarView != null) {
@@ -1120,8 +1150,8 @@
     }
 
     private void onRecentsClick(View v) {
-        if (LatencyTracker.isEnabled(getContext())) {
-            LatencyTracker.getInstance(getContext()).onActionStart(
+        if (LatencyTracker.isEnabled(mContext)) {
+            LatencyTracker.getInstance(mContext).onActionStart(
                     LatencyTracker.ACTION_TOGGLE_RECENTS);
         }
         mStatusBarLazy.get().awakenDreams();
@@ -1215,7 +1245,7 @@
     }
 
     private boolean onLongPressRecents() {
-        if (mRecentsOptional.isPresent() || !ActivityTaskManager.supportsMultiWindow(getContext())
+        if (mRecentsOptional.isPresent() || !ActivityTaskManager.supportsMultiWindow(mContext)
                 || !mDivider.getView().getSnapAlgorithm().isSplitScreenFeasible()
                 || ActivityManager.isLowRamDeviceStatic()
                 // If we are connected to the overview service, then disable the recents button
@@ -1230,7 +1260,7 @@
     private void onAccessibilityClick(View v) {
         final Display display = v.getDisplay();
         mAccessibilityManager.notifyAccessibilityButtonClicked(
-                display != null ? display.getDisplayId() : Display.DEFAULT_DISPLAY);
+                display != null ? display.getDisplayId() : DEFAULT_DISPLAY);
     }
 
     private boolean onAccessibilityLongClick(View v) {
@@ -1238,7 +1268,7 @@
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
         final String chooserClassName = AccessibilityButtonChooserActivity.class.getName();
         intent.setClassName(CHOOSER_PACKAGE_NAME, chooserClassName);
-        v.getContext().startActivityAsUser(intent, UserHandle.CURRENT);
+        mContext.startActivityAsUser(intent, UserHandle.CURRENT);
         return true;
     }
 
@@ -1335,7 +1365,10 @@
 
     public void setLightBarController(LightBarController lightBarController) {
         mLightBarController = lightBarController;
-        mLightBarController.setNavigationBar(mNavigationBarView.getLightTransitionsController());
+        if (mLightBarController != null) {
+            mLightBarController.setNavigationBar(
+                    mNavigationBarView.getLightTransitionsController());
+        }
     }
 
     /** Sets {@link AutoHideController} to the navigation bar. */
@@ -1381,17 +1414,6 @@
         if (!canShowSecondaryHandle()) {
             resetSecondaryHandle();
         }
-
-        // Workaround for b/132825155, for secondary users, we currently don't receive configuration
-        // changes on overlay package change since SystemUI runs for the system user. In this case,
-        // trigger a new configuration change to ensure that the nav bar is updated in the same way.
-        int userId = ActivityManagerWrapper.getInstance().getCurrentUserId();
-        if (userId != UserHandle.USER_SYSTEM) {
-            mHandler.post(() -> {
-                FragmentHostManager fragmentHost = FragmentHostManager.get(mNavigationBarView);
-                fragmentHost.reloadFragments();
-            });
-        }
     }
 
     public void disableAnimationsDuringHide(long delay) {
@@ -1442,7 +1464,7 @@
             return;
         }
 
-        int rotation = getContext().getResources().getConfiguration()
+        int rotation = mContext.getResources().getConfiguration()
                 .windowConfiguration.getRotation();
         if (rotation != mCurrentRotation) {
             mCurrentRotation = rotation;
@@ -1480,37 +1502,6 @@
         }
     };
 
-    public static View create(Context context, FragmentListener listener) {
-        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
-                LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
-                WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
-                WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
-                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
-                        | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
-                        | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
-                        | WindowManager.LayoutParams.FLAG_SLIPPERY,
-                PixelFormat.TRANSLUCENT);
-        lp.token = new Binder();
-        lp.setTitle("NavigationBar" + context.getDisplayId());
-        lp.accessibilityTitle = context.getString(R.string.nav_bar);
-        lp.windowAnimations = 0;
-        lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
-
-        View navigationBarView = LayoutInflater.from(context).inflate(
-                R.layout.navigation_bar_window, null);
-
-        if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + navigationBarView);
-        if (navigationBarView == null) return null;
-
-        NavigationBarFragment fragment = FragmentHostManager.get(navigationBarView)
-                .create(NavigationBarFragment.class);
-        navigationBarView.addOnAttachStateChangeListener(new NavBarViewAttachedListener(fragment,
-                listener));
-        context.getSystemService(WindowManager.class).addView(navigationBarView, lp);
-        return navigationBarView;
-    }
-
     @VisibleForTesting
     int getNavigationIconHints() {
         return mNavigationIconHints;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
new file mode 100644
index 0000000..fd157c6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -0,0 +1,396 @@
+/*
+ * 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.navigationbar;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.hardware.display.DisplayManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.IWindowManager;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+import android.view.accessibility.AccessibilityManager;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.statusbar.RegisterStatusBarResult;
+import com.android.settingslib.applications.InterestingConfigChanges;
+import com.android.systemui.Dumpable;
+import com.android.systemui.accessibility.SystemActions;
+import com.android.systemui.assist.AssistHandleViewController;
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.model.SysUiState;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.stackdivider.Divider;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.CommandQueue.Callbacks;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
+import com.android.systemui.statusbar.phone.ShadeController;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Optional;
+
+import javax.inject.Inject;
+
+import dagger.Lazy;
+
+
+/** A controller to handle navigation bars. */
+@SysUISingleton
+public class NavigationBarController implements Callbacks,
+        ConfigurationController.ConfigurationListener,
+        NavigationModeController.ModeChangedListener, Dumpable {
+
+    private static final String TAG = NavigationBarController.class.getSimpleName();
+
+    private final Context mContext;
+    private final WindowManager mWindowManager;
+    private final Lazy<AssistManager> mAssistManagerLazy;
+    private final AccessibilityManager mAccessibilityManager;
+    private final AccessibilityManagerWrapper mAccessibilityManagerWrapper;
+    private final DeviceProvisionedController mDeviceProvisionedController;
+    private final MetricsLogger mMetricsLogger;
+    private final OverviewProxyService mOverviewProxyService;
+    private final NavigationModeController mNavigationModeController;
+    private final StatusBarStateController mStatusBarStateController;
+    private final SysUiState mSysUiFlagsContainer;
+    private final BroadcastDispatcher mBroadcastDispatcher;
+    private final CommandQueue mCommandQueue;
+    private final Divider mDivider;
+    private final Optional<Recents> mRecentsOptional;
+    private final Lazy<StatusBar> mStatusBarLazy;
+    private final ShadeController mShadeController;
+    private final NotificationRemoteInputManager mNotificationRemoteInputManager;
+    private final SystemActions mSystemActions;
+    private final UiEventLogger mUiEventLogger;
+    private final Handler mHandler;
+    private final DisplayManager mDisplayManager;
+
+    /** A displayId - nav bar maps. */
+    @VisibleForTesting
+    SparseArray<NavigationBar> mNavigationBars = new SparseArray<>();
+
+    // Tracks config changes that will actually recreate the nav bar
+    private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges(
+            ActivityInfo.CONFIG_FONT_SCALE | ActivityInfo.CONFIG_LOCALE
+                    | ActivityInfo.CONFIG_SCREEN_LAYOUT | ActivityInfo.CONFIG_ASSETS_PATHS
+                    | ActivityInfo.CONFIG_UI_MODE);
+
+    @Inject
+    public NavigationBarController(Context context,
+            WindowManager windowManager,
+            Lazy<AssistManager> assistManagerLazy,
+            AccessibilityManager accessibilityManager,
+            AccessibilityManagerWrapper accessibilityManagerWrapper,
+            DeviceProvisionedController deviceProvisionedController,
+            MetricsLogger metricsLogger,
+            OverviewProxyService overviewProxyService,
+            NavigationModeController navigationModeController,
+            StatusBarStateController statusBarStateController,
+            SysUiState sysUiFlagsContainer,
+            BroadcastDispatcher broadcastDispatcher,
+            CommandQueue commandQueue,
+            Divider divider,
+            Optional<Recents> recentsOptional,
+            Lazy<StatusBar> statusBarLazy,
+            ShadeController shadeController,
+            NotificationRemoteInputManager notificationRemoteInputManager,
+            SystemActions systemActions,
+            @Main Handler mainHandler,
+            UiEventLogger uiEventLogger,
+            ConfigurationController configurationController) {
+        mContext = context;
+        mWindowManager = windowManager;
+        mAssistManagerLazy = assistManagerLazy;
+        mAccessibilityManager = accessibilityManager;
+        mAccessibilityManagerWrapper = accessibilityManagerWrapper;
+        mDeviceProvisionedController = deviceProvisionedController;
+        mMetricsLogger = metricsLogger;
+        mOverviewProxyService = overviewProxyService;
+        mNavigationModeController = navigationModeController;
+        mStatusBarStateController = statusBarStateController;
+        mSysUiFlagsContainer = sysUiFlagsContainer;
+        mBroadcastDispatcher = broadcastDispatcher;
+        mCommandQueue = commandQueue;
+        mDivider = divider;
+        mRecentsOptional = recentsOptional;
+        mStatusBarLazy = statusBarLazy;
+        mShadeController = shadeController;
+        mNotificationRemoteInputManager = notificationRemoteInputManager;
+        mSystemActions = systemActions;
+        mUiEventLogger = uiEventLogger;
+        mHandler = mainHandler;
+        mDisplayManager = mContext.getSystemService(DisplayManager.class);
+        commandQueue.addCallback(this);
+        configurationController.addCallback(this);
+        mConfigChanges.applyNewConfig(mContext.getResources());
+    }
+
+    @Override
+    public void onConfigChanged(Configuration newConfig) {
+        if (mConfigChanges.applyNewConfig(mContext.getResources())) {
+            for (int i = 0; i < mNavigationBars.size(); i++) {
+                recreateNavigationBar(mNavigationBars.keyAt(i));
+            }
+        } else {
+            for (int i = 0; i < mNavigationBars.size(); i++) {
+                mNavigationBars.get(i).onConfigurationChanged(newConfig);
+            }
+        }
+    }
+
+    @Override
+    public void onNavigationModeChanged(int mode) {
+        // Workaround for b/132825155, for secondary users, we currently don't receive configuration
+        // changes on overlay package change since SystemUI runs for the system user. In this case,
+        // trigger a new configuration change to ensure that the nav bar is updated in the same way.
+        int userId = ActivityManagerWrapper.getInstance().getCurrentUserId();
+        if (userId != UserHandle.USER_SYSTEM) {
+            mHandler.post(() -> {
+                for (int i = 0; i < mNavigationBars.size(); i++) {
+                    recreateNavigationBar(mNavigationBars.keyAt(i));
+                }
+            });
+        }
+    }
+
+    @Override
+    public void onDisplayRemoved(int displayId) {
+        removeNavigationBar(displayId);
+    }
+
+    @Override
+    public void onDisplayReady(int displayId) {
+        Display display = mDisplayManager.getDisplay(displayId);
+        createNavigationBar(display, null /* savedState */, null /* result */);
+    }
+
+    /**
+     * Recreates the navigation bar for the given display.
+     */
+    private void recreateNavigationBar(int displayId) {
+        // TODO: Improve this flow so that we don't need to create a new nav bar but just
+        //       the view
+        Bundle savedState = new Bundle();
+        NavigationBar bar = mNavigationBars.get(displayId);
+        if (bar != null) {
+            bar.onSaveInstanceState(savedState);
+        }
+        removeNavigationBar(displayId);
+        createNavigationBar(mDisplayManager.getDisplay(displayId), savedState, null /* result */);
+    }
+
+    // TODO(b/117478341): I use {@code includeDefaultDisplay} to make this method compatible to
+    // CarStatusBar because they have their own nav bar. Think about a better way for it.
+    /**
+     * Creates navigation bars when car/status bar initializes.
+     *
+     * @param includeDefaultDisplay {@code true} to create navigation bar on default display.
+     */
+    public void createNavigationBars(final boolean includeDefaultDisplay,
+            RegisterStatusBarResult result) {
+        Display[] displays = mDisplayManager.getDisplays();
+        for (Display display : displays) {
+            if (includeDefaultDisplay || display.getDisplayId() != DEFAULT_DISPLAY) {
+                createNavigationBar(display, null /* savedState */, result);
+            }
+        }
+    }
+
+    /**
+     * Adds a navigation bar on default display or an external display if the display supports
+     * system decorations.
+     *
+     * @param display the display to add navigation bar on.
+     */
+    @VisibleForTesting
+    void createNavigationBar(Display display, Bundle savedState, RegisterStatusBarResult result) {
+        if (display == null) {
+            return;
+        }
+
+        final int displayId = display.getDisplayId();
+        final boolean isOnDefaultDisplay = displayId == DEFAULT_DISPLAY;
+        final IWindowManager wms = WindowManagerGlobal.getWindowManagerService();
+
+        try {
+            if (!wms.hasNavigationBar(displayId)) {
+                return;
+            }
+        } catch (RemoteException e) {
+            // Cannot get wms, just return with warning message.
+            Log.w(TAG, "Cannot get WindowManager.");
+            return;
+        }
+        final Context context = isOnDefaultDisplay
+                ? mContext
+                : mContext.createDisplayContext(display);
+        NavigationBar navBar = new NavigationBar(context,
+                mWindowManager,
+                mAssistManagerLazy,
+                mAccessibilityManager,
+                mAccessibilityManagerWrapper,
+                mDeviceProvisionedController,
+                mMetricsLogger,
+                mOverviewProxyService,
+                mNavigationModeController,
+                mStatusBarStateController,
+                mSysUiFlagsContainer,
+                mBroadcastDispatcher,
+                mCommandQueue,
+                mDivider,
+                mRecentsOptional,
+                mStatusBarLazy,
+                mShadeController,
+                mNotificationRemoteInputManager,
+                mSystemActions,
+                mHandler,
+                mUiEventLogger);
+
+        View navigationBarView = navBar.createView(savedState);
+        navigationBarView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+            @Override
+            public void onViewAttachedToWindow(View v) {
+                mNavigationBars.put(displayId, navBar);
+
+                if (result != null) {
+                    navBar.setImeWindowStatus(display.getDisplayId(), result.mImeToken,
+                            result.mImeWindowVis, result.mImeBackDisposition,
+                            result.mShowImeSwitcher);
+                }
+            }
+
+            @Override
+            public void onViewDetachedFromWindow(View v) {
+                v.removeOnAttachStateChangeListener(this);
+            }
+        });
+    }
+
+    private void removeNavigationBar(int displayId) {
+        NavigationBar navBar = mNavigationBars.get(displayId);
+        if (navBar != null) {
+            navBar.setAutoHideController(/* autoHideController */ null);
+            navBar.destroyView();
+            mNavigationBars.remove(displayId);
+        }
+    }
+
+    /** @see NavigationBar#checkNavBarModes() */
+    public void checkNavBarModes(int displayId) {
+        NavigationBar navBar = mNavigationBars.get(displayId);
+        if (navBar != null) {
+            navBar.checkNavBarModes();
+        }
+    }
+
+    /** @see NavigationBar#finishBarAnimations() */
+    public void finishBarAnimations(int displayId) {
+        NavigationBar navBar = mNavigationBars.get(displayId);
+        if (navBar != null) {
+            navBar.finishBarAnimations();
+        }
+    }
+
+    /** @see NavigationBar#touchAutoDim() */
+    public void touchAutoDim(int displayId) {
+        NavigationBar navBar = mNavigationBars.get(displayId);
+        if (navBar != null) {
+            navBar.touchAutoDim();
+        }
+    }
+
+    /** @see NavigationBar#transitionTo(int, boolean) */
+    public void transitionTo(int displayId, @TransitionMode int barMode, boolean animate) {
+        NavigationBar navBar = mNavigationBars.get(displayId);
+        if (navBar != null) {
+            navBar.transitionTo(barMode, animate);
+        }
+    }
+
+    /** @see NavigationBar#disableAnimationsDuringHide(long) */
+    public void disableAnimationsDuringHide(int displayId, long delay) {
+        NavigationBar navBar = mNavigationBars.get(displayId);
+        if (navBar != null) {
+            navBar.disableAnimationsDuringHide(delay);
+        }
+    }
+
+    /** @return {@link NavigationBarView} on the default display. */
+    public @Nullable NavigationBarView getDefaultNavigationBarView() {
+        return getNavigationBarView(DEFAULT_DISPLAY);
+    }
+
+    /**
+     * @param displayId the ID of display which Navigation bar is on
+     * @return {@link NavigationBarView} on the display with {@code displayId}.
+     *         {@code null} if no navigation bar on that display.
+     */
+    public @Nullable NavigationBarView getNavigationBarView(int displayId) {
+        NavigationBar navBar = mNavigationBars.get(displayId);
+        return (navBar == null) ? null : (NavigationBarView) navBar.getView();
+    }
+
+    /** @return {@link NavigationBar} on the default display. */
+    @Nullable
+    public NavigationBar getDefaultNavigationBar() {
+        return mNavigationBars.get(DEFAULT_DISPLAY);
+    }
+
+    /** @return {@link AssistHandleViewController} (only on the default display). */
+    @Nullable
+    public AssistHandleViewController getAssistHandlerViewController() {
+        NavigationBar navBar = getDefaultNavigationBar();
+        return navBar == null ? null : navBar.getAssistHandlerViewController();
+    }
+
+    @Override
+    public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+        for (int i = 0; i < mNavigationBars.size(); i++) {
+            if (i > 0) {
+                pw.println();
+            }
+            mNavigationBars.get(i).dump(pw);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFrame.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarFrame.java
similarity index 71%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFrame.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarFrame.java
index 741f783..6c531d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFrame.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarFrame.java
@@ -1,18 +1,20 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * 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
+ * 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.
+ * 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.phone;
+package com.android.systemui.navigationbar;
 
 import static android.view.MotionEvent.ACTION_OUTSIDE;
 
@@ -24,7 +26,7 @@
 import android.view.MotionEvent;
 import android.widget.FrameLayout;
 
-import com.android.systemui.statusbar.policy.DeadZone;
+import com.android.systemui.navigationbar.buttons.DeadZone;
 
 public class NavigationBarFrame extends FrameLayout {
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java
similarity index 95%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java
index 4337e20..2707460 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java
@@ -1,18 +1,20 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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
+ * 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.
+ * 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.phone;
+package com.android.systemui.navigationbar;
 
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
@@ -35,10 +37,12 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
+import com.android.systemui.navigationbar.buttons.ButtonDispatcher;
+import com.android.systemui.navigationbar.buttons.KeyButtonView;
+import com.android.systemui.navigationbar.buttons.ReverseLinearLayout;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.shared.system.QuickStepContract;
-import com.android.systemui.statusbar.phone.ReverseLinearLayout.ReverseRelativeLayout;
-import com.android.systemui.statusbar.policy.KeyButtonView;
+import com.android.systemui.navigationbar.buttons.ReverseLinearLayout.ReverseRelativeLayout;
 
 import java.io.PrintWriter;
 import java.util.Objects;
@@ -472,8 +476,7 @@
     }
 
     public void dump(PrintWriter pw) {
-        pw.println("NavigationBarInflaterView {");
-        pw.println("      mCurrentLayout: " + mCurrentLayout);
-        pw.println("    }");
+        pw.println("NavigationBarInflaterView");
+        pw.println("  mCurrentLayout: " + mCurrentLayout);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java
index 1f50954..c0535b5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 The Android Open Source Project
+ * 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.
@@ -14,27 +14,27 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar;
 
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
 
 import static com.android.systemui.util.Utils.isGesturalModeOnDefaultDisplay;
 
-import android.content.Context;
 import android.graphics.Rect;
 import android.os.Handler;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.util.SparseArray;
 import android.view.Display;
 import android.view.IWallpaperVisibilityListener;
 import android.view.IWindowManager;
 import android.view.View;
 
-import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
+import com.android.systemui.navigationbar.buttons.ButtonDispatcher;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.phone.BarTransitions;
+import com.android.systemui.statusbar.phone.LightBarTransitionsController;
 
 import java.util.ArrayList;
 import java.util.List;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index e3cb105..a335183 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar;
 
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
 
@@ -70,6 +70,15 @@
 import com.android.systemui.R;
 import com.android.systemui.assist.AssistHandleViewController;
 import com.android.systemui.model.SysUiState;
+import com.android.systemui.navigationbar.buttons.ButtonDispatcher;
+import com.android.systemui.navigationbar.buttons.ContextualButton;
+import com.android.systemui.navigationbar.buttons.ContextualButtonGroup;
+import com.android.systemui.navigationbar.buttons.DeadZone;
+import com.android.systemui.navigationbar.buttons.KeyButtonDrawable;
+import com.android.systemui.navigationbar.buttons.RotationContextButton;
+import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
+import com.android.systemui.navigationbar.gestural.FloatingRotationButton;
+import com.android.systemui.navigationbar.gestural.RegionSamplingHelper;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsOnboarding;
@@ -80,9 +89,10 @@
 import com.android.systemui.shared.system.WindowManagerWrapper;
 import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.NavigationBarController;
-import com.android.systemui.statusbar.policy.DeadZone;
-import com.android.systemui.statusbar.policy.KeyButtonDrawable;
+import com.android.systemui.statusbar.phone.AutoHideController;
+import com.android.systemui.statusbar.phone.LightBarTransitionsController;
+import com.android.systemui.statusbar.phone.NotificationPanelViewController;
+import com.android.systemui.statusbar.phone.StatusBar;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -167,7 +177,7 @@
      * When quickswitching between apps of different orientations, we draw a secondary home handle
      * in the position of the first app's orientation. This rect represents the region of that
      * home handle so we can apply the correct light/dark luma on that.
-     * @see {@link NavigationBarFragment#mOrientationHandle}
+     * @see {@link NavigationBar#mOrientationHandle}
      */
     @Nullable
     private Rect mOrientedHandleSamplingRegion;
@@ -779,7 +789,7 @@
             } else {
                 return;
             }
-            WindowManager wm = (WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE);
+            WindowManager wm = getContext().getSystemService(WindowManager.class);
             wm.updateViewLayout((View) getParent(), lp);
         }
     }
@@ -867,7 +877,7 @@
         } else {
             lp.flags &= ~flags;
         }
-        WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
+        WindowManager wm = getContext().getSystemService(WindowManager.class);
         wm.updateViewLayout(navbarView, lp);
     }
 
@@ -1163,6 +1173,9 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
+        // This needs to happen first as it can changed the enabled state which can affect whether
+        // the back button is visible
+        mEdgeBackGestureHandler.onNavBarAttached();
         requestApplyInsets();
         reorient();
         onNavigationModeChanged(mNavBarMode);
@@ -1171,7 +1184,6 @@
             mRotationButtonController.registerListeners();
         }
 
-        mEdgeBackGestureHandler.onNavBarAttached();
         getViewTreeObserver().addOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener);
     }
 
@@ -1200,12 +1212,12 @@
         }
     }
 
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("NavigationBarView {");
+    public void dump(PrintWriter pw) {
         final Rect r = new Rect();
         final Point size = new Point();
         getContextDisplay().getRealSize(size);
 
+        pw.println("NavigationBarView:");
         pw.println(String.format("      this: " + StatusBar.viewInfo(this)
                         + " " + visibilityToString(getVisibility())));
 
@@ -1228,6 +1240,8 @@
                         getLightTransitionsController().getCurrentDarkIntensity()));
 
         pw.println("      mOrientedHandleSamplingRegion: " + mOrientedHandleSamplingRegion);
+        pw.println("    mScreenOn: " + mScreenOn);
+
 
         dumpButton(pw, "back", getBackButton());
         dumpButton(pw, "home", getHomeButton());
@@ -1236,9 +1250,6 @@
         dumpButton(pw, "a11y", getAccessibilityButton());
         dumpButton(pw, "ime", getImeSwitchButton());
 
-        pw.println("    }");
-        pw.println("    mScreenOn: " + mScreenOn);
-
         if (mNavigationInflaterView != null) {
             mNavigationInflaterView.dump(pw);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java
index c211de0..c704d32 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar;
 
 import static android.content.Intent.ACTION_OVERLAY_CHANGED;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
@@ -37,6 +37,7 @@
 import android.util.Log;
 
 import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -48,12 +49,11 @@
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Controller for tracking the current navigation bar mode.
  */
-@Singleton
+@SysUISingleton
 public class NavigationModeController implements Dumpable {
 
     private static final String TAG = NavigationModeController.class.getSimpleName();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButton.java b/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButton.java
similarity index 87%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButton.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/RotationButton.java
index 74d4eb1..e487858 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButton.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButton.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,16 +14,16 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar;
 
 import android.view.View;
 
-import com.android.systemui.statusbar.policy.KeyButtonDrawable;
+import com.android.systemui.navigationbar.buttons.KeyButtonDrawable;
 
 import java.util.function.Consumer;
 
 /** Interface of a rotation button that interacts {@link RotationButtonController}. */
-interface RotationButton {
+public interface RotationButton {
     void setRotationButtonController(RotationButtonController rotationButtonController);
     void setVisibilityChangedCallback(Consumer<Boolean> visibilityChangedCallback);
     View getCurrentView();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButtonController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButtonController.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
index 2f2e1f9..6cbf065 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButtonController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar;
 
 import static com.android.internal.view.RotationPolicy.NATURAL_ROTATION;
 
@@ -23,7 +23,6 @@
 import android.animation.ObjectAnimator;
 import android.annotation.ColorInt;
 import android.annotation.DrawableRes;
-import android.annotation.StyleRes;
 import android.app.StatusBarManager;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -32,7 +31,6 @@
 import android.os.RemoteException;
 import android.provider.Settings;
 import android.util.Log;
-import android.view.ContextThemeWrapper;
 import android.view.IRotationWatcher.Stub;
 import android.view.MotionEvent;
 import android.view.Surface;
@@ -43,14 +41,13 @@
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.UiEventLoggerImpl;
-import com.android.settingslib.Utils;
 import com.android.systemui.Dependency;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
+import com.android.systemui.navigationbar.buttons.KeyButtonDrawable;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
-import com.android.systemui.statusbar.policy.KeyButtonDrawable;
 import com.android.systemui.statusbar.policy.RotationLockController;
 
 import java.util.Optional;
@@ -328,7 +325,7 @@
         }
     }
 
-    Context getContext() {
+    public Context getContext() {
         return mContext;
     }
 
@@ -336,15 +333,15 @@
         return mRotationButton;
     }
 
-    @DrawableRes int getIconResId() {
+    public @DrawableRes int getIconResId() {
         return mIconResId;
     }
 
-    @ColorInt int getLightIconColor() {
+    public @ColorInt int getLightIconColor() {
         return mLightIconColor;
     }
 
-    @ColorInt int getDarkIconColor() {
+    public @ColorInt int getDarkIconColor() {
         return mDarkIconColor;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenPinningNotify.java b/packages/SystemUI/src/com/android/systemui/navigationbar/ScreenPinningNotify.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenPinningNotify.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/ScreenPinningNotify.java
index 071e00d..ac7baf5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenPinningNotify.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/ScreenPinningNotify.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar;
 
 import android.content.Context;
 import android.os.SystemClock;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java
similarity index 94%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java
index c273108..ade2923 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java
@@ -1,18 +1,20 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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
+ * 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.
+ * 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.phone;
+package com.android.systemui.navigationbar.buttons;
 
 import static com.android.systemui.Interpolators.LINEAR;
 
@@ -24,7 +26,6 @@
 
 import com.android.systemui.Dependency;
 import com.android.systemui.assist.AssistManager;
-import com.android.systemui.statusbar.policy.KeyButtonDrawable;
 
 import java.util.ArrayList;
 
@@ -75,11 +76,11 @@
         mAssistManager = Dependency.get(AssistManager.class);
     }
 
-    void clear() {
+    public void clear() {
         mViews.clear();
     }
 
-    void addView(View view) {
+    public void addView(View view) {
         mViews.add(view);
         view.setOnClickListener(mClickListener);
         view.setOnTouchListener(mTouchListener);
@@ -337,6 +338,6 @@
     /**
      * Executes when button is detached from window.
      */
-    protected void onDestroy() {
+    public void onDestroy() {
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonInterface.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonInterface.java
similarity index 86%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonInterface.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonInterface.java
index 150a960..8d291dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonInterface.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonInterface.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -11,10 +11,10 @@
  * 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
+ * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar.buttons;
 
 import android.annotation.Nullable;
 import android.graphics.drawable.Drawable;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ContextualButton.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ContextualButton.java
similarity index 94%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/ContextualButton.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ContextualButton.java
index eb47645..453e85a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ContextualButton.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ContextualButton.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -11,10 +11,10 @@
  * 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
+ * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar.buttons;
 
 import android.annotation.DrawableRes;
 import android.annotation.IdRes;
@@ -22,9 +22,6 @@
 import android.content.Context;
 import android.view.View;
 
-import com.android.systemui.statusbar.policy.KeyButtonDrawable;
-import com.android.systemui.statusbar.policy.KeyButtonView;
-
 /**
  * Simple contextual button that is added to the {@link ContextualButtonGroup}. Extend if need extra
  * functionality.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ContextualButtonGroup.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ContextualButtonGroup.java
similarity index 90%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/ContextualButtonGroup.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ContextualButtonGroup.java
index c1017f4..50b638b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ContextualButtonGroup.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ContextualButtonGroup.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -11,10 +11,10 @@
  * 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
+ * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar.buttons;
 
 import android.annotation.IdRes;
 import android.annotation.NonNull;
@@ -119,21 +119,20 @@
 
     public void dump(PrintWriter pw) {
         View view = getCurrentView();
-        pw.println("ContextualButtonGroup {");
-        pw.println("      getVisibleContextButton(): " + getVisibleContextButton());
-        pw.println("      isVisible(): " + isVisible());
-        pw.println("      attached(): " + (view != null && view.isAttachedToWindow()));
-        pw.println("      mButtonData [ ");
+        pw.println("ContextualButtonGroup");
+        pw.println("  getVisibleContextButton(): " + getVisibleContextButton());
+        pw.println("  isVisible(): " + isVisible());
+        pw.println("  attached(): " + (view != null && view.isAttachedToWindow()));
+        pw.println("  mButtonData [ ");
         for (int i = mButtonData.size() - 1; i >= 0; --i) {
             final ButtonData data = mButtonData.get(i);
             view = data.button.getCurrentView();
-            pw.println("            " + i + ": markedVisible=" + data.markedVisible
+            pw.println("    " + i + ": markedVisible=" + data.markedVisible
                     + " visible=" + data.button.getVisibility()
                     + " attached=" + (view != null && view.isAttachedToWindow())
                     + " alpha=" + data.button.getAlpha());
         }
-        pw.println("      ]");
-        pw.println("    }");
+        pw.println("  ]");
     }
 
     private int getContextButtonIndex(@IdRes int buttonResId) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeadZone.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/DeadZone.java
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/statusbar/policy/DeadZone.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/buttons/DeadZone.java
index 12d0617..7e5b554 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeadZone.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/DeadZone.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.policy;
+package com.android.systemui.navigationbar.buttons;
 
 import android.animation.ObjectAnimator;
 import android.content.res.Resources;
@@ -26,8 +26,8 @@
 
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
-import com.android.systemui.statusbar.NavigationBarController;
-import com.android.systemui.statusbar.phone.NavigationBarView;
+import com.android.systemui.navigationbar.NavigationBarController;
+import com.android.systemui.navigationbar.NavigationBarView;
 
 /**
  * The "dead zone" consumes unintentional taps along the top edge of the navigation bar.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonDrawable.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonDrawable.java
index 7559388..fc20169 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonDrawable.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -11,10 +11,10 @@
  * 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
+ * limitations under the License.
  */
 
-package com.android.systemui.statusbar.policy;
+package com.android.systemui.navigationbar.buttons;
 
 import android.animation.ArgbEvaluator;
 import android.annotation.ColorInt;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java
index 2d8784d..72cd4f1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -11,10 +11,10 @@
  * 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
+ * limitations under the License.
  */
 
-package com.android.systemui.statusbar.policy;
+package com.android.systemui.navigationbar.buttons;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
index 8d7ecd0..d6b8316 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.policy;
+package com.android.systemui.navigationbar.buttons;
 
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.KeyEvent.KEYCODE_UNKNOWN;
@@ -61,7 +61,6 @@
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.shared.system.QuickStepContract;
-import com.android.systemui.statusbar.phone.ButtonInterface;
 
 public class KeyButtonView extends ImageView implements ButtonInterface {
     private static final String TAG = KeyButtonView.class.getSimpleName();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NearestTouchFrame.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/NearestTouchFrame.java
similarity index 88%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/NearestTouchFrame.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/buttons/NearestTouchFrame.java
index d022808..88c8fea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NearestTouchFrame.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/NearestTouchFrame.java
@@ -1,18 +1,20 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * 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
+ * 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.
+ * 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.phone;
+package com.android.systemui.navigationbar.buttons;
 
 import android.content.Context;
 import android.content.res.Configuration;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ReverseLinearLayout.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ReverseLinearLayout.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/ReverseLinearLayout.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ReverseLinearLayout.java
index d3ec187..f1e1366 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ReverseLinearLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ReverseLinearLayout.java
@@ -12,7 +12,7 @@
  * permissions and limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar.buttons;
 
 import android.annotation.Nullable;
 import android.content.Context;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationContextButton.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/RotationContextButton.java
similarity index 87%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationContextButton.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/buttons/RotationContextButton.java
index d63d445..6a97a33 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationContextButton.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/RotationContextButton.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -11,17 +11,20 @@
  * 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
+ * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar.buttons;
 
 import android.annotation.DrawableRes;
 import android.annotation.IdRes;
 import android.content.Context;
 import android.view.View;
 
-import com.android.systemui.statusbar.policy.KeyButtonDrawable;
+import com.android.systemui.navigationbar.RotationButton;
+import com.android.systemui.navigationbar.RotationButtonController;
+import com.android.systemui.navigationbar.buttons.ContextualButton;
+import com.android.systemui.navigationbar.buttons.KeyButtonDrawable;
 
 import java.util.function.Consumer;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 9606318..a1b55c4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -1,5 +1,5 @@
-/**
- * Copyright (C) 2019 The Android Open Source Project
+/*
+ * 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.
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar.gestural;
 
 import static android.view.Display.INVALID_DISPLAY;
 
@@ -59,6 +59,8 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.model.SysUiState;
+import com.android.systemui.navigationbar.NavigationBarView;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.NavigationEdgeBackPlugin;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.recents.OverviewProxyService;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/FloatingRotationButton.java
similarity index 93%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/gestural/FloatingRotationButton.java
index 8a85f7d..61118c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/FloatingRotationButton.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar.gestural;
 
 import android.content.Context;
 import android.content.res.Resources;
@@ -27,8 +27,10 @@
 import android.view.WindowManager;
 
 import com.android.systemui.R;
-import com.android.systemui.statusbar.policy.KeyButtonDrawable;
-import com.android.systemui.statusbar.policy.KeyButtonView;
+import com.android.systemui.navigationbar.RotationButton;
+import com.android.systemui.navigationbar.RotationButtonController;
+import com.android.systemui.navigationbar.buttons.KeyButtonDrawable;
+import com.android.systemui.navigationbar.buttons.KeyButtonView;
 
 import java.util.function.Consumer;
 
@@ -49,7 +51,7 @@
     private RotationButtonController mRotationButtonController;
     private Consumer<Boolean> mVisibilityChangedCallback;
 
-    FloatingRotationButton(Context context) {
+    public FloatingRotationButton(Context context) {
         mContext = context;
         mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
         mKeyButtonView = (KeyButtonView) LayoutInflater.from(mContext).inflate(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
similarity index 99%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarEdgePanel.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
index 2357309..284f41a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarEdgePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar.gestural;
 
 import android.animation.ValueAnimator;
 import android.content.Context;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationHandle.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationHandle.java
similarity index 95%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationHandle.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationHandle.java
index b874795..33e6aa4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationHandle.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationHandle.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar.gestural;
 
 import android.animation.ArgbEvaluator;
 import android.annotation.ColorInt;
@@ -29,6 +29,7 @@
 
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
+import com.android.systemui.navigationbar.buttons.ButtonInterface;
 
 public class NavigationHandle extends View implements ButtonInterface {
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickswitchOrientedNavHandle.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/QuickswitchOrientedNavHandle.java
similarity index 91%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickswitchOrientedNavHandle.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/gestural/QuickswitchOrientedNavHandle.java
index fe74677..71c8a2c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickswitchOrientedNavHandle.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/QuickswitchOrientedNavHandle.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar.gestural;
 
 import android.content.Context;
 import android.graphics.Canvas;
@@ -34,7 +34,7 @@
         mWidth = context.getResources().getDimensionPixelSize(R.dimen.navigation_home_handle_width);
     }
 
-    void setDeltaRotation(@Surface.Rotation int rotation) {
+    public void setDeltaRotation(@Surface.Rotation int rotation) {
         mDeltaRotation = rotation;
     }
 
@@ -43,7 +43,7 @@
         canvas.drawRoundRect(computeHomeHandleBounds(), mRadius, mRadius, mPaint);
     }
 
-    RectF computeHomeHandleBounds() {
+    public RectF computeHomeHandleBounds() {
         int left;
         int top;
         int bottom;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RegionSamplingHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/RegionSamplingHelper.java
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/RegionSamplingHelper.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/gestural/RegionSamplingHelper.java
index 3c8aa86..70117eb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RegionSamplingHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/RegionSamplingHelper.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -11,10 +11,10 @@
  * 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
+ * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar.gestural;
 
 import static android.view.Display.DEFAULT_DISPLAY;
 
@@ -108,7 +108,7 @@
         }
     }
 
-    void start(Rect initialSamplingBounds) {
+    public void start(Rect initialSamplingBounds) {
         if (!mCallback.isSamplingEnabled()) {
             return;
         }
@@ -122,12 +122,12 @@
         updateSamplingListener();
     }
 
-    void stop() {
+    public void stop() {
         mSamplingEnabled = false;
         updateSamplingListener();
     }
 
-    void stopAndDestroy() {
+    public void stopAndDestroy() {
         stop();
         mSamplingListener.destroy();
         mIsDestroyed = true;
@@ -220,12 +220,12 @@
         }
     }
 
-    void setWindowVisible(boolean visible) {
+    public void setWindowVisible(boolean visible) {
         mWindowVisible = visible;
         updateSamplingListener();
     }
 
-    void dump(PrintWriter pw) {
+    public void dump(PrintWriter pw) {
         pw.println("RegionSamplingHelper:");
         pw.println("  sampleView isAttached: " + mSampledView.isAttachedToWindow());
         pw.println("  sampleView isScValid: " + (mSampledView.isAttachedToWindow()
diff --git a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedAnimationController.java
index 2b07ac3..9be1b5a 100644
--- a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedAnimationController.java
@@ -19,6 +19,7 @@
 import android.animation.Animator;
 import android.animation.ValueAnimator;
 import android.annotation.IntDef;
+import android.content.Context;
 import android.graphics.Rect;
 import android.view.SurfaceControl;
 import android.view.animation.Interpolator;
@@ -32,8 +33,6 @@
 import java.util.HashMap;
 import java.util.List;
 
-import javax.inject.Inject;
-
 /**
  * Controller class of OneHanded animations (both from and to OneHanded mode).
  */
@@ -62,10 +61,8 @@
     /**
      * Constructor of OneHandedAnimationController
      */
-    @Inject
-    public OneHandedAnimationController(
-            OneHandedSurfaceTransactionHelper surfaceTransactionHelper) {
-        mSurfaceTransactionHelper = surfaceTransactionHelper;
+    public OneHandedAnimationController(Context context) {
+        mSurfaceTransactionHelper = new OneHandedSurfaceTransactionHelper(context);
         mOvershootInterpolator = new OvershootInterpolator();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedDisplayAreaOrganizer.java b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedDisplayAreaOrganizer.java
index 8550959..ad9f7ea 100644
--- a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedDisplayAreaOrganizer.java
+++ b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedDisplayAreaOrganizer.java
@@ -26,6 +26,7 @@
 import android.graphics.Rect;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.SystemProperties;
 import android.util.Log;
 import android.view.SurfaceControl;
 import android.window.DisplayAreaInfo;
@@ -47,8 +48,6 @@
 import java.util.List;
 import java.util.Objects;
 
-import javax.inject.Inject;
-
 /**
  * Manages OneHanded display areas such as offset.
  *
@@ -61,6 +60,8 @@
  */
 public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer implements Dumpable {
     private static final String TAG = "OneHandedDisplayAreaOrganizer";
+    private static final String ONE_HANDED_MODE_TRANSLATE_ANIMATION_DURATION =
+            "persist.debug.one_handed_translate_animation_duration";
 
     @VisibleForTesting
     static final int MSG_RESET_IMMEDIATE = 1;
@@ -146,7 +147,6 @@
     /**
      * Constructor of OneHandedDisplayAreaOrganizer
      */
-    @Inject
     public OneHandedDisplayAreaOrganizer(Context context,
             DisplayController displayController,
             OneHandedAnimationController animationController,
@@ -156,8 +156,8 @@
         mDisplayController = displayController;
         mDefaultDisplayBounds.set(getDisplayBounds());
         mLastVisualDisplayBounds.set(getDisplayBounds());
-        mEnterExitAnimationDurationMs = context.getResources().getInteger(
-                com.android.systemui.R.integer.config_one_handed_translate_animation_duration);
+        mEnterExitAnimationDurationMs =
+                SystemProperties.getInt(ONE_HANDED_MODE_TRANSLATE_ANIMATION_DURATION, 300);
         mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
         mTutorialHandler = tutorialHandler;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedGestureHandler.java b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedGestureHandler.java
index 563684a..1420811 100644
--- a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedGestureHandler.java
@@ -40,18 +40,14 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.NavigationModeController;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.wm.shell.common.DisplayChangeController;
 import com.android.wm.shell.common.DisplayController;
 
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
 /**
  * The class manage swipe up and down gesture for 3-Button mode navigation,
  * others(e.g, 2-button, full gesture mode) are handled by Launcher quick steps.
  */
-@Singleton
 public class OneHandedGestureHandler implements OneHandedTransitionCallback,
         NavigationModeController.ModeChangedListener,
         DisplayChangeController.OnDisplayChangingListener {
@@ -91,7 +87,6 @@
      * @param displayController        {@link DisplayController}
      * @param navigationModeController {@link NavigationModeController}
      */
-    @Inject
     public OneHandedGestureHandler(Context context, DisplayController displayController,
             NavigationModeController navigationModeController) {
         mDisplayController = displayController;
diff --git a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedManagerImpl.java b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedManagerImpl.java
index a3921ee..90e7e12 100644
--- a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedManagerImpl.java
@@ -24,14 +24,16 @@
 import android.content.Context;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.os.SystemProperties;
 import android.view.KeyEvent;
 
 import androidx.annotation.NonNull;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Dumpable;
-import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.model.SysUiState;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.statusbar.CommandQueue;
@@ -42,14 +44,15 @@
 import java.io.PrintWriter;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Manages and manipulates the one handed states, transitions, and gesture for phones.
  */
-@Singleton
+@SysUISingleton
 public class OneHandedManagerImpl implements OneHandedManager, Dumpable {
     private static final String TAG = "OneHandedManager";
+    private static final String ONE_HANDED_MODE_OFFSET_PERCENTAGE =
+            "persist.debug.one_handed_offset_percentage";
 
     private boolean mIsOneHandedEnabled;
     private boolean mIsSwipeToNotificationEnabled;
@@ -92,7 +95,6 @@
     /**
      * Handle rotation based on OnDisplayChangingListener callback
      */
-    @VisibleForTesting
     private final DisplayChangeController.OnDisplayChangingListener mRotationController =
             (display, fromRotation, toRotation, wct) -> {
                 if (mDisplayAreaOrganizer != null) {
@@ -107,6 +109,37 @@
     public OneHandedManagerImpl(Context context,
             CommandQueue commandQueue,
             DisplayController displayController,
+            NavigationModeController navigationModeController,
+            SysUiState sysUiState) {
+        mCommandQueue = commandQueue;
+        mDisplayController = displayController;
+        mDisplayController.addDisplayChangingController(mRotationController);
+        mSysUiFlagContainer = sysUiState;
+        mOffSetFraction = SystemProperties.getInt(ONE_HANDED_MODE_OFFSET_PERCENTAGE, 50) / 100.0f;
+
+        mIsOneHandedEnabled = OneHandedSettingsUtil.getSettingsOneHandedModeEnabled(
+                context.getContentResolver());
+        mIsSwipeToNotificationEnabled = OneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
+                context.getContentResolver());
+        mTimeoutHandler = OneHandedTimeoutHandler.get();
+        mTouchHandler = new OneHandedTouchHandler();
+        mTutorialHandler = new OneHandedTutorialHandler(context);
+        mDisplayAreaOrganizer = new OneHandedDisplayAreaOrganizer(context, displayController,
+                new OneHandedAnimationController(context), mTutorialHandler);
+        mGestureHandler = new OneHandedGestureHandler(
+                context, displayController, navigationModeController);
+        updateOneHandedEnabled();
+        setupGestures();
+    }
+
+    /**
+     * Constructor of OneHandedManager for testing
+     */
+    // TODO(b/161980408): Should remove extra constructor.
+    @VisibleForTesting
+    OneHandedManagerImpl(Context context,
+            CommandQueue commandQueue,
+            DisplayController displayController,
             OneHandedDisplayAreaOrganizer displayAreaOrganizer,
             OneHandedTouchHandler touchHandler,
             OneHandedTutorialHandler tutorialHandler,
@@ -117,8 +150,8 @@
         mDisplayController = displayController;
         mDisplayController.addDisplayChangingController(mRotationController);
         mSysUiFlagContainer = sysUiState;
-        mOffSetFraction =
-                context.getResources().getFraction(R.fraction.config_one_handed_offset, 1, 1);
+        mOffSetFraction = SystemProperties.getInt(ONE_HANDED_MODE_OFFSET_PERCENTAGE, 50) / 100.0f;
+
         mIsOneHandedEnabled = OneHandedSettingsUtil.getSettingsOneHandedModeEnabled(
                 context.getContentResolver());
         mIsSwipeToNotificationEnabled = OneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
diff --git a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedSettingsUtil.java b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedSettingsUtil.java
index 1b6ec04..0598f32 100644
--- a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedSettingsUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedSettingsUtil.java
@@ -22,19 +22,16 @@
 import android.net.Uri;
 import android.provider.Settings;
 
-import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.dagger.SysUISingleton;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
 /**
  * APIs for querying or updating one handed settings .
  */
-@Singleton
+@SysUISingleton
 public final class OneHandedSettingsUtil {
     private static final String TAG = "OneHandedSettingsUtil";
 
@@ -65,11 +62,6 @@
      */
     public static final int ONE_HANDED_TIMEOUT_LONG_IN_SECONDS = 12;
 
-    @VisibleForTesting
-    @Inject
-    OneHandedSettingsUtil() {
-    }
-
     /**
      * Register one handed preference settings observer
      *
@@ -150,4 +142,6 @@
         pw.print(innerPrefix + "tapsAppToExit=");
         pw.println(getSettingsTapsAppToExit(resolver));
     }
+
+    private OneHandedSettingsUtil() {}
 }
diff --git a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedSurfaceTransactionHelper.java b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedSurfaceTransactionHelper.java
index 5933eb3..bc4a9b4 100644
--- a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedSurfaceTransactionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedSurfaceTransactionHelper.java
@@ -23,8 +23,6 @@
 
 import com.android.systemui.R;
 
-import javax.inject.Inject;
-
 /**
  * Abstracts the common operations on {@link SurfaceControl.Transaction} for OneHanded transition.
  */
@@ -32,7 +30,6 @@
     private final boolean mEnableCornerRadius;
     private final float mCornerRadius;
 
-    @Inject
     public OneHandedSurfaceTransactionHelper(Context context) {
         final Resources res = context.getResources();
         mCornerRadius = res.getDimension(com.android.internal.R.dimen.rounded_corner_radius);
diff --git a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedTimeoutHandler.java b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedTimeoutHandler.java
index 194b24f..6bed304 100644
--- a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedTimeoutHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedTimeoutHandler.java
@@ -33,12 +33,9 @@
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 
-import javax.inject.Singleton;
-
 /**
  * Timeout handler for stop one handed mode operations.
  */
-@Singleton
 public class OneHandedTimeoutHandler implements Dumpable {
     private static final String TAG = "OneHandedTimeoutHandler";
     private static boolean sIsDragging = false;
diff --git a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedTouchHandler.java b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedTouchHandler.java
index 1446e5a..0a7eb1b 100644
--- a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedTouchHandler.java
@@ -35,15 +35,11 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
 /**
  * Manages all the touch handling for One Handed on the Phone, including user tap outside region
  * to exit, reset timer when user is in one-handed mode.
  * Refer {@link OneHandedGestureHandler} to see start and stop one handed gesture
  */
-@Singleton
 public class OneHandedTouchHandler implements OneHandedTransitionCallback, Dumpable {
     private static final String TAG = "OneHandedTouchHandler";
     private final Rect mLastUpdatedBounds = new Rect();
@@ -61,7 +57,6 @@
     private boolean mIsOnStopTransitioning;
     private boolean mIsInOutsideRegion;
 
-    @Inject
     public OneHandedTouchHandler() {
         mTimeoutHandler = OneHandedTimeoutHandler.get();
         updateIsEnabled();
diff --git a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedTutorialHandler.java b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedTutorialHandler.java
index b28730d0..8ef9b09 100644
--- a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedTutorialHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedTutorialHandler.java
@@ -22,6 +22,7 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Handler;
+import android.os.SystemProperties;
 import android.provider.Settings;
 import android.view.Gravity;
 import android.view.LayoutInflater;
@@ -38,18 +39,16 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
 /**
  * Manages the user tutorial handling for One Handed operations, including animations synchronized
  * with one-handed translation.
  * Refer {@link OneHandedGestureHandler} and {@link OneHandedTouchHandler} to see start and stop
  * one handed gesture
  */
-@Singleton
 public class OneHandedTutorialHandler implements OneHandedTransitionCallback, Dumpable {
     private static final String TAG = "OneHandedTutorialHandler";
+    private static final String ONE_HANDED_MODE_OFFSET_PERCENTAGE =
+            "persist.debug.one_handed_offset_percentage";
     private static final int MAX_TUTORIAL_SHOW_COUNT = 2;
     private final Rect mLastUpdatedBounds = new Rect();
     private final WindowManager mWindowManager;
@@ -73,7 +72,6 @@
         }
     };
 
-    @Inject
     public OneHandedTutorialHandler(Context context) {
         context.getDisplay().getRealSize(mDisplaySize);
         mContentResolver = context.getContentResolver();
@@ -81,8 +79,8 @@
         mWindowManager = context.getSystemService(WindowManager.class);
         mTargetViewContainer = new FrameLayout(context);
         mTargetViewContainer.setClipChildren(false);
-        mTutorialAreaHeight = Math.round(mDisplaySize.y * context.getResources().getFraction(
-                R.fraction.config_one_handed_offset, 1, 1));
+        mTutorialAreaHeight = Math.round(mDisplaySize.y
+                * (SystemProperties.getInt(ONE_HANDED_MODE_OFFSET_PERCENTAGE, 50) / 100.0f));
         mTutorialView = LayoutInflater.from(context).inflate(R.xml.one_handed_tutorial, null);
         mTargetViewContainer.addView(mTutorialView);
         mCanShowTutorial = (Settings.Secure.getInt(mContentResolver,
diff --git a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedUI.java b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedUI.java
index 0903c0e..cebcd4c 100644
--- a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedUI.java
+++ b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedUI.java
@@ -40,6 +40,7 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
 import com.android.systemui.SystemUI;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.statusbar.CommandQueue;
 
@@ -47,12 +48,11 @@
 import java.io.PrintWriter;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * A service that controls UI of the one handed mode function.
  */
-@Singleton
+@SysUISingleton
 public class OneHandedUI extends SystemUI implements CommandQueue.Callbacks, Dumpable {
     private static final String TAG = "OneHandedUI";
     private static final String ONE_HANDED_MODE_GESTURAL_OVERLAY =
@@ -63,7 +63,6 @@
     private final CommandQueue mCommandQueue;
     private final Handler mMainHandler = new Handler(Looper.getMainLooper());
     private final IOverlayManager mOverlayManager;
-    private final OneHandedSettingsUtil mSettingUtil;
     private final OneHandedTimeoutHandler mTimeoutHandler;
     private final ScreenLifecycle mScreenLifecycle;
 
@@ -154,7 +153,6 @@
     public OneHandedUI(Context context,
             CommandQueue commandQueue,
             OneHandedManagerImpl oneHandedManager,
-            OneHandedSettingsUtil settingsUtil,
             ScreenLifecycle screenLifecycle) {
         super(context);
 
@@ -163,7 +161,6 @@
             mCommandQueue = null;
             mOneHandedManager = null;
             mOverlayManager = null;
-            mSettingUtil = null;
             mTimeoutHandler = null;
             mScreenLifecycle = null;
             return;
@@ -171,7 +168,6 @@
 
         mCommandQueue = commandQueue;
         mOneHandedManager = oneHandedManager;
-        mSettingUtil = settingsUtil;
         mTimeoutHandler = OneHandedTimeoutHandler.get();
         mScreenLifecycle = screenLifecycle;
         mOverlayManager = IOverlayManager.Stub.asInterface(
@@ -252,26 +248,26 @@
     }
 
     private void setupSettingObservers() {
-        mSettingUtil.registerSettingsKeyObserver(Settings.Secure.ONE_HANDED_MODE_ENABLED,
+        OneHandedSettingsUtil.registerSettingsKeyObserver(Settings.Secure.ONE_HANDED_MODE_ENABLED,
                 mContext.getContentResolver(), mEnabledObserver);
-        mSettingUtil.registerSettingsKeyObserver(Settings.Secure.ONE_HANDED_MODE_TIMEOUT,
+        OneHandedSettingsUtil.registerSettingsKeyObserver(Settings.Secure.ONE_HANDED_MODE_TIMEOUT,
                 mContext.getContentResolver(), mTimeoutObserver);
-        mSettingUtil.registerSettingsKeyObserver(Settings.Secure.TAPS_APP_TO_EXIT,
+        OneHandedSettingsUtil.registerSettingsKeyObserver(Settings.Secure.TAPS_APP_TO_EXIT,
                 mContext.getContentResolver(), mTaskChangeExitObserver);
-        mSettingUtil.registerSettingsKeyObserver(
+        OneHandedSettingsUtil.registerSettingsKeyObserver(
                 Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED,
                 mContext.getContentResolver(), mSwipeToNotificationEnabledObserver);
     }
 
     private void updateSettings() {
-        mOneHandedManager.setOneHandedEnabled(
-                mSettingUtil.getSettingsOneHandedModeEnabled(mContext.getContentResolver()));
-        mTimeoutHandler.setTimeout(
-                mSettingUtil.getSettingsOneHandedModeTimeout(mContext.getContentResolver()));
-        mOneHandedManager.setTaskChangeToExit(
-                mSettingUtil.getSettingsTapsAppToExit(mContext.getContentResolver()));
-        mOneHandedManager.setSwipeToNotificationEnabled(
-                mSettingUtil.getSettingsSwipeToNotificationEnabled(mContext.getContentResolver()));
+        mOneHandedManager.setOneHandedEnabled(OneHandedSettingsUtil
+                .getSettingsOneHandedModeEnabled(mContext.getContentResolver()));
+        mTimeoutHandler.setTimeout(OneHandedSettingsUtil
+                .getSettingsOneHandedModeTimeout(mContext.getContentResolver()));
+        mOneHandedManager.setTaskChangeToExit(OneHandedSettingsUtil
+                .getSettingsTapsAppToExit(mContext.getContentResolver()));
+        mOneHandedManager.setSwipeToNotificationEnabled(OneHandedSettingsUtil
+                .getSettingsSwipeToNotificationEnabled(mContext.getContentResolver()));
     }
 
     @Override
@@ -326,9 +322,7 @@
             mTimeoutHandler.dump(fd, pw, args);
         }
 
-        if (mSettingUtil != null) {
-            mSettingUtil.dump(pw, innerPrefix, mContext.getContentResolver());
-        }
+        OneHandedSettingsUtil.dump(pw, innerPrefix, mContext.getContentResolver());
 
         if (mOverlayManager != null) {
             OverlayInfo info = null;
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
index df3aead..d6aa61b 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
@@ -39,19 +39,19 @@
 import android.view.Gravity;
 import android.window.WindowContainerTransaction;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayLayout;
 
 import java.io.PrintWriter;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Handles bounds calculation for PIP on Phone and other form factors, it keeps tracking variant
  * state changes originated from Window Manager and is the source of truth for PiP window bounds.
  */
-@Singleton
+@SysUISingleton
 public class PipBoundsHandler {
 
     private static final String TAG = PipBoundsHandler.class.getSimpleName();
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipSurfaceTransactionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/PipSurfaceTransactionHelper.java
index 2c7ec48..e88451c 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipSurfaceTransactionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipSurfaceTransactionHelper.java
@@ -23,16 +23,16 @@
 import android.graphics.RectF;
 import android.view.SurfaceControl;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.wm.shell.R;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Abstracts the common operations on {@link SurfaceControl.Transaction} for PiP transition.
  */
-@Singleton
+@SysUISingleton
 public class PipSurfaceTransactionHelper implements ConfigurationController.ConfigurationListener {
 
     private final Context mContext;
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
index 025341c..0e60c83 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
@@ -56,6 +56,7 @@
 import android.window.WindowOrganizer;
 
 import com.android.internal.os.SomeArgs;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.pip.phone.PipUpdateThread;
 import com.android.systemui.stackdivider.Divider;
 import com.android.wm.shell.R;
@@ -70,7 +71,6 @@
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Manages PiP tasks such as resize and offset.
@@ -83,7 +83,7 @@
  * This class is also responsible for general resize/offset PiP operations within SysUI component,
  * see also {@link com.android.systemui.pip.phone.PipMotionHelper}.
  */
-@Singleton
+@SysUISingleton
 public class PipTaskOrganizer extends TaskOrganizer implements
         DisplayController.OnDisplaysChangedListener {
     private static final String TAG = PipTaskOrganizer.class.getSimpleName();
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipUI.java b/packages/SystemUI/src/com/android/systemui/pip/PipUI.java
index 4fb675e..2cd1e20 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipUI.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipUI.java
@@ -25,6 +25,7 @@
 import android.os.UserManager;
 
 import com.android.systemui.SystemUI;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.shared.recents.IPinnedStackAnimationListener;
 import com.android.systemui.statusbar.CommandQueue;
 
@@ -32,12 +33,11 @@
 import java.io.PrintWriter;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Controls the picture-in-picture window.
  */
-@Singleton
+@SysUISingleton
 public class PipUI extends SystemUI implements CommandQueue.Callbacks {
 
     private final CommandQueue mCommandQueue;
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipUiEventLogger.java b/packages/SystemUI/src/com/android/systemui/pip/PipUiEventLogger.java
index 5e2cd9c..7ce2028 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipUiEventLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipUiEventLogger.java
@@ -20,15 +20,15 @@
 
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.dagger.SysUISingleton;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 
 /**
  * Helper class that ends PiP log to UiEvent, see also go/uievent
  */
-@Singleton
+@SysUISingleton
 public class PipUiEventLogger {
 
     private final UiEventLogger mUiEventLogger;
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
index 9dfa864..facb396 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -42,6 +42,7 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.UiOffloadThread;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.pip.BasePipManager;
 import com.android.systemui.pip.PipBoundsHandler;
@@ -64,12 +65,11 @@
 import java.io.PrintWriter;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Manages the picture-in-picture (PIP) UI and states for Phones.
  */
-@Singleton
+@SysUISingleton
 public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitionCallback {
     private static final String TAG = "PipManager";
 
@@ -233,7 +233,10 @@
 
         @Override
         public void onAspectRatioChanged(float aspectRatio) {
-            mHandler.post(() -> mPipBoundsHandler.onAspectRatioChanged(aspectRatio));
+            mHandler.post(() -> {
+                mPipBoundsHandler.onAspectRatioChanged(aspectRatio);
+                mTouchHandler.onAspectRatioChanged();
+            });
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java
index 9c42f8b..2800bb9 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java
@@ -364,6 +364,10 @@
         mUserResizeBounds.set(bounds);
     }
 
+    void invalidateUserResizeBounds() {
+        mUserResizeBounds.setEmpty();
+    }
+
     Rect getUserResizeBounds() {
         return mUserResizeBounds;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index b20ea4e..ecd315b 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -426,8 +426,21 @@
         }
     }
 
+    /**
+     * Responds to IPinnedStackListener on resetting aspect ratio for the pinned window.
+     */
+    public void onAspectRatioChanged() {
+        mPipResizeGestureHandler.invalidateUserResizeBounds();
+    }
+
     public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds, Rect curBounds,
             boolean fromImeAdjustment, boolean fromShelfAdjustment, int displayRotation) {
+        // Set the user resized bounds equal to the new normal bounds in case they were
+        // invalidated (e.g. by an aspect ratio change).
+        if (mPipResizeGestureHandler.getUserResizeBounds().isEmpty()) {
+            mPipResizeGestureHandler.setUserResizeBounds(normalBounds);
+        }
+
         final int bottomOffset = mIsImeShowing ? mImeHeight : 0;
         final boolean fromDisplayRotationChanged = (mDisplayRotation != displayRotation);
         if (fromDisplayRotationChanged) {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
index f1c8b0c..74dc003 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
@@ -49,6 +49,7 @@
 import com.android.systemui.R;
 import com.android.systemui.UiOffloadThread;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.pip.BasePipManager;
 import com.android.systemui.pip.PipBoundsHandler;
 import com.android.systemui.pip.PipSurfaceTransactionHelper;
@@ -63,12 +64,11 @@
 import java.util.List;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Manages the picture-in-picture (PIP) UI and states.
  */
-@Singleton
+@SysUISingleton
 public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitionCallback {
     private static final String TAG = "PipManager";
     static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginDependencyProvider.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginDependencyProvider.java
index 6949531..ad1e21d 100644
--- a/packages/SystemUI/src/com/android/systemui/plugins/PluginDependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginDependencyProvider.java
@@ -17,15 +17,15 @@
 import android.util.ArrayMap;
 
 import com.android.systemui.Dependency;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.PluginDependency.DependencyProvider;
 import com.android.systemui.shared.plugins.PluginManager;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  */
-@Singleton
+@SysUISingleton
 public class PluginDependencyProvider extends DependencyProvider {
 
     private final ArrayMap<Class<?>, Object> mDependencies = new ArrayMap<>();
diff --git a/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java
index 7d54c21..90da891 100644
--- a/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java
@@ -2,11 +2,11 @@
 
 import com.android.settingslib.fuelgauge.Estimate;
 import com.android.settingslib.fuelgauge.EstimateKt;
+import com.android.systemui.dagger.SysUISingleton;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
-@Singleton
+@SysUISingleton
 public class EnhancedEstimatesImpl implements EnhancedEstimates {
 
     @Inject
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index 6abbbbe..a27e9ac 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -59,6 +59,7 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.util.NotificationChannels;
@@ -70,11 +71,10 @@
 import java.util.Objects;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  */
-@Singleton
+@SysUISingleton
 public class PowerNotificationWarnings implements PowerUI.WarningsUI {
 
     private static final String TAG = PowerUI.TAG + ".Notification";
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index 66804be..a888305 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -46,6 +46,7 @@
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.StatusBar;
 
@@ -56,11 +57,10 @@
 import java.util.concurrent.Future;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 
-@Singleton
+@SysUISingleton
 public class PowerUI extends SystemUI implements CommandQueue.Callbacks {
 
     static final String TAG = "PowerUI";
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
index affc5ee..255ba1b 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.appops.AppOpItem
 import com.android.systemui.appops.AppOpsController
 import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
@@ -41,9 +42,8 @@
 import java.lang.ref.WeakReference
 import java.util.concurrent.Executor
 import javax.inject.Inject
-import javax.inject.Singleton
 
-@Singleton
+@SysUISingleton
 class PrivacyItemController @Inject constructor(
     private val appOpsController: AppOpsController,
     @Main uiExecutor: DelayableExecutor,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 3b16a4e..9a63a56 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -37,6 +37,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
@@ -69,10 +70,9 @@
 
 import javax.inject.Inject;
 import javax.inject.Provider;
-import javax.inject.Singleton;
 
 /** Platform implementation of the quick settings tile host **/
-@Singleton
+@SysUISingleton
 public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, Dumpable {
     private static final String TAG = "QSTileHost";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index 69a6fe1..8e33496 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -20,6 +20,7 @@
 import android.view.ContextThemeWrapper;
 
 import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.qs.QSFactory;
 import com.android.systemui.plugins.qs.QSIconView;
 import com.android.systemui.plugins.qs.QSTile;
@@ -49,11 +50,10 @@
 
 import javax.inject.Inject;
 import javax.inject.Provider;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 
-@Singleton
+@SysUISingleton
 public class QSFactoryImpl implements QSFactory {
 
     private static final String TAG = "QSFactory";
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java
index f89185e..0347867 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java
@@ -35,6 +35,7 @@
 
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.stackdivider.Divider;
@@ -43,14 +44,13 @@
 import java.util.Optional;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 
 /**
  * An implementation of the Recents interface which proxies to the OverviewProxyService.
  */
-@Singleton
+@SysUISingleton
 public class OverviewProxyRecentsImpl implements RecentsImplementation {
 
     private final static String TAG = "OverviewProxyRecentsImpl";
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index d03082e..e931a6b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -61,12 +61,19 @@
 import android.view.Surface;
 import android.view.accessibility.AccessibilityManager;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity;
 import com.android.internal.policy.ScreenDecorationsUtils;
 import com.android.internal.util.ScreenshotHelper;
 import com.android.systemui.Dumpable;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.model.SysUiState;
+import com.android.systemui.navigationbar.NavigationBar;
+import com.android.systemui.navigationbar.NavigationBarController;
+import com.android.systemui.navigationbar.NavigationBarView;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.onehanded.OneHandedUI;
 import com.android.systemui.pip.PipAnimationController;
 import com.android.systemui.pip.PipUI;
@@ -81,11 +88,7 @@
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.NavigationBarController;
-import com.android.systemui.statusbar.phone.NavigationBarFragment;
-import com.android.systemui.statusbar.phone.NavigationBarView;
-import com.android.systemui.statusbar.phone.NavigationModeController;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
 import com.android.systemui.statusbar.policy.CallbackController;
@@ -97,14 +100,13 @@
 import java.util.Optional;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 
 /**
  * Class to send information from overview to launcher with a binder.
  */
-@Singleton
+@SysUISingleton
 public class OverviewProxyService extends CurrentUserTracker implements
         CallbackController<OverviewProxyListener>, NavigationModeController.ModeChangedListener,
         Dumpable {
@@ -124,7 +126,7 @@
     private final Optional<Divider> mDividerOptional;
     private SysUiState mSysUiState;
     private final Handler mHandler;
-    private final NavigationBarController mNavBarController;
+    private final Lazy<NavigationBarController> mNavBarControllerLazy;
     private final NotificationShadeWindowController mStatusBarWinController;
     private final Runnable mConnectionRunnable = this::internalConnectToCurrentUser;
     private final ComponentName mRecentsComponentName;
@@ -598,7 +600,7 @@
     @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
     @Inject
     public OverviewProxyService(Context context, CommandQueue commandQueue,
-            NavigationBarController navBarController, NavigationModeController navModeController,
+            Lazy<NavigationBarController> navBarControllerLazy, NavigationModeController navModeController,
             NotificationShadeWindowController statusBarWinController, SysUiState sysUiState,
             PipUI pipUI, Optional<Divider> dividerOptional,
             Optional<Lazy<StatusBar>> statusBarOptionalLazy, OneHandedUI oneHandedUI,
@@ -608,7 +610,7 @@
         mPipUI = pipUI;
         mStatusBarOptionalLazy = statusBarOptionalLazy;
         mHandler = new Handler();
-        mNavBarController = navBarController;
+        mNavBarControllerLazy = navBarControllerLazy;
         mStatusBarWinController = statusBarWinController;
         mConnectionBackoffAttempts = 0;
         mDividerOptional = dividerOptional;
@@ -677,10 +679,10 @@
     }
 
     private void updateSystemUiStateFlags() {
-        final NavigationBarFragment navBarFragment =
-                mNavBarController.getDefaultNavigationBarFragment();
+        final NavigationBar navBarFragment =
+                mNavBarControllerLazy.get().getDefaultNavigationBar();
         final NavigationBarView navBarView =
-                mNavBarController.getNavigationBarView(mContext.getDisplayId());
+                mNavBarControllerLazy.get().getNavigationBarView(mContext.getDisplayId());
         if (SysUiState.DEBUG) {
             Log.d(TAG_OPS, "Updating sysui state flags: navBarFragment=" + navBarFragment
                     + " navBarView=" + navBarView);
@@ -808,7 +810,7 @@
     }
 
     @Override
-    public void addCallback(OverviewProxyListener listener) {
+    public void addCallback(@NonNull OverviewProxyListener listener) {
         if (!mConnectionCallbacks.contains(listener)) {
             mConnectionCallbacks.add(listener);
         }
@@ -817,7 +819,7 @@
     }
 
     @Override
-    public void removeCallback(OverviewProxyListener listener) {
+    public void removeCallback(@NonNull OverviewProxyListener listener) {
         mConnectionCallbacks.remove(listener);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
index 3b3d9dd..9c5a3de 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
@@ -466,23 +466,22 @@
     }
 
     public void dump(PrintWriter pw) {
-        pw.println("RecentsOnboarding {");
-        pw.println("      mTaskListenerRegistered: " + mTaskListenerRegistered);
-        pw.println("      mOverviewProxyListenerRegistered: " + mOverviewProxyListenerRegistered);
-        pw.println("      mLayoutAttachedToWindow: " + mLayoutAttachedToWindow);
-        pw.println("      mHasDismissedSwipeUpTip: " + mHasDismissedSwipeUpTip);
-        pw.println("      mHasDismissedQuickScrubTip: " + mHasDismissedQuickScrubTip);
-        pw.println("      mNumAppsLaunchedSinceSwipeUpTipDismiss: "
+        pw.println("RecentsOnboarding");
+        pw.println("  mTaskListenerRegistered: " + mTaskListenerRegistered);
+        pw.println("  mOverviewProxyListenerRegistered: " + mOverviewProxyListenerRegistered);
+        pw.println("  mLayoutAttachedToWindow: " + mLayoutAttachedToWindow);
+        pw.println("  mHasDismissedSwipeUpTip: " + mHasDismissedSwipeUpTip);
+        pw.println("  mHasDismissedQuickScrubTip: " + mHasDismissedQuickScrubTip);
+        pw.println("  mNumAppsLaunchedSinceSwipeUpTipDismiss: "
                 + mNumAppsLaunchedSinceSwipeUpTipDismiss);
-        pw.println("      hasSeenSwipeUpOnboarding: " + hasSeenSwipeUpOnboarding());
-        pw.println("      hasSeenQuickScrubOnboarding: " + hasSeenQuickScrubOnboarding());
-        pw.println("      getDismissedSwipeUpOnboardingCount: "
+        pw.println("  hasSeenSwipeUpOnboarding: " + hasSeenSwipeUpOnboarding());
+        pw.println("  hasSeenQuickScrubOnboarding: " + hasSeenQuickScrubOnboarding());
+        pw.println("  getDismissedSwipeUpOnboardingCount: "
                 + getDismissedSwipeUpOnboardingCount());
-        pw.println("      hasDismissedQuickScrubOnboardingOnce: "
+        pw.println("  hasDismissedQuickScrubOnboardingOnce: "
                 + hasDismissedQuickScrubOnboardingOnce());
-        pw.println("      getOpenedOverviewCount: " + getOpenedOverviewCount());
-        pw.println("      getOpenedOverviewFromHomeCount: " + getOpenedOverviewFromHomeCount());
-        pw.println("    }");
+        pw.println("  getOpenedOverviewCount: " + getOpenedOverviewCount());
+        pw.println("  getOpenedOverviewFromHomeCount: " + getOpenedOverviewFromHomeCount());
     }
 
     private WindowManager.LayoutParams getWindowLayoutParams(int gravity, int x) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
index 3874903..6afc756 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
@@ -51,8 +51,8 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.WindowManagerWrapper;
-import com.android.systemui.statusbar.phone.NavigationBarView;
-import com.android.systemui.statusbar.phone.NavigationModeController;
+import com.android.systemui.navigationbar.NavigationBarView;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.util.leak.RotationUtils;
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index 82ac1f6..10a44dd 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -26,19 +26,21 @@
 import android.os.UserHandle;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.policy.CallbackController;
 
 import java.util.ArrayList;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Helper class to initiate a screen recording
  */
-@Singleton
+@SysUISingleton
 public class RecordingController
         implements CallbackController<RecordingController.RecordingStateChangeCallback> {
     private static final String TAG = "RecordingController";
@@ -191,12 +193,12 @@
     }
 
     @Override
-    public void addCallback(RecordingStateChangeCallback listener) {
+    public void addCallback(@NonNull RecordingStateChangeCallback listener) {
         mListeners.add(listener);
     }
 
     @Override
-    public void removeCallback(RecordingStateChangeCallback listener) {
+    public void removeCallback(@NonNull RecordingStateChangeCallback listener) {
         mListeners.remove(listener);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 6747281..e24fbc6 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -81,6 +81,7 @@
 
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.shared.system.QuickStepContract;
 
@@ -89,12 +90,11 @@
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Class for handling device screen shots
  */
-@Singleton
+@SysUISingleton
 public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInsetsListener {
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java
index b5209bb..a488702 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java
@@ -65,10 +65,10 @@
     }
 
     void setIcon(Icon icon, boolean tint) {
-        if (tint) {
-            icon.setTint(mIconColor);
-        }
         mIcon.setImageIcon(icon);
+        if (!tint) {
+            mIcon.setImageTintList(null);
+        }
     }
 
     void setText(CharSequence text) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java
index 633cdd6..468602a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java
@@ -31,6 +31,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.SystemUIFactory;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 
 import java.util.Collections;
@@ -40,12 +41,11 @@
 import java.util.concurrent.TimeoutException;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Collects the static functions for retrieving and acting on smart actions.
  */
-@Singleton
+@SysUISingleton
 public class ScreenshotSmartActions {
     private static final String TAG = "ScreenshotSmartActions";
 
diff --git a/packages/SystemUI/src/com/android/systemui/settings/dagger/SettingsModule.java b/packages/SystemUI/src/com/android/systemui/settings/dagger/SettingsModule.java
index eb5bd5c..b1ed772 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/dagger/SettingsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/dagger/SettingsModule.java
@@ -19,11 +19,10 @@
 import android.content.Context;
 
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.settings.CurrentUserContentResolverProvider;
 import com.android.systemui.settings.CurrentUserContextTracker;
 
-import javax.inject.Singleton;
-
 import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
@@ -37,7 +36,7 @@
     /**
      * Provides and initializes a CurrentUserContextTracker
      */
-    @Singleton
+    @SysUISingleton
     @Provides
     static CurrentUserContextTracker provideCurrentUserContextTracker(
             Context context,
@@ -49,7 +48,7 @@
     }
 
     @Binds
-    @Singleton
+    @SysUISingleton
     abstract CurrentUserContentResolverProvider bindCurrentUserContentResolverTracker(
             CurrentUserContextTracker tracker);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
index f7f1223..ee3303b 100644
--- a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
@@ -28,17 +28,17 @@
 
 import com.android.internal.policy.DividerSnapAlgorithm;
 import com.android.systemui.SystemUI;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.stackdivider.DividerView;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Dispatches shortcut to System UI components
  */
-@Singleton
+@SysUISingleton
 public class ShortcutKeyDispatcher extends SystemUI
         implements ShortcutKeyServiceProxy.Callbacks {
 
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
index d40b666..e9c880e 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
@@ -23,6 +23,7 @@
 import android.window.WindowContainerToken;
 
 import com.android.systemui.SystemUI;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -31,12 +32,10 @@
 import java.io.PrintWriter;
 import java.util.function.Consumer;
 
-import javax.inject.Singleton;
-
 /**
  * Controls the docked stack divider.
  */
-@Singleton
+@SysUISingleton
 public class Divider extends SystemUI {
     private final KeyguardStateController mKeyguardStateController;
     private final DividerController mDividerController;
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerModule.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerModule.java
index db0aef8..9c21397 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerModule.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerModule.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.os.Handler;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.wm.shell.common.DisplayController;
@@ -26,8 +27,6 @@
 import com.android.wm.shell.common.SystemWindows;
 import com.android.wm.shell.common.TransactionPool;
 
-import javax.inject.Singleton;
-
 import dagger.Module;
 import dagger.Provides;
 
@@ -36,7 +35,7 @@
  */
 @Module
 public class DividerModule {
-    @Singleton
+    @SysUISingleton
     @Provides
     static Divider provideDivider(Context context, DisplayController displayController,
             SystemWindows systemWindows, DisplayImeController imeController, @Main Handler handler,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
index c70d3847..f758db8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
@@ -26,14 +26,14 @@
 import com.android.internal.util.IndentingPrintWriter
 import com.android.systemui.Dumpable
 import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
 import java.io.FileDescriptor
 import java.io.PrintWriter
 import javax.inject.Inject
-import javax.inject.Singleton
 
-@Singleton
+@SysUISingleton
 open class BlurUtils @Inject constructor(
     @Main private val resources: Resources,
     dumpManager: DumpManager
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 1638dd9..4673ec7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -49,6 +49,8 @@
 import android.view.InsetsState.InternalInsetsType;
 import android.view.WindowInsetsController.Appearance;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.os.SomeArgs;
 import com.android.internal.statusbar.IStatusBar;
 import com.android.internal.statusbar.StatusBarIcon;
@@ -391,7 +393,8 @@
                 && !ONLY_CORE_APPS;
     }
 
-    public void addCallback(Callbacks callbacks) {
+    @Override
+    public void addCallback(@NonNull Callbacks callbacks) {
         mCallbacks.add(callbacks);
         // TODO(b/117478341): find a better way to pass disable flags by display.
         for (int i = 0; i < mDisplayDisabled.size(); i++) {
@@ -402,7 +405,8 @@
         }
     }
 
-    public void removeCallback(Callbacks callbacks) {
+    @Override
+    public void removeCallback(@NonNull Callbacks callbacks) {
         mCallbacks.remove(callbacks);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
index 6839921..3811ca9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
@@ -20,13 +20,13 @@
 import android.provider.DeviceConfig;
 import android.util.ArrayMap;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 
 import java.util.Map;
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Class to manage simple DeviceConfig-based feature flags.
@@ -43,7 +43,7 @@
  *  $ adb shell am restart com.android.systemui
  * }
  */
-@Singleton
+@SysUISingleton
 public class FeatureFlags {
     private final Map<String, Boolean> mCachedDeviceConfigFlags = new ArrayMap<>();
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 7e1dc66..a59ff38 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -55,6 +55,7 @@
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
@@ -71,12 +72,11 @@
 import java.util.IllegalFormatConversionException;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Controls the indications and error messages shown on the Keyguard
  */
-@Singleton
+@SysUISingleton
 public class KeyguardIndicationController implements StateListener,
         KeyguardStateController.Callback {
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt
index 326757e..750272d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt
@@ -28,17 +28,16 @@
 import android.util.Log
 import android.util.MathUtils
 import com.android.internal.graphics.ColorUtils
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.notification.MediaNotificationProcessor
-
 import javax.inject.Inject
-import javax.inject.Singleton
 
 private const val TAG = "MediaArtworkProcessor"
 private const val COLOR_ALPHA = (255 * 0.7f).toInt()
 private const val BLUR_RADIUS = 25f
 private const val DOWNSAMPLE = 6
 
-@Singleton
+@SysUISingleton
 class MediaArtworkProcessor @Inject constructor() {
 
     private val mTmpSize = Point()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
deleted file mode 100644
index 2638d28..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
+++ /dev/null
@@ -1,248 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar;
-
-import static android.view.Display.DEFAULT_DISPLAY;
-
-import android.content.Context;
-import android.hardware.display.DisplayManager;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.util.Log;
-import android.util.SparseArray;
-import android.view.Display;
-import android.view.IWindowManager;
-import android.view.View;
-import android.view.WindowManagerGlobal;
-
-import androidx.annotation.Nullable;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.statusbar.RegisterStatusBarResult;
-import com.android.systemui.Dependency;
-import com.android.systemui.assist.AssistHandleViewController;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.fragments.FragmentHostManager;
-import com.android.systemui.plugins.DarkIconDispatcher;
-import com.android.systemui.statusbar.CommandQueue.Callbacks;
-import com.android.systemui.statusbar.phone.AutoHideController;
-import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
-import com.android.systemui.statusbar.phone.LightBarController;
-import com.android.systemui.statusbar.phone.NavigationBarFragment;
-import com.android.systemui.statusbar.phone.NavigationBarView;
-import com.android.systemui.statusbar.phone.NavigationModeController;
-import com.android.systemui.statusbar.policy.BatteryController;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-
-/** A controller to handle navigation bars. */
-@Singleton
-public class NavigationBarController implements Callbacks {
-
-    private static final String TAG = NavigationBarController.class.getSimpleName();
-
-    private final Context mContext;
-    private final Handler mHandler;
-    private final DisplayManager mDisplayManager;
-
-    /** A displayId - nav bar maps. */
-    @VisibleForTesting
-    SparseArray<NavigationBarFragment> mNavigationBars = new SparseArray<>();
-
-    @Inject
-    public NavigationBarController(Context context, @Main Handler handler,
-            CommandQueue commandQueue) {
-        mContext = context;
-        mHandler = handler;
-        mDisplayManager = (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
-        commandQueue.addCallback(this);
-    }
-
-    @Override
-    public void onDisplayRemoved(int displayId) {
-        removeNavigationBar(displayId);
-    }
-
-    @Override
-    public void onDisplayReady(int displayId) {
-        Display display = mDisplayManager.getDisplay(displayId);
-        createNavigationBar(display, null);
-    }
-
-    // TODO(b/117478341): I use {@code includeDefaultDisplay} to make this method compatible to
-    // CarStatusBar because they have their own nav bar. Think about a better way for it.
-    /**
-     * Creates navigation bars when car/status bar initializes.
-     *
-     * @param includeDefaultDisplay {@code true} to create navigation bar on default display.
-     */
-    public void createNavigationBars(final boolean includeDefaultDisplay,
-            RegisterStatusBarResult result) {
-        Display[] displays = mDisplayManager.getDisplays();
-        for (Display display : displays) {
-            if (includeDefaultDisplay || display.getDisplayId() != DEFAULT_DISPLAY) {
-                createNavigationBar(display, result);
-            }
-        }
-    }
-
-    /**
-     * Adds a navigation bar on default display or an external display if the display supports
-     * system decorations.
-     *
-     * @param display the display to add navigation bar on.
-     */
-    @VisibleForTesting
-    void createNavigationBar(Display display, RegisterStatusBarResult result) {
-        if (display == null) {
-            return;
-        }
-
-        final int displayId = display.getDisplayId();
-        final boolean isOnDefaultDisplay = displayId == DEFAULT_DISPLAY;
-        final IWindowManager wms = WindowManagerGlobal.getWindowManagerService();
-
-        try {
-            if (!wms.hasNavigationBar(displayId)) {
-                return;
-            }
-        } catch (RemoteException e) {
-            // Cannot get wms, just return with warning message.
-            Log.w(TAG, "Cannot get WindowManager.");
-            return;
-        }
-        final Context context = isOnDefaultDisplay
-                ? mContext
-                : mContext.createDisplayContext(display);
-        NavigationBarFragment.create(context, (tag, fragment) -> {
-            NavigationBarFragment navBar = (NavigationBarFragment) fragment;
-
-            // Unfortunately, we still need it because status bar needs LightBarController
-            // before notifications creation. We cannot directly use getLightBarController()
-            // from NavigationBarFragment directly.
-            LightBarController lightBarController = isOnDefaultDisplay
-                    ? Dependency.get(LightBarController.class)
-                    : new LightBarController(context,
-                            Dependency.get(DarkIconDispatcher.class),
-                            Dependency.get(BatteryController.class),
-                            Dependency.get(NavigationModeController.class));
-            navBar.setLightBarController(lightBarController);
-
-            // TODO(b/118592525): to support multi-display, we start to add something which is
-            //                    per-display, while others may be global. I think it's time to add
-            //                    a new class maybe named DisplayDependency to solve per-display
-            //                    Dependency problem.
-            AutoHideController autoHideController = isOnDefaultDisplay
-                    ? Dependency.get(AutoHideController.class)
-                    : new AutoHideController(context, mHandler,
-                            Dependency.get(IWindowManager.class));
-            navBar.setAutoHideController(autoHideController);
-            navBar.restoreAppearanceAndTransientState();
-            mNavigationBars.put(displayId, navBar);
-
-            if (result != null) {
-                navBar.setImeWindowStatus(display.getDisplayId(), result.mImeToken,
-                        result.mImeWindowVis, result.mImeBackDisposition,
-                        result.mShowImeSwitcher);
-            }
-        });
-    }
-
-    private void removeNavigationBar(int displayId) {
-        NavigationBarFragment navBar = mNavigationBars.get(displayId);
-        if (navBar != null) {
-            navBar.setAutoHideController(/* autoHideController */ null);
-            View navigationWindow = navBar.getView().getRootView();
-            WindowManagerGlobal.getInstance()
-                    .removeView(navigationWindow, true /* immediate */);
-            // Also remove FragmentHostState here in case that onViewDetachedFromWindow has not yet
-            // invoked after display removal.
-            FragmentHostManager.removeAndDestroy(navigationWindow);
-            mNavigationBars.remove(displayId);
-        }
-    }
-
-    /** @see NavigationBarFragment#checkNavBarModes() */
-    public void checkNavBarModes(int displayId) {
-        NavigationBarFragment navBar = mNavigationBars.get(displayId);
-        if (navBar != null) {
-            navBar.checkNavBarModes();
-        }
-    }
-
-    /** @see NavigationBarFragment#finishBarAnimations() */
-    public void finishBarAnimations(int displayId) {
-        NavigationBarFragment navBar = mNavigationBars.get(displayId);
-        if (navBar != null) {
-            navBar.finishBarAnimations();
-        }
-    }
-
-    /** @see NavigationBarFragment#touchAutoDim() */
-    public void touchAutoDim(int displayId) {
-        NavigationBarFragment navBar = mNavigationBars.get(displayId);
-        if (navBar != null) {
-            navBar.touchAutoDim();
-        }
-    }
-
-    /** @see NavigationBarFragment#transitionTo(int, boolean) */
-    public void transitionTo(int displayId, @TransitionMode int barMode, boolean animate) {
-        NavigationBarFragment navBar = mNavigationBars.get(displayId);
-        if (navBar != null) {
-            navBar.transitionTo(barMode, animate);
-        }
-    }
-
-    /** @see NavigationBarFragment#disableAnimationsDuringHide(long) */
-    public void disableAnimationsDuringHide(int displayId, long delay) {
-        NavigationBarFragment navBar = mNavigationBars.get(displayId);
-        if (navBar != null) {
-            navBar.disableAnimationsDuringHide(delay);
-        }
-    }
-
-    /** @return {@link NavigationBarView} on the default display. */
-    public @Nullable NavigationBarView getDefaultNavigationBarView() {
-        return getNavigationBarView(DEFAULT_DISPLAY);
-    }
-
-    /**
-     * @param displayId the ID of display which Navigation bar is on
-     * @return {@link NavigationBarView} on the display with {@code displayId}.
-     *         {@code null} if no navigation bar on that display.
-     */
-    public @Nullable NavigationBarView getNavigationBarView(int displayId) {
-        NavigationBarFragment navBar = mNavigationBars.get(displayId);
-        return (navBar == null) ? null : (NavigationBarView) navBar.getView();
-    }
-
-    /** @return {@link NavigationBarFragment} on the default display. */
-    @Nullable
-    public NavigationBarFragment getDefaultNavigationBarFragment() {
-        return mNavigationBars.get(DEFAULT_DISPLAY);
-    }
-
-    /** @return {@link AssistHandleViewController} (only on the default display). */
-    @Nullable
-    public AssistHandleViewController getAssistHandlerViewController() {
-        NavigationBarFragment navBar = getDefaultNavigationBarFragment();
-        return navBar == null ? null : navBar.getAssistHandlerViewController();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationClickNotifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationClickNotifier.kt
index 8248fc9..abf81c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationClickNotifier.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationClickNotifier.kt
@@ -4,11 +4,11 @@
 import android.os.RemoteException
 import com.android.internal.statusbar.IStatusBarService
 import com.android.internal.statusbar.NotificationVisibility
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.util.Assert
 import java.util.concurrent.Executor
 import javax.inject.Inject
-import javax.inject.Singleton
 
 /**
  * Class to shim calls to IStatusBarManager#onNotificationClick/#onNotificationActionClick that
@@ -18,7 +18,7 @@
  * NOTE: this class eats exceptions from system server, as no current client of these APIs cares
  * about errors
  */
-@Singleton
+@SysUISingleton
 public class NotificationClickNotifier @Inject constructor(
     val barService: IStatusBarService,
     @Main val mainExecutor: Executor
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInteractionTracker.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInteractionTracker.kt
index 9dbec10..2ca1beb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInteractionTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInteractionTracker.kt
@@ -1,16 +1,16 @@
 package com.android.systemui.statusbar
 
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.notification.NotificationEntryManager
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
 import javax.inject.Inject
-import javax.inject.Singleton
 
 /**
  * Class to track user interaction with notifications. It's a glorified map of key : bool that can
  * merge multiple "user interacted with notification" signals into a single place.
  */
-@Singleton
+@SysUISingleton
 class NotificationInteractionTracker @Inject constructor(
     private val clicker: NotificationClickNotifier,
     private val entryManager: NotificationEntryManager
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 03424c4..8d82270 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -48,6 +48,7 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
@@ -65,13 +66,12 @@
 import java.util.List;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Handles keeping track of the current user, profiles, and various things related to hiding
  * contents, redacting notifications, and the lockscreen.
  */
-@Singleton
+@SysUISingleton
 public class NotificationLockscreenUserManagerImpl implements
         Dumpable, NotificationLockscreenUserManager, StateListener {
     private static final String TAG = "LockscreenUserManager";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 739d30c..c01bdc4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -60,7 +60,6 @@
 import com.android.systemui.statusbar.phone.BiometricUnlockController;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.LockscreenWallpaper;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.ScrimState;
 import com.android.systemui.statusbar.phone.StatusBar;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index 0445c98..c1196d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -32,27 +32,26 @@
 import com.android.internal.util.IndentingPrintWriter
 import com.android.systemui.Dumpable
 import com.android.systemui.Interpolators
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.notification.ActivityLaunchAnimator
 import com.android.systemui.statusbar.phone.BiometricUnlockController
 import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK
 import com.android.systemui.statusbar.phone.DozeParameters
-import com.android.systemui.statusbar.phone.NotificationShadeWindowController
 import com.android.systemui.statusbar.phone.PanelExpansionListener
 import com.android.systemui.statusbar.phone.ScrimController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import java.io.FileDescriptor
 import java.io.PrintWriter
 import javax.inject.Inject
-import javax.inject.Singleton
 import kotlin.math.max
 import kotlin.math.sign
 
 /**
  * Controller responsible for statusbar window blur.
  */
-@Singleton
+@SysUISingleton
 class NotificationShadeDepthController @Inject constructor(
     private val statusBarStateController: StatusBarStateController,
     private val blurUtils: BlurUtils,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
new file mode 100644
index 0000000..1fd0b03
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
@@ -0,0 +1,184 @@
+/*
+ * 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;
+
+import android.view.ViewGroup;
+
+import androidx.annotation.Nullable;
+
+import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
+
+import java.util.function.Consumer;
+
+/**
+ * Interface to control the state of the notification shade window. Not all methods of this
+ * interface will be used by each implementation of {@link NotificationShadeWindowController}.
+ */
+public interface NotificationShadeWindowController extends RemoteInputController.Callback {
+
+    /**
+     * Registers a {@link StatusBarWindowCallback} to receive notifications about status bar
+     * window state changes.
+     */
+    default void registerCallback(StatusBarWindowCallback callback) {}
+
+    /** Notifies the registered {@link StatusBarWindowCallback} instances. */
+    default void notifyStateChangedCallbacks() {}
+
+    /**
+     * Registers a listener to monitor scrims visibility.
+     *
+     * @param listener A listener to monitor scrims visibility
+     */
+    default void setScrimsVisibilityListener(Consumer<Integer> listener) {}
+
+    /**
+     * Adds the notification shade view to the window manager.
+     */
+    default void attach() {}
+
+    /** Sets the notification shade view. */
+    default void setNotificationShadeView(ViewGroup view) {}
+
+    /** Gets the notification shade view. */
+    @Nullable
+    default ViewGroup getNotificationShadeView() {
+        return null;
+    }
+
+    /** Sets the state of whether the keyguard is currently showing or not. */
+    default void setKeyguardShowing(boolean showing) {}
+
+    /** Sets the state of whether the keyguard is currently occluded or not. */
+    default void setKeyguardOccluded(boolean occluded) {}
+
+    /** Sets the state of whether the keyguard is currently needs input or not. */
+    default void setKeyguardNeedsInput(boolean needsInput) {}
+
+    /** Sets the state of whether the notification shade panel is currently visible or not. */
+    default void setPanelVisible(boolean visible) {}
+
+    /** Sets the state of whether the notification shade is focusable or not. */
+    default void setNotificationShadeFocusable(boolean focusable) {}
+
+    /** Sets the state of whether the bouncer is showing or not. */
+    default void setBouncerShowing(boolean showing) {}
+
+    /** Sets the state of whether the backdrop is showing or not. */
+    default void setBackdropShowing(boolean showing) {}
+
+    /** Sets the state of whether the keyguard is fading away or not. */
+    default void setKeyguardFadingAway(boolean keyguardFadingAway) {}
+
+    /** Sets the state of whether the quick settings is expanded or not. */
+    default void setQsExpanded(boolean expanded) {}
+
+    /** Sets the state of whether the user activities are forced or not. */
+    default void setForceUserActivity(boolean forceUserActivity) {}
+
+    /** Sets the state of whether the user activities are forced or not. */
+    default void setLaunchingActivity(boolean launching) {}
+
+    /** Sets the state of whether the scrim is visible or not. */
+    default void setScrimsVisibility(int scrimsVisibility) {}
+
+    /** Sets the background blur radius of the notification shade window. */
+    default void setBackgroundBlurRadius(int backgroundBlurRadius) {}
+
+    /** Sets the state of whether heads up is showing or not. */
+    default void setHeadsUpShowing(boolean showing) {}
+
+    /** Sets whether the wallpaper supports ambient mode or not. */
+    default void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) {}
+
+    /** Gets whether the wallpaper is showing or not. */
+    default boolean isShowingWallpaper() {
+        return false;
+    }
+
+    /** Sets whether the window was collapsed by force or not. */
+    default void setForceWindowCollapsed(boolean force) {}
+
+    /** Sets whether panel is expanded or not. */
+    default void setPanelExpanded(boolean isExpanded) {}
+
+    /** Gets whether the panel is expanded or not. */
+    default boolean getPanelExpanded() {
+        return false;
+    }
+
+    /** Sets the state of whether the remote input is active or not. */
+    default void onRemoteInputActive(boolean remoteInputActive) {}
+
+    /** Sets the screen brightness level for when the device is dozing. */
+    default void setDozeScreenBrightness(int value) {}
+
+    /**
+     * Sets whether the screen brightness is forced to the value we use for doze mode by the status
+     * bar window. No-op if the device does not support dozing.
+     */
+    default void setForceDozeBrightness(boolean forceDozeBrightness) {}
+
+    /** Sets the state of whether sysui is dozing or not. */
+    default void setDozing(boolean dozing) {}
+
+    /** Sets the state of whether plugin open is forced or not. */
+    default void setForcePluginOpen(boolean forcePluginOpen) {}
+
+    /** Gets whether we are forcing plugin open or not. */
+    default boolean getForcePluginOpen() {
+        return false;
+    }
+
+    /** Sets the state of whether the notification shade is touchable or not. */
+    default void setNotTouchable(boolean notTouchable) {}
+
+    /** Sets a {@link OtherwisedCollapsedListener}. */
+    default void setStateListener(OtherwisedCollapsedListener listener) {}
+
+    /** Sets a {@link ForcePluginOpenListener}. */
+    default void setForcePluginOpenListener(ForcePluginOpenListener listener) {}
+
+    /** Sets whether the system is in a state where the keyguard is going away. */
+    default void setKeyguardGoingAway(boolean goingAway) {}
+
+    /**
+     * SystemUI may need top-ui to avoid jank when performing animations.  After the
+     * animation is performed, the component should remove itself from the list of features that
+     * are forcing SystemUI to be top-ui.
+     */
+    default void setRequestTopUi(boolean requestTopUi, String componentTag) {}
+
+    /**
+     * Custom listener to pipe data back to plugins about whether or not the status bar would be
+     * collapsed if not for the plugin.
+     * TODO: Find cleaner way to do this.
+     */
+    interface OtherwisedCollapsedListener {
+        void setWouldOtherwiseCollapse(boolean otherwiseCollapse);
+    }
+
+    /**
+     * Listener to indicate forcePluginOpen has changed
+     */
+    interface ForcePluginOpenListener {
+        /**
+         * Called when mState.forcePluginOpen is changed
+         */
+        void onChange(boolean forceOpen);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index 02a8fee..1cd1b60 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -21,7 +21,6 @@
 import android.os.Handler;
 import android.os.Trace;
 import android.os.UserHandle;
-import android.service.notification.NotificationListenerService.Ranking;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
@@ -35,9 +34,9 @@
 import com.android.systemui.statusbar.notification.DynamicChildBindController;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.inflation.LowPriorityInflationHelper;
+import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.stack.ForegroundServiceSectionController;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -491,7 +490,6 @@
                 }
             }
 
-            row.showAppOpsIcons(entry.mActiveAppOps);
             row.showFeedbackIcon(mAssistantFeedbackController.showFeedbackIndicator(entry));
             row.setLastAudiblyAlertedMs(entry.getLastAudiblyAlertedMs());
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
index 0a7ee3b..2aba103 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
@@ -28,8 +28,8 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.settingslib.WirelessUtils;
-import com.android.systemui.DemoMode;
 import com.android.systemui.Dependency;
+import com.android.systemui.demomode.DemoModeCommandReceiver;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
 import com.android.systemui.statusbar.policy.NetworkController;
@@ -40,7 +40,8 @@
 
 import java.util.List;
 
-public class OperatorNameView extends TextView implements DemoMode, DarkReceiver,
+/** Shows the operator name */
+public class OperatorNameView extends TextView implements DemoModeCommandReceiver, DarkReceiver,
         SignalCallback, Tunable {
 
     private static final String KEY_SHOW_OPERATOR_NAME = "show_operator_name";
@@ -103,14 +104,18 @@
 
     @Override
     public void dispatchDemoCommand(String command, Bundle args) {
-        if (!mDemoMode && command.equals(COMMAND_ENTER)) {
-            mDemoMode = true;
-        } else if (mDemoMode && command.equals(COMMAND_EXIT)) {
-            mDemoMode = false;
-            update();
-        } else if (mDemoMode && command.equals(COMMAND_OPERATOR)) {
-            setText(args.getString("name"));
-        }
+        setText(args.getString("name"));
+    }
+
+    @Override
+    public void onDemoModeStarted() {
+        mDemoMode = true;
+    }
+
+    @Override
+    public void onDemoModeFinished() {
+        mDemoMode = false;
+        update();
     }
 
     private void update() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
index ff13c4e..ba54d1b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.Gefingerpoken
 import com.android.systemui.Interpolators
 import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
@@ -41,13 +42,12 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.phone.ShadeController
 import javax.inject.Inject
-import javax.inject.Singleton
 import kotlin.math.max
 
 /**
  * A utility class to enable the downward swipe on when pulsing.
  */
-@Singleton
+@SysUISingleton
 class PulseExpansionHandler @Inject
 constructor(
     context: Context,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index 2bef355..e944249 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -23,11 +23,14 @@
 import android.util.Log;
 import android.view.animation.Interpolator;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.Dumpable;
 import com.android.systemui.Interpolators;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 import com.android.systemui.statusbar.policy.CallbackController;
@@ -38,12 +41,11 @@
 import java.util.Comparator;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Tracks and reports on {@link StatusBarState}.
  */
-@Singleton
+@SysUISingleton
 public class StatusBarStateControllerImpl implements SysuiStatusBarStateController,
         CallbackController<StateListener>, Dumpable {
     private static final String TAG = "SbStateController";
@@ -101,6 +103,11 @@
     private boolean mIsDozing;
 
     /**
+     * If the status bar is currently expanded or not.
+     */
+    private boolean mIsExpanded;
+
+    /**
      * Current {@link #mDozeAmount} animator.
      */
     private ValueAnimator mDarkAnimator;
@@ -188,6 +195,26 @@
     }
 
     @Override
+    public boolean isExpanded() {
+        return mIsExpanded;
+    }
+
+    @Override
+    public boolean setPanelExpanded(boolean expanded) {
+        if (mIsExpanded == expanded) {
+            return false;
+        }
+        mIsExpanded = expanded;
+        String tag = getClass().getSimpleName() + "#setIsExpanded";
+        DejankUtils.startDetectingBlockingIpcs(tag);
+        for (RankedListener rl : new ArrayList<>(mListeners)) {
+            rl.mListener.onExpandedChanged(mIsExpanded);
+        }
+        DejankUtils.stopDetectingBlockingIpcs(tag);
+        return true;
+    }
+
+    @Override
     public float getInterpolatedDozeAmount() {
         return mDozeInterpolator.getInterpolation(mDozeAmount);
     }
@@ -276,7 +303,7 @@
     }
 
     @Override
-    public void addCallback(StateListener listener) {
+    public void addCallback(@NonNull StateListener listener) {
         synchronized (mListeners) {
             addListenerInternalLocked(listener, Integer.MAX_VALUE);
         }
@@ -316,7 +343,7 @@
 
 
     @Override
-    public void removeCallback(StateListener listener) {
+    public void removeCallback(@NonNull StateListener listener) {
         synchronized (mListeners) {
             mListeners.removeIf((it) -> it.mListener.equals(listener));
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SuperStatusBarViewFactory.java b/packages/SystemUI/src/com/android/systemui/statusbar/SuperStatusBarViewFactory.java
index 27e4ade..1ec043c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SuperStatusBarViewFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SuperStatusBarViewFactory.java
@@ -21,6 +21,7 @@
 import android.view.ViewGroup;
 
 import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent;
 import com.android.systemui.statusbar.phone.LockIcon;
 import com.android.systemui.statusbar.phone.LockscreenLockIconController;
@@ -30,13 +31,12 @@
 import com.android.systemui.util.InjectionInflationController;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Creates a single instance of super_status_bar and super_notification_shade that can be shared
  * across various system ui objects.
  */
-@Singleton
+@SysUISingleton
 public class SuperStatusBarViewFactory {
 
     private final Context mContext;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
index 07b3550..9f8fe35 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
@@ -75,6 +75,14 @@
      */
     void setDozeAmount(float dozeAmount, boolean animated);
 
+
+    /**
+     * Update the expanded state from {@link StatusBar}'s perspective
+     * @param expanded are we expanded?
+     * @return {@code true} if the state changed, else {@code false}
+     */
+    boolean setPanelExpanded(boolean expanded);
+
     /**
      * Sets whether to leave status bar open when hiding keyguard
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
index 442416f..ea90bdd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
@@ -26,12 +26,13 @@
 import android.os.Vibrator;
 import android.provider.Settings;
 
+import com.android.systemui.dagger.SysUISingleton;
+
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  */
-@Singleton
+@SysUISingleton
 public class VibratorHelper {
 
     private final Vibrator mVibrator;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
index 992f2da..44550b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
@@ -22,6 +22,7 @@
 
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.media.MediaDataManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -33,26 +34,24 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.NotificationViewHierarchyManager;
 import com.android.systemui.statusbar.SmartReplyController;
 import com.android.systemui.statusbar.notification.AssistantFeedbackController;
 import com.android.systemui.statusbar.notification.DynamicChildBindController;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.inflation.LowPriorityInflationHelper;
+import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.stack.ForegroundServiceSectionController;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.policy.RemoteInputUriController;
 import com.android.systemui.tracing.ProtoTracer;
 import com.android.systemui.util.DeviceConfigProxy;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 
-import javax.inject.Singleton;
-
 import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
@@ -65,7 +64,7 @@
 @Module
 public interface StatusBarDependenciesModule {
     /** */
-    @Singleton
+    @SysUISingleton
     @Provides
     static NotificationRemoteInputManager provideNotificationRemoteInputManager(
             Context context,
@@ -92,7 +91,7 @@
     }
 
     /** */
-    @Singleton
+    @SysUISingleton
     @Provides
     static NotificationMediaManager provideNotificationMediaManager(
             Context context,
@@ -117,7 +116,7 @@
     }
 
     /** */
-    @Singleton
+    @SysUISingleton
     @Provides
     static NotificationListener provideNotificationListener(
             Context context,
@@ -128,7 +127,7 @@
     }
 
     /** */
-    @Singleton
+    @SysUISingleton
     @Provides
     static SmartReplyController provideSmartReplyController(
             NotificationEntryManager entryManager,
@@ -138,7 +137,7 @@
     }
 
     /** */
-    @Singleton
+    @SysUISingleton
     @Provides
     static NotificationViewHierarchyManager provideNotificationViewHierarchyManager(
             Context context,
@@ -176,7 +175,7 @@
      * Provides our instance of CommandQueue which is considered optional.
      */
     @Provides
-    @Singleton
+    @SysUISingleton
     static CommandQueue provideCommandQueue(Context context, ProtoTracer protoTracer) {
         return new CommandQueue(context, protoTracer);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AssistantFeedbackController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AssistantFeedbackController.java
index b813b62..87a3f07 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AssistantFeedbackController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AssistantFeedbackController.java
@@ -30,10 +30,10 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Determines whether to show any indicators or controls related to notification assistant.
@@ -41,7 +41,7 @@
  * Flags protect any changes from being shown. Notifications that are adjusted by the assistant
  * should show an indicator.
  */
-@Singleton
+@SysUISingleton
 public class AssistantFeedbackController extends ContentObserver {
     private final Uri FEEDBACK_URI
             = Settings.Global.getUriFor(Settings.Global.NOTIFICATION_FEEDBACK_ENABLED);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
index 1972b86..c68625c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
@@ -24,6 +24,7 @@
 import android.service.notification.NotificationListenerService.RankingMap
 import com.android.internal.statusbar.NotificationVisibility
 import com.android.internal.widget.ConversationLayout
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
@@ -32,7 +33,6 @@
 import com.android.systemui.statusbar.phone.NotificationGroupManager
 import java.util.concurrent.ConcurrentHashMap
 import javax.inject.Inject
-import javax.inject.Singleton
 
 /** Populates additional information in conversation notifications */
 class ConversationNotificationProcessor @Inject constructor(
@@ -61,7 +61,7 @@
  * Tracks state related to conversation notifications, and updates the UI of existing notifications
  * when necessary.
  */
-@Singleton
+@SysUISingleton
 class ConversationNotificationManager @Inject constructor(
     private val notificationEntryManager: NotificationEntryManager,
     private val notificationGroupManager: NotificationGroupManager,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java
index 2ab329e..9482c17 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java
@@ -16,8 +16,10 @@
 
 package com.android.systemui.statusbar.notification;
 
+import android.annotation.Nullable;
 import android.util.ArraySet;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.StatusBarState;
@@ -25,22 +27,21 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * A controller which dynamically controls the visibility of Notification content
  */
-@Singleton
+@SysUISingleton
 public class DynamicPrivacyController implements KeyguardStateController.Callback {
 
     private final KeyguardStateController mKeyguardStateController;
     private final NotificationLockscreenUserManager mLockscreenUserManager;
     private final StatusBarStateController mStateController;
-    private ArraySet<Listener> mListeners = new ArraySet<>();
+    private final ArraySet<Listener> mListeners = new ArraySet<>();
 
     private boolean mLastDynamicUnlocked;
     private boolean mCacheInvalid;
-    private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+    @Nullable private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
 
     @Inject
     DynamicPrivacyController(NotificationLockscreenUserManager notificationLockscreenUserManager,
@@ -96,8 +97,7 @@
      * contents aren't revealed yet?
      */
     public boolean isInLockedDownShade() {
-        if (!mStatusBarKeyguardViewManager.isShowing()
-                || !mKeyguardStateController.isMethodSecure()) {
+        if (!isStatusBarKeyguardShowing() || !mKeyguardStateController.isMethodSecure()) {
             return false;
         }
         int state = mStateController.getState();
@@ -110,6 +110,10 @@
         return true;
     }
 
+    private boolean isStatusBarKeyguardShowing() {
+        return mStatusBarKeyguardViewManager != null && mStatusBarKeyguardViewManager.isShowing();
+    }
+
     public void setStatusBarKeyguardViewManager(
             StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ForegroundServiceDismissalFeatureController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ForegroundServiceDismissalFeatureController.kt
index dfc2fc1..314051c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ForegroundServiceDismissalFeatureController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ForegroundServiceDismissalFeatureController.kt
@@ -19,9 +19,9 @@
 import android.content.Context
 import android.provider.DeviceConfig
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NOTIFICATIONS_ALLOW_FGS_DISMISSAL
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.util.DeviceConfigProxy
 import javax.inject.Inject
-import javax.inject.Singleton
 
 private var sIsEnabled: Boolean? = null
 
@@ -29,7 +29,7 @@
  * Feature controller for NOTIFICATIONS_ALLOW_FGS_DISMISSAL config.
  */
 // TODO: this is really boilerplatey, make a base class that just wraps the device config
-@Singleton
+@SysUISingleton
 class ForegroundServiceDismissalFeatureController @Inject constructor(
     val proxy: DeviceConfigProxy,
     val context: Context
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
index 312b2c5..ea614fb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
@@ -53,6 +53,7 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.statusbar.CommandQueue;
@@ -63,12 +64,11 @@
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /** The class to show notification(s) of instant apps. This may show multiple notifications on
  * splitted screen.
  */
-@Singleton
+@SysUISingleton
 public class InstantAppNotifier extends SystemUI
         implements CommandQueue.Callbacks, KeyguardStateController.Callback {
     private static final String TAG = "InstantAppNotifier";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index e1ff872..b5f1c7f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -48,6 +48,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
+import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
index 6335a09..590ccf83 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
@@ -29,6 +29,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Dependency;
 import com.android.systemui.ForegroundServiceController;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.media.MediaFeatureFlag;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -37,13 +38,12 @@
 import com.android.systemui.statusbar.phone.ShadeController;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /** Component which manages the various reasons a notification might be filtered out.*/
 // TODO: delete NotificationFilter.java after migrating to new NotifPipeline b/145659174.
 //  Notification filtering is taken care of across the different Coordinators (mostly
 //  KeyguardCoordinator.java)
-@Singleton
+@SysUISingleton
 public class NotificationFilter {
 
     private final NotificationGroupManager mGroupManager = Dependency.get(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
index 0469176..1326d92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -19,6 +19,7 @@
 import android.animation.ObjectAnimator
 import android.util.FloatProperty
 import com.android.systemui.Interpolators
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -26,16 +27,13 @@
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator
 import com.android.systemui.statusbar.phone.DozeParameters
 import com.android.systemui.statusbar.phone.KeyguardBypassController
-import com.android.systemui.statusbar.phone.NotificationIconAreaController
 import com.android.systemui.statusbar.phone.PanelExpansionListener
 import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
-
 import javax.inject.Inject
-import javax.inject.Singleton
 import kotlin.math.min
 
-@Singleton
+@SysUISingleton
 class NotificationWakeUpCoordinator @Inject constructor(
     private val mHeadsUpManager: HeadsUpManager,
     private val statusBarStateController: StatusBarStateController,
@@ -99,7 +97,6 @@
         }
 
     private var collapsedEnoughToHide: Boolean = false
-    lateinit var iconAreaController: NotificationIconAreaController
 
     var pulsing: Boolean = false
         set(value) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
index 57f8a6a..17e6289 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
@@ -46,7 +46,14 @@
     /**
      * The [NotifPromoter] promoting this entry to top-level, if any. Always null for [GroupEntry]s.
      */
-    var promoter: NotifPromoter?
+    var promoter: NotifPromoter?,
+
+    /**
+     * If the [VisualStabilityManager] is suppressing group or section changes for this entry,
+     * suppressedChanges will contain the new parent or section that we would have assigned to
+     * the entry had it not been suppressed by the VisualStabilityManager.
+     */
+    var suppressedChanges: SuppressedAttachState
 ) {
 
     /** Copies the state of another instance. */
@@ -56,6 +63,7 @@
         sectionIndex = other.sectionIndex
         excludingFilter = other.excludingFilter
         promoter = other.promoter
+        suppressedChanges.clone(other.suppressedChanges)
     }
 
     /** Resets back to a "clean" state (the same as created by the factory method) */
@@ -65,6 +73,7 @@
         sectionIndex = -1
         excludingFilter = null
         promoter = null
+        suppressedChanges.reset()
     }
 
     companion object {
@@ -75,7 +84,8 @@
                     null,
                     -1,
                     null,
-                    null)
+                    null,
+                SuppressedAttachState.create())
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
index 3a05201..786c97d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
@@ -167,6 +167,23 @@
                         .append(" ");
             }
 
+            if (notifEntry.getAttachState().getSuppressedChanges().getParent() != null) {
+                rksb.append("suppressedParent=")
+                        .append(notifEntry.getAttachState().getSuppressedChanges()
+                                .getParent().getKey())
+                        .append(" ");
+            }
+
+            if (notifEntry.getAttachState().getSuppressedChanges().getSection() != null) {
+                rksb.append("suppressedSectionIndex=")
+                        .append(notifEntry.getAttachState().getSuppressedChanges()
+                                .getSectionIndex())
+                        .append(" sectionName=")
+                        .append(notifEntry.getAttachState().getSuppressedChanges()
+                                .getSection().getName())
+                        .append(" ");
+            }
+
             if (hasBeenInteractedWith) {
                 rksb.append("interacted=yes ");
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index 285cf7a..90492b5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -59,6 +59,7 @@
 
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.dump.LogBufferEulogizer;
 import com.android.systemui.statusbar.FeatureFlags;
@@ -98,7 +99,6 @@
 import java.util.concurrent.TimeUnit;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Keeps a record of all of the "active" notifications, i.e. the notifications that are currently
@@ -123,7 +123,7 @@
  * events occur.
  */
 @MainThread
-@Singleton
+@SysUISingleton
 public class NotifCollection implements Dumpable {
     private final IStatusBarService mStatusBarService;
     private final SystemClock mClock;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
index aaf5c4d..8562a2e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.notification.collection;
 
 import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.notification.InflationException;
 import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
@@ -24,14 +25,13 @@
 import com.android.systemui.statusbar.notification.row.NotificationContentInflater;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Handles notification inflating, rebinding, and inflation aborting.
  *
  * Currently a wrapper for NotificationRowBinderImpl.
  */
-@Singleton
+@SysUISingleton
 public class NotifInflaterImpl implements NotifInflater {
 
     private final IStatusBarService mStatusBarService;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
index 17899e9..05dd4df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.collection;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener;
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener;
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener;
@@ -24,6 +25,7 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
@@ -33,7 +35,6 @@
 import java.util.List;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * The system that constructs the "shade list", the filtered, grouped, and sorted list of
@@ -68,7 +69,7 @@
  *  9. OnBeforeRenderListListeners are fired ({@link #addOnBeforeRenderListListener})
  *  9. The list is handed off to the view layer to be rendered
  */
-@Singleton
+@SysUISingleton
 public class NotifPipeline implements CommonNotifCollection {
     private final NotifCollection mNotifCollection;
     private final ShadeListBuilder mShadeListBuilder;
@@ -161,6 +162,14 @@
     }
 
     /**
+     * StabilityManager that is used to determine whether to suppress group and section changes.
+     * This should only be set once.
+     */
+    public void setVisualStabilityManager(NotifStabilityManager notifStabilityManager) {
+        mShadeListBuilder.setNotifStabilityManager(notifStabilityManager);
+    }
+
+    /**
      * Comparators that are used to sort top-level entries that share the same section. The
      * comparators are executed in order until one of them returns a non-zero result. If all return
      * zero, the pipeline falls back to sorting by rank (and, failing that, Notification.when).
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index d45f89c..6cbebf8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -21,6 +21,7 @@
 import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_FINALIZE_FILTERING;
 import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_FINALIZING;
 import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_GROUPING;
+import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_GROUP_STABILIZING;
 import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_IDLE;
 import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_PRE_GROUP_FILTERING;
 import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_RESETTING;
@@ -35,6 +36,7 @@
 import androidx.annotation.NonNull;
 
 import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.statusbar.NotificationInteractionTracker;
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener;
@@ -47,6 +49,7 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
 import com.android.systemui.util.Assert;
@@ -63,7 +66,6 @@
 import java.util.Objects;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * The second half of {@link NotifPipeline}. Sits downstream of the NotifCollection and transforms
@@ -71,7 +73,7 @@
  * notifications that are currently present in the notification shade.
  */
 @MainThread
-@Singleton
+@SysUISingleton
 public class ShadeListBuilder implements Dumpable {
     private final SystemClock mSystemClock;
     private final ShadeListBuilderLogger mLogger;
@@ -90,6 +92,7 @@
     private final List<NotifFilter> mNotifFinalizeFilters = new ArrayList<>();
     private final List<NotifComparator> mNotifComparators = new ArrayList<>();
     private final List<NotifSection> mNotifSections = new ArrayList<>();
+    @Nullable private NotifStabilityManager mNotifStabilityManager;
 
     private final List<OnBeforeTransformGroupsListener> mOnBeforeTransformGroupsListeners =
             new ArrayList<>();
@@ -109,7 +112,8 @@
             SystemClock systemClock,
             ShadeListBuilderLogger logger,
             DumpManager dumpManager,
-            NotificationInteractionTracker interactionTracker) {
+            NotificationInteractionTracker interactionTracker
+    ) {
         Assert.isMainThread();
         mSystemClock = systemClock;
         mLogger = logger;
@@ -200,6 +204,22 @@
         }
     }
 
+    void setNotifStabilityManager(NotifStabilityManager notifStabilityManager) {
+        Assert.isMainThread();
+        mPipelineState.requireState(STATE_IDLE);
+
+        if (mNotifStabilityManager != null) {
+            throw new IllegalStateException(
+                    "Attempting to set the NotifStabilityManager more than once. There should "
+                            + "only be one visual stability manager. Manager is being set by "
+                            + mNotifStabilityManager.getName() + " and "
+                            + notifStabilityManager.getName());
+        }
+
+        mNotifStabilityManager = notifStabilityManager;
+        mNotifStabilityManager.setInvalidationListener(this::onReorderingAllowedInvalidated);
+    }
+
     void setComparators(List<NotifComparator> comparators) {
         Assert.isMainThread();
         mPipelineState.requireState(STATE_IDLE);
@@ -237,6 +257,16 @@
         rebuildListIfBefore(STATE_PRE_GROUP_FILTERING);
     }
 
+    private void onReorderingAllowedInvalidated(NotifStabilityManager stabilityManager) {
+        Assert.isMainThread();
+
+        mLogger.logReorderingAllowedInvalidated(
+                stabilityManager.getName(),
+                mPipelineState.getState());
+
+        rebuildListIfBefore(STATE_GROUPING);
+    }
+
     private void onPromoterInvalidated(NotifPromoter promoter) {
         Assert.isMainThread();
 
@@ -285,6 +315,7 @@
         // Step 1: Reset notification states
         mPipelineState.incrementTo(STATE_RESETTING);
         resetNotifs();
+        onBeginRun();
 
         // Step 2: Filter out any notifications that shouldn't be shown right now
         mPipelineState.incrementTo(STATE_PRE_GROUP_FILTERING);
@@ -303,6 +334,10 @@
         promoteNotifs(mNotifList);
         pruneIncompleteGroups(mNotifList);
 
+        // Step 4.5: Reassign/revert any groups to maintain visual stability
+        mPipelineState.incrementTo(STATE_GROUP_STABILIZING);
+        stabilizeGroupingNotifs(mNotifList);
+
         // Step 5: Sort
         // Assign each top-level entry a section, then sort the list by section and then within
         // section by our list of custom comparators
@@ -472,6 +507,28 @@
         }
     }
 
+    private void stabilizeGroupingNotifs(List<ListEntry> list) {
+        if (mNotifStabilityManager == null) {
+            return;
+        }
+
+        for (int i = 0; i < list.size(); i++) {
+            final ListEntry tle = list.get(i);
+            if (tle.getPreviousAttachState().getParent() == null) {
+                continue; // new entries are allowed
+            }
+
+            final GroupEntry prevParent = tle.getPreviousAttachState().getParent();
+            final GroupEntry assignedParent = tle.getParent();
+            if (prevParent != assignedParent) {
+                if (!mNotifStabilityManager.isGroupChangeAllowed(tle.getRepresentativeEntry())) {
+                    tle.getAttachState().getSuppressedChanges().setParent(assignedParent);
+                    tle.setParent(prevParent);
+                }
+            }
+        }
+    }
+
     private void promoteNotifs(List<ListEntry> list) {
         for (int i = 0; i < list.size(); i++) {
             final ListEntry tle = list.get(i);
@@ -650,9 +707,18 @@
                 mLogger.logParentChanged(mIterationCount, prev.getParent(), curr.getParent());
             }
 
+            if (curr.getSuppressedChanges().getParent() != null) {
+                mLogger.logParentChangeSuppressed(
+                        mIterationCount,
+                        curr.getSuppressedChanges().getParent(),
+                        curr.getParent());
+            }
+
             if (curr.getExcludingFilter() != prev.getExcludingFilter()) {
                 mLogger.logFilterChanged(
-                        mIterationCount, prev.getExcludingFilter(), curr.getExcludingFilter());
+                        mIterationCount,
+                        prev.getExcludingFilter(),
+                        curr.getExcludingFilter());
             }
 
             // When something gets detached, its promoter and section are always set to null, so
@@ -661,7 +727,9 @@
 
             if (!wasDetached && curr.getPromoter() != prev.getPromoter()) {
                 mLogger.logPromoterChanged(
-                        mIterationCount, prev.getPromoter(), curr.getPromoter());
+                        mIterationCount,
+                        prev.getPromoter(),
+                        curr.getPromoter());
             }
 
             if (!wasDetached && curr.getSection() != prev.getSection()) {
@@ -672,6 +740,20 @@
                         curr.getSection(),
                         curr.getSectionIndex());
             }
+
+            if (curr.getSuppressedChanges().getSection() != null) {
+                mLogger.logSectionChangeSuppressed(
+                        mIterationCount,
+                        curr.getSuppressedChanges().getSection(),
+                        curr.getSuppressedChanges().getSectionIndex(),
+                        curr.getSection());
+            }
+        }
+    }
+
+    private void onBeginRun() {
+        if (mNotifStabilityManager != null) {
+            mNotifStabilityManager.onBeginRun();
         }
     }
 
@@ -681,6 +763,10 @@
         callOnCleanup(mNotifFinalizeFilters);
         callOnCleanup(mNotifComparators);
         callOnCleanup(mNotifSections);
+
+        if (mNotifStabilityManager != null) {
+            callOnCleanup(List.of(mNotifStabilityManager));
+        }
     }
 
     private void callOnCleanup(List<? extends Pluggable<?>> pluggables) {
@@ -770,12 +856,32 @@
     }
 
     private Pair<NotifSection, Integer> applySections(ListEntry entry) {
-        final Pair<NotifSection, Integer> sectionWithIndex = findSection(entry);
-        final NotifSection section = sectionWithIndex.first;
-        final Integer sectionIndex = sectionWithIndex.second;
+        Pair<NotifSection, Integer> sectionWithIndex = findSection(entry);
+        final ListAttachState prevAttachState = entry.getPreviousAttachState();
 
-        entry.getAttachState().setSection(section);
-        entry.getAttachState().setSectionIndex(sectionIndex);
+        // are we changing sections of this entry?
+        if (mNotifStabilityManager != null
+                && prevAttachState.getParent() != null
+                && (sectionWithIndex.first != prevAttachState.getSection()
+                    || sectionWithIndex.second != prevAttachState.getSectionIndex())) {
+
+            // are section changes allowed?
+            if (!mNotifStabilityManager.isSectionChangeAllowed(
+                    entry.getRepresentativeEntry())) {
+                entry.getAttachState().getSuppressedChanges().setSection(
+                        sectionWithIndex.first);
+                entry.getAttachState().getSuppressedChanges().setSectionIndex(
+                        sectionWithIndex.second);
+
+                // keep the previous section
+                sectionWithIndex = new Pair(
+                        prevAttachState.getSection(),
+                        prevAttachState.getSectionIndex());
+            }
+        }
+
+        entry.getAttachState().setSection(sectionWithIndex.first);
+        entry.getAttachState().setSectionIndex(sectionWithIndex.second);
 
         return sectionWithIndex;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/SuppressedAttachState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/SuppressedAttachState.kt
new file mode 100644
index 0000000..5261236
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/SuppressedAttachState.kt
@@ -0,0 +1,63 @@
+/*
+ * 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 com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection
+
+/**
+ * Stores the suppressed state that [ShadeListBuilder] assigned to this [ListEntry] before the
+ * VisualStabilityManager suppressed group and section changes.
+ */
+data class SuppressedAttachState private constructor(
+    /**
+     * Null if not attached to the current shade list. If top-level, then the shade list root. If
+     * part of a group, then that group's GroupEntry.
+     */
+    var parent: GroupEntry?,
+
+    /**
+     * The assigned section for this ListEntry. If the child of the group, this will be the
+     * parent's section. Null if not attached to the list.
+     */
+    var section: NotifSection?,
+    var sectionIndex: Int
+) {
+
+    /** Copies the state of another instance. */
+    fun clone(other: SuppressedAttachState) {
+        parent = other.parent
+        section = other.section
+        sectionIndex = other.sectionIndex
+    }
+
+    /** Resets back to a "clean" state (the same as created by the factory method) */
+    fun reset() {
+        parent = null
+        section = null
+        sectionIndex = -1
+    }
+
+    companion object {
+        @JvmStatic
+        fun create(): SuppressedAttachState {
+            return SuppressedAttachState(
+                null,
+                null,
+                -1)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolver.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolver.kt
index a0f9dc9..5dc0dcc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolver.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolver.kt
@@ -20,13 +20,13 @@
 import android.content.pm.PackageManager
 import android.service.notification.StatusBarNotification
 import android.util.Log
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
 import com.android.systemui.statusbar.phone.StatusBar
 import javax.inject.Inject
-import javax.inject.Singleton
 
-@Singleton
+@SysUISingleton
 class TargetSdkResolver @Inject constructor(
     private val context: Context
 ) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java
index 68ec6b6..0b9bded 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java
@@ -19,28 +19,24 @@
 import static android.app.NotificationManager.IMPORTANCE_MIN;
 
 import android.app.Notification;
-import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
-import android.util.ArraySet;
 
 import com.android.systemui.ForegroundServiceController;
 import com.android.systemui.appops.AppOpsController;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.statusbar.notification.collection.ListEntry;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
-import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
-import com.android.systemui.util.Assert;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 
 import java.util.HashMap;
 import java.util.Map;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Handles ForegroundService and AppOp interactions with notifications.
@@ -55,7 +51,7 @@
  *  frameworks/base/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener
  *  frameworks/base/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender
  */
-@Singleton
+@SysUISingleton
 public class AppOpsCoordinator implements Coordinator {
     private static final String TAG = "AppOpsCoordinator";
 
@@ -82,14 +78,9 @@
         // extend the lifetime of foreground notification services to show for at least 5 seconds
         mNotifPipeline.addNotificationLifetimeExtender(mForegroundLifetimeExtender);
 
-        // listen for new notifications to add appOps
-        mNotifPipeline.addCollectionListener(mNotifCollectionListener);
-
         // filter out foreground service notifications that aren't necessary anymore
         mNotifPipeline.addPreGroupFilter(mNotifFilter);
 
-        // when appOps change, update any relevant notifications to update appOps for
-        mAppOpsController.addCallback(ForegroundServiceController.APP_OPS, this::onAppOpsChanged);
     }
 
     public NotifSection getSection() {
@@ -186,35 +177,6 @@
     };
 
     /**
-     * Adds appOps to incoming and updating notifications
-     */
-    private NotifCollectionListener mNotifCollectionListener = new NotifCollectionListener() {
-        @Override
-        public void onEntryAdded(NotificationEntry entry) {
-            tagAppOps(entry);
-        }
-
-        @Override
-        public void onEntryUpdated(NotificationEntry entry) {
-            tagAppOps(entry);
-        }
-
-        private void tagAppOps(NotificationEntry entry) {
-            final StatusBarNotification sbn = entry.getSbn();
-            // note: requires that the ForegroundServiceController is updating their appOps first
-            ArraySet<Integer> activeOps =
-                    mForegroundServiceController.getAppOps(
-                            sbn.getUser().getIdentifier(),
-                            sbn.getPackageName());
-
-            entry.mActiveAppOps.clear();
-            if (activeOps != null) {
-                entry.mActiveAppOps.addAll(activeOps);
-            }
-        }
-    };
-
-    /**
      * Puts foreground service notifications into its own section.
      */
     private final NotifSection mNotifSection = new NotifSection("ForegroundService") {
@@ -230,53 +192,4 @@
             return false;
         }
     };
-
-    private void onAppOpsChanged(int code, int uid, String packageName, boolean active) {
-        mMainExecutor.execute(() -> handleAppOpsChanged(code, uid, packageName, active));
-    }
-
-    /**
-     * Update the appOp for the posted notification associated with the current foreground service
-     *
-     * @param code code for appOp to add/remove
-     * @param uid of user the notification is sent to
-     * @param packageName package that created the notification
-     * @param active whether the appOpCode is active or not
-     */
-    private void handleAppOpsChanged(int code, int uid, String packageName, boolean active) {
-        Assert.isMainThread();
-
-        int userId = UserHandle.getUserId(uid);
-
-        // Update appOps of the app's posted notifications with standard layouts
-        final ArraySet<String> notifKeys =
-                mForegroundServiceController.getStandardLayoutKeys(userId, packageName);
-        if (notifKeys != null) {
-            boolean changed = false;
-            for (int i = 0; i < notifKeys.size(); i++) {
-                final NotificationEntry entry = findNotificationEntryWithKey(notifKeys.valueAt(i));
-                if (entry != null
-                        && uid == entry.getSbn().getUid()
-                        && packageName.equals(entry.getSbn().getPackageName())) {
-                    if (active) {
-                        changed |= entry.mActiveAppOps.add(code);
-                    } else {
-                        changed |= entry.mActiveAppOps.remove(code);
-                    }
-                }
-            }
-            if (changed) {
-                mNotifFilter.invalidateList();
-            }
-        }
-    }
-
-    private NotificationEntry findNotificationEntryWithKey(String key) {
-        for (NotificationEntry entry : mNotifPipeline.getAllNotifs()) {
-            if (entry.getKey().equals(key)) {
-                return entry;
-            }
-        }
-        return null;
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
index 08462c1..4ddc1dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.notification.collection.coordinator;
 
 import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -28,7 +29,6 @@
 import java.util.Set;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Coordinates hiding, intercepting (the dismissal), and deletion of bubbled notifications.
@@ -50,7 +50,7 @@
  * respond to app-cancellations (ie: remove the bubble if the app cancels the notification).
  *
  */
-@Singleton
+@SysUISingleton
 public class BubbleCoordinator implements Coordinator {
     private static final String TAG = "BubbleCoordinator";
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
index 1a9de88..c8e859f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.collection.coordinator
 
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.notification.collection.ListEntry
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -24,14 +25,13 @@
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON
 import javax.inject.Inject
-import javax.inject.Singleton
 
 /**
  * A Conversation/People Coordinator that:
  * - Elevates important conversation notifications
  * - Puts conversations into its own people section. @see [NotifCoordinators] for section ordering.
  */
-@Singleton
+@SysUISingleton
 class ConversationCoordinator @Inject constructor(
     private val peopleNotificationIdentifier: PeopleNotificationIdentifier
 ) : Coordinator {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
index 625d1b9..47928b4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
@@ -23,20 +23,20 @@
 import android.os.RemoteException;
 import android.service.notification.StatusBarNotification;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Filters out most notifications when the device is unprovisioned.
  * Special notifications with extra permissions and tags won't be filtered out even when the
  * device is unprovisioned.
  */
-@Singleton
+@SysUISingleton
 public class DeviceProvisionedCoordinator implements Coordinator {
     private static final String TAG = "DeviceProvisionedCoordinator";
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java
index 72597af..6e6ceca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java
@@ -21,6 +21,7 @@
 
 import android.annotation.Nullable;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.notification.collection.ListEntry;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -37,7 +38,6 @@
 import java.util.Objects;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Coordinates heads up notification (HUN) interactions with the notification pipeline based on
@@ -53,7 +53,7 @@
  *
  * Note: The inflation callback in {@link PreparationCoordinator} handles showing HUNs.
  */
-@Singleton
+@SysUISingleton
 public class HeadsUpCoordinator implements Coordinator {
     private static final String TAG = "HeadsUpCoordinator";
 
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 95ba759..318cdb1 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
@@ -34,6 +34,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -46,12 +47,11 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Filters low priority and privacy-sensitive notifications from the lockscreen.
  */
-@Singleton
+@SysUISingleton
 public class KeyguardCoordinator implements Coordinator {
     private static final String TAG = "KeyguardCoordinator";
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
index a09c650..87ca717 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.notification.collection.coordinator;
 
 import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -31,17 +32,17 @@
 import java.util.List;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Handles the attachment of {@link Coordinator}s to the {@link NotifPipeline} so that the
  * Coordinators can register their respective callbacks.
  */
-@Singleton
+@SysUISingleton
 public class NotifCoordinators implements Dumpable {
     private static final String TAG = "NotifCoordinators";
     private final List<Coordinator> mCoordinators = new ArrayList<>();
     private final List<NotifSection> mOrderedSections = new ArrayList<>();
+
     /**
      * Creates all the coordinators.
      */
@@ -58,7 +59,8 @@
             HeadsUpCoordinator headsUpCoordinator,
             ConversationCoordinator conversationCoordinator,
             PreparationCoordinator preparationCoordinator,
-            MediaCoordinator mediaCoordinator) {
+            MediaCoordinator mediaCoordinator,
+            VisualStabilityCoordinator visualStabilityCoordinator) {
         dumpManager.registerDumpable(TAG, this);
 
         mCoordinators.add(new HideLocallyDismissedNotifsCoordinator());
@@ -70,6 +72,7 @@
         mCoordinators.add(bubbleCoordinator);
         mCoordinators.add(mediaCoordinator);
         mCoordinators.add(conversationCoordinator);
+        mCoordinators.add(visualStabilityCoordinator);
         if (featureFlags.isNewNotifPipelineRenderingEnabled()) {
             mCoordinators.add(headsUpCoordinator);
             mCoordinators.add(preparationCoordinator);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
index ee2bb6b1..31826c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
@@ -28,6 +28,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.notification.collection.GroupEntry;
 import com.android.systemui.statusbar.notification.collection.ListEntry;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -48,7 +49,6 @@
 import java.util.Set;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Kicks off core notification inflation and view rebinding when a notification is added or updated.
@@ -57,7 +57,7 @@
  * If a notification was uninflated, this coordinator will filter the notification out from the
  * {@link ShadeListBuilder} until it is inflated.
  */
-@Singleton
+@SysUISingleton
 public class PreparationCoordinator implements Coordinator {
     private static final String TAG = "PreparationCoordinator";
 
@@ -307,7 +307,7 @@
     private void onInflationFinished(NotificationEntry entry) {
         mLogger.logNotifInflated(entry.getKey());
         mInflatingNotifs.remove(entry);
-        mViewBarn.registerViewForEntry(entry, entry.getRow());
+        mViewBarn.registerViewForEntry(entry, entry.getRowController());
         mInflationStates.put(entry, STATE_INFLATED);
         mNotifInflatingFilter.invalidateList();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
index 0d2f9da..a32b163 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.collection.coordinator;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.notification.collection.ListEntry;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -25,7 +26,6 @@
 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Filters out NotificationEntries based on its Ranking and dozing state.
@@ -34,7 +34,7 @@
  *  - whether the notification's app is suspended or hiding its notifications
  *  - whether DND settings are hiding notifications from ambient display or the notification list
  */
-@Singleton
+@SysUISingleton
 public class RankingCoordinator implements Coordinator {
     private static final String TAG = "RankingNotificationCoordinator";
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
new file mode 100644
index 0000000..08030f8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
@@ -0,0 +1,205 @@
+/*
+ * 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.coordinator;
+
+import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
+import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
+
+import android.annotation.NonNull;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.NotificationViewHierarchyManager;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.inject.Inject;
+
+/**
+ * Ensures that notifications are visually stable if the user is looking at the notifications.
+ * Group and section changes are re-allowed when the notification entries are no longer being
+ * viewed.
+ *
+ * Previously this was implemented in the view-layer {@link NotificationViewHierarchyManager} by
+ * {@link com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager}.
+ * This is now integrated in the data-layer via
+ * {@link com.android.systemui.statusbar.notification.collection.ShadeListBuilder}.
+ */
+@SysUISingleton
+public class VisualStabilityCoordinator implements Coordinator {
+    private final DelayableExecutor mDelayableExecutor;
+    private final WakefulnessLifecycle mWakefulnessLifecycle;
+    private final StatusBarStateController mStatusBarStateController;
+    private final HeadsUpManager mHeadsUpManager;
+
+    private boolean mScreenOn;
+    private boolean mPanelExpanded;
+    private boolean mPulsing;
+
+    private boolean mReorderingAllowed;
+    private boolean mIsSuppressingGroupChange = false;
+    private final Set<String> mEntriesWithSuppressedSectionChange = new HashSet<>();
+
+    // key: notification key that can temporarily change its section
+    // value: runnable that when run removes its associated RemoveOverrideSuppressionRunnable
+    // from the DelayableExecutor's queue
+    private Map<String, Runnable> mEntriesThatCanChangeSection = new HashMap<>();
+
+    @VisibleForTesting
+    protected static final long ALLOW_SECTION_CHANGE_TIMEOUT = 500;
+
+    @Inject
+    public VisualStabilityCoordinator(
+            HeadsUpManager headsUpManager,
+            WakefulnessLifecycle wakefulnessLifecycle,
+            StatusBarStateController statusBarStateController,
+            DelayableExecutor delayableExecutor
+    ) {
+        mHeadsUpManager = headsUpManager;
+        mWakefulnessLifecycle = wakefulnessLifecycle;
+        mStatusBarStateController = statusBarStateController;
+        mDelayableExecutor = delayableExecutor;
+    }
+
+    @Override
+    public void attach(NotifPipeline pipeline) {
+        mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
+        mScreenOn = mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_AWAKE
+                || mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_WAKING;
+
+        mStatusBarStateController.addCallback(mStatusBarStateControllerListener);
+        mPulsing = mStatusBarStateController.isPulsing();
+
+        pipeline.setVisualStabilityManager(mNotifStabilityManager);
+    }
+
+    private final NotifStabilityManager mNotifStabilityManager =
+            new NotifStabilityManager("VisualStabilityCoordinator") {
+                @Override
+                public void onBeginRun() {
+                    mIsSuppressingGroupChange = false;
+                    mEntriesWithSuppressedSectionChange.clear();
+                }
+
+                @Override
+                public boolean isGroupChangeAllowed(NotificationEntry entry) {
+                    final boolean isGroupChangeAllowedForEntry =
+                            mReorderingAllowed || mHeadsUpManager.isAlerting(entry.getKey());
+                    mIsSuppressingGroupChange |= isGroupChangeAllowedForEntry;
+                    return isGroupChangeAllowedForEntry;
+                }
+
+                @Override
+                public boolean isSectionChangeAllowed(NotificationEntry entry) {
+                    final boolean isSectionChangeAllowedForEntry =
+                            mReorderingAllowed
+                                    || mHeadsUpManager.isAlerting(entry.getKey())
+                                    || mEntriesThatCanChangeSection.containsKey(entry.getKey());
+                    if (isSectionChangeAllowedForEntry) {
+                        mEntriesWithSuppressedSectionChange.add(entry.getKey());
+                    }
+                    return isSectionChangeAllowedForEntry;
+                }
+            };
+
+    private void updateAllowedStates() {
+        mReorderingAllowed = isReorderingAllowed();
+        if (mReorderingAllowed && (mIsSuppressingGroupChange || isSuppressingSectionChange())) {
+            mNotifStabilityManager.invalidateList();
+        }
+    }
+
+    private boolean isSuppressingSectionChange() {
+        return !mEntriesWithSuppressedSectionChange.isEmpty();
+    }
+
+    private boolean isReorderingAllowed() {
+        return (!mScreenOn || !mPanelExpanded) && !mPulsing;
+    }
+
+    /**
+     * Allows this notification entry to be re-ordered in the notification list temporarily until
+     * the timeout has passed.
+     *
+     * Typically this is allowed because the user has directly changed something about the
+     * notification and we are reordering based on the user's change.
+     *
+     * @param entry notification entry that can change sections even if isReorderingAllowed is false
+     * @param now current time SystemClock.uptimeMillis
+     */
+    public void temporarilyAllowSectionChanges(@NonNull NotificationEntry entry, long now) {
+        final String entryKey = entry.getKey();
+        final boolean wasSectionChangeAllowed =
+                mNotifStabilityManager.isSectionChangeAllowed(entry);
+
+        // If it exists, cancel previous timeout
+        if (mEntriesThatCanChangeSection.containsKey(entryKey)) {
+            mEntriesThatCanChangeSection.get(entryKey).run();
+        }
+
+        // Schedule & store new timeout cancellable
+        mEntriesThatCanChangeSection.put(
+                entryKey,
+                mDelayableExecutor.executeAtTime(
+                        () -> mEntriesThatCanChangeSection.remove(entryKey),
+                        now + ALLOW_SECTION_CHANGE_TIMEOUT));
+
+        if (!wasSectionChangeAllowed) {
+            mNotifStabilityManager.invalidateList();
+        }
+    }
+
+    final StatusBarStateController.StateListener mStatusBarStateControllerListener =
+            new StatusBarStateController.StateListener() {
+                @Override
+                public void onPulsingChanged(boolean pulsing) {
+                    mPulsing = pulsing;
+                    updateAllowedStates();
+                }
+
+                @Override
+                public void onExpandedChanged(boolean expanded) {
+                    mPanelExpanded = expanded;
+                    updateAllowedStates();
+                }
+            };
+
+    final WakefulnessLifecycle.Observer mWakefulnessObserver = new WakefulnessLifecycle.Observer() {
+        @Override
+        public void onFinishedGoingToSleep() {
+            mScreenOn = false;
+            updateAllowedStates();
+        }
+
+        @Override
+        public void onStartedWakingUp() {
+            mScreenOn = true;
+            updateAllowedStates();
+        }
+    };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/LowPriorityInflationHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/LowPriorityInflationHelper.java
index 73c0fdc..6089aa2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/LowPriorityInflationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/LowPriorityInflationHelper.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.collection.inflation;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.notification.collection.GroupEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -25,13 +26,12 @@
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Helper class that provide methods to help check when we need to inflate a low priority version
  * ot notification content.
  */
-@Singleton
+@SysUISingleton
 public class LowPriorityInflationHelper {
     private final FeatureFlags mFeatureFlags;
     private final NotificationGroupManager mGroupManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index 8849824..7c061aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -24,6 +24,7 @@
 import android.view.ViewGroup;
 
 import com.android.internal.util.NotificationMessagingUtil;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -44,10 +45,9 @@
 
 import javax.inject.Inject;
 import javax.inject.Provider;
-import javax.inject.Singleton;
 
 /** Handles inflating and updating views for notifications. */
-@Singleton
+@SysUISingleton
 public class NotificationRowBinderImpl implements NotificationRowBinder {
 
     private static final String TAG = "NotificationViewManager";
@@ -136,6 +136,7 @@
                                         .expandableNotificationRow(row)
                                         .notificationEntry(entry)
                                         .onExpandClickListener(mPresenter)
+                                        .listContainer(mListContainer)
                                         .build();
                         ExpandableNotificationRowController rowController =
                                 component.getExpandableNotificationRowController();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnDismissCallbackImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java
similarity index 71%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnDismissCallbackImpl.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java
index 36adf9b..19a356b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnDismissCallbackImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java
@@ -18,6 +18,7 @@
 
 import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
 
+import android.os.SystemClock;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationStats;
 
@@ -26,35 +27,43 @@
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.coordinator.VisualStabilityCoordinator;
 import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
-import com.android.systemui.statusbar.notification.row.OnDismissCallback;
+import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
 /**
- * Callback used when a user:
- * 1. Manually dismisses a notification {@see ExpandableNotificationRow}.
- * 2. Clicks on a notification with flag {@link android.app.Notification#FLAG_AUTO_CANCEL}.
- * {@see StatusBarNotificationActivityStarter}
+ * Callback for when a user interacts with a {@see ExpandableNotificationRow}. Sends relevant
+ * information about the interaction to the notification pipeline.
  */
-public class OnDismissCallbackImpl implements OnDismissCallback {
+public class OnUserInteractionCallbackImpl implements OnUserInteractionCallback {
     private final NotifPipeline mNotifPipeline;
     private final NotifCollection mNotifCollection;
     private final HeadsUpManager mHeadsUpManager;
     private final StatusBarStateController mStatusBarStateController;
+    private final VisualStabilityCoordinator mVisualStabilityCoordinator;
 
-    public OnDismissCallbackImpl(
+    public OnUserInteractionCallbackImpl(
             NotifPipeline notifPipeline,
             NotifCollection notifCollection,
             HeadsUpManager headsUpManager,
-            StatusBarStateController statusBarStateController
+            StatusBarStateController statusBarStateController,
+            VisualStabilityCoordinator visualStabilityCoordinator
     ) {
         mNotifPipeline = notifPipeline;
         mNotifCollection = notifCollection;
         mHeadsUpManager = headsUpManager;
         mStatusBarStateController = statusBarStateController;
+        mVisualStabilityCoordinator = visualStabilityCoordinator;
     }
 
+    /**
+     * Callback triggered when a user:
+     * 1. Manually dismisses a notification {@see ExpandableNotificationRow}.
+     * 2. Clicks on a notification with flag {@link android.app.Notification#FLAG_AUTO_CANCEL}.
+     * {@see StatusBarNotificationActivityStarter}
+     */
     @Override
     public void onDismiss(
             NotificationEntry entry,
@@ -80,4 +89,11 @@
                             NotificationLogger.getNotificationLocation(entry)))
         );
     }
+
+    @Override
+    public void onImportanceChanged(NotificationEntry entry) {
+        mVisualStabilityCoordinator.temporarilyAllowSectionChanges(
+                entry,
+                SystemClock.uptimeMillis());
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
index 9782c3e..db49e44 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
@@ -19,6 +19,7 @@
 import android.util.Log;
 
 import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationListener;
@@ -29,20 +30,18 @@
 import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer;
 import com.android.systemui.statusbar.notification.collection.coordinator.NotifCoordinators;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
-import com.android.systemui.statusbar.notification.collection.render.NotifViewManager;
-import com.android.systemui.statusbar.notification.collection.render.NotifViewManagerBuilder;
+import com.android.systemui.statusbar.notification.collection.render.ShadeViewManagerFactory;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Initialization code for the new notification pipeline.
  */
-@Singleton
+@SysUISingleton
 public class NotifPipelineInitializer implements Dumpable {
     private final NotifPipeline mPipelineWrapper;
     private final GroupCoalescer mGroupCoalescer;
@@ -51,7 +50,7 @@
     private final NotifCoordinators mNotifPluggableCoordinators;
     private final NotifInflaterImpl mNotifInflater;
     private final DumpManager mDumpManager;
-    private final NotifViewManagerBuilder mNotifViewManagerBuilder;
+    private final ShadeViewManagerFactory mShadeViewManagerFactory;
     private final FeatureFlags mFeatureFlags;
 
 
@@ -64,7 +63,7 @@
             NotifCoordinators notifCoordinators,
             NotifInflaterImpl notifInflater,
             DumpManager dumpManager,
-            NotifViewManagerBuilder notifViewManagerBuilder,
+            ShadeViewManagerFactory shadeViewManagerFactory,
             FeatureFlags featureFlags) {
         mPipelineWrapper = pipelineWrapper;
         mGroupCoalescer = groupCoalescer;
@@ -73,8 +72,8 @@
         mNotifPluggableCoordinators = notifCoordinators;
         mDumpManager = dumpManager;
         mNotifInflater = notifInflater;
+        mShadeViewManagerFactory = shadeViewManagerFactory;
         mFeatureFlags = featureFlags;
-        mNotifViewManagerBuilder = notifViewManagerBuilder;
     }
 
     /** Hooks the new pipeline up to NotificationManager */
@@ -95,8 +94,7 @@
 
         // Wire up pipeline
         if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
-            NotifViewManager notifViewManager = mNotifViewManagerBuilder.build(listContainer);
-            notifViewManager.attach(mListBuilder);
+            mShadeViewManagerFactory.create(listContainer).attach(mListBuilder);
         }
         mListBuilder.attach(mNotifCollection);
         mNotifCollection.attach(mGroupCoalescer);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/OnDismissCallbackImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/OnUserInteractionCallbackImplLegacy.java
similarity index 75%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/OnDismissCallbackImpl.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/OnUserInteractionCallbackImplLegacy.java
index 94ffa8f..cce8cdc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/OnDismissCallbackImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/OnUserInteractionCallbackImplLegacy.java
@@ -27,30 +27,36 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
-import com.android.systemui.statusbar.notification.row.OnDismissCallback;
+import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
 /**
- * Callback used when a user:
- * 1. Manually dismisses a notification {@see ExpandableNotificationRow}.
- * 2. Clicks on a notification with flag {@link android.app.Notification#FLAG_AUTO_CANCEL}.
- * {@see StatusBarNotificationActivityStarter}
+ * Callback for when a user interacts with a {@see ExpandableNotificationRow}.
  */
-public class OnDismissCallbackImpl implements OnDismissCallback {
+public class OnUserInteractionCallbackImplLegacy implements OnUserInteractionCallback {
     private final NotificationEntryManager mNotificationEntryManager;
     private final HeadsUpManager mHeadsUpManager;
     private final StatusBarStateController mStatusBarStateController;
+    private final VisualStabilityManager mVisualStabilityManager;
 
-    public OnDismissCallbackImpl(
+    public OnUserInteractionCallbackImplLegacy(
             NotificationEntryManager notificationEntryManager,
             HeadsUpManager headsUpManager,
-            StatusBarStateController statusBarStateController
+            StatusBarStateController statusBarStateController,
+            VisualStabilityManager visualStabilityManager
     ) {
         mNotificationEntryManager = notificationEntryManager;
         mHeadsUpManager = headsUpManager;
         mStatusBarStateController = statusBarStateController;
+        mVisualStabilityManager = visualStabilityManager;
     }
 
+    /**
+     * Callback triggered when a user:
+     * 1. Manually dismisses a notification {@see ExpandableNotificationRow}.
+     * 2. Clicks on a notification with flag {@link android.app.Notification#FLAG_AUTO_CANCEL}.
+     * {@see StatusBarNotificationActivityStarter}
+     */
     @Override
     public void onDismiss(
             NotificationEntry entry,
@@ -77,5 +83,10 @@
                 cancellationReason
         );
     }
+
+    @Override
+    public void onImportanceChanged(NotificationEntry entry) {
+        mVisualStabilityManager.temporarilyAllowReordering();
+    }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/VisualStabilityManager.java
similarity index 78%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/VisualStabilityManager.java
index 8341c02..165df30 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/VisualStabilityManager.java
@@ -11,10 +11,10 @@
  * 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
+ * limitations under the License.
  */
 
-package com.android.systemui.statusbar.notification;
+package com.android.systemui.statusbar.notification.collection.legacy;
 
 import android.os.Handler;
 import android.os.SystemClock;
@@ -24,7 +24,11 @@
 
 import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.statusbar.NotificationPresenter;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.notification.NotificationEntryListener;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -65,24 +69,44 @@
      */
     public VisualStabilityManager(
             NotificationEntryManager notificationEntryManager,
-            @Main Handler handler) {
+            @Main Handler handler,
+            StatusBarStateController statusBarStateController,
+            WakefulnessLifecycle wakefulnessLifecycle) {
 
         mHandler = handler;
 
-        notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
-            @Override
-            public void onPreEntryUpdated(NotificationEntry entry) {
-                final boolean ambientStateHasChanged =
-                        entry.isAmbient() != entry.getRow().isLowPriority();
-                if (ambientStateHasChanged) {
-                    // note: entries are removed in onReorderingFinished
-                    mLowPriorityReorderingViews.add(entry);
+        if (notificationEntryManager != null) {
+            notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
+                @Override
+                public void onPreEntryUpdated(NotificationEntry entry) {
+                    final boolean ambientStateHasChanged =
+                            entry.isAmbient() != entry.getRow().isLowPriority();
+                    if (ambientStateHasChanged) {
+                        // note: entries are removed in onReorderingFinished
+                        mLowPriorityReorderingViews.add(entry);
+                    }
                 }
-            }
-        });
-    }
+            });
+        }
 
-    public void setUpWithPresenter(NotificationPresenter presenter) {
+        if (statusBarStateController != null) {
+            setPulsing(statusBarStateController.isPulsing());
+            statusBarStateController.addCallback(new StatusBarStateController.StateListener() {
+                @Override
+                public void onPulsingChanged(boolean pulsing) {
+                    setPulsing(pulsing);
+                }
+
+                @Override
+                public void onExpandedChanged(boolean expanded) {
+                    setPanelExpanded(expanded);
+                }
+            });
+        }
+
+        if (wakefulnessLifecycle != null) {
+            wakefulnessLifecycle.addObserver(mWakefulnessObserver);
+        }
     }
 
     /**
@@ -120,25 +144,25 @@
     }
 
     /**
-     * Set the panel to be expanded.
+     * @param screenOn whether the screen is on
      */
-    public void setPanelExpanded(boolean expanded) {
-        mPanelExpanded = expanded;
+    private void setScreenOn(boolean screenOn) {
+        mScreenOn = screenOn;
         updateAllowedStates();
     }
 
     /**
-     * @param screenOn whether the screen is on
+     * Set the panel to be expanded.
      */
-    public void setScreenOn(boolean screenOn) {
-        mScreenOn = screenOn;
+    private void setPanelExpanded(boolean expanded) {
+        mPanelExpanded = expanded;
         updateAllowedStates();
     }
 
     /**
      * @param pulsing whether we are currently pulsing for ambient display.
      */
-    public void setPulsing(boolean pulsing) {
+    private void setPulsing(boolean pulsing) {
         if (mPulsing == pulsing) {
             return;
         }
@@ -215,6 +239,10 @@
         mVisibilityLocationProvider = visibilityLocationProvider;
     }
 
+    /**
+     * Notifications have been reordered, so reset all the allowed list of views that are allowed
+     * to reorder.
+     */
     public void onReorderingFinished() {
         mAllowedReorderViews.clear();
         mAddedChildren.clear();
@@ -271,11 +299,27 @@
         pw.println();
     }
 
+    final WakefulnessLifecycle.Observer mWakefulnessObserver = new WakefulnessLifecycle.Observer() {
+        @Override
+        public void onFinishedGoingToSleep() {
+            setScreenOn(false);
+        }
+
+        @Override
+        public void onStartedWakingUp() {
+            setScreenOn(true);
+        }
+    };
+
+
+    /**
+     * See {@link Callback#onChangeAllowed()}
+     */
     public interface Callback {
+
         /**
          * Called when changing is allowed again.
          */
         void onChangeAllowed();
     }
-
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java
index f1f7d63..798bfe7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java
@@ -81,9 +81,10 @@
     public static final int STATE_PRE_GROUP_FILTERING = 3;
     public static final int STATE_GROUPING = 4;
     public static final int STATE_TRANSFORMING = 5;
-    public static final int STATE_SORTING = 6;
-    public static final int STATE_FINALIZE_FILTERING = 7;
-    public static final int STATE_FINALIZING = 8;
+    public static final int STATE_GROUP_STABILIZING = 6;
+    public static final int STATE_SORTING = 7;
+    public static final int STATE_FINALIZE_FILTERING = 8;
+    public static final int STATE_FINALIZING = 9;
 
     @IntDef(prefix = { "STATE_" }, value = {
             STATE_IDLE,
@@ -92,6 +93,7 @@
             STATE_PRE_GROUP_FILTERING,
             STATE_GROUPING,
             STATE_TRANSFORMING,
+            STATE_GROUP_STABILIZING,
             STATE_SORTING,
             STATE_FINALIZE_FILTERING,
             STATE_FINALIZING,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
index 67f8bfe..f7bfeb7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
@@ -57,6 +57,15 @@
         })
     }
 
+    fun logReorderingAllowedInvalidated(name: String, pipelineState: Int) {
+        buffer.log(TAG, DEBUG, {
+            str1 = name
+            int1 = pipelineState
+        }, {
+            """ReorderingNowAllowed "$str1" invalidated; pipeline state is $int1"""
+        })
+    }
+
     fun logPromoterInvalidated(name: String, pipelineState: Int) {
         buffer.log(TAG, DEBUG, {
             str1 = name
@@ -156,6 +165,21 @@
         })
     }
 
+    fun logParentChangeSuppressed(
+        buildId: Int,
+        suppressedParent: GroupEntry?,
+        keepingParent: GroupEntry?
+    ) {
+        buffer.log(TAG, INFO, {
+            int1 = buildId
+            str1 = suppressedParent?.key
+            str2 = keepingParent?.key
+        }, {
+            "(Build $long1)     Change of parent to '$str1' suppressed; " +
+                "keeping parent '$str2'"
+        })
+    }
+
     fun logFilterChanged(
         buildId: Int,
         prevFilter: NotifFilter?,
@@ -206,6 +230,23 @@
         })
     }
 
+    fun logSectionChangeSuppressed(
+        buildId: Int,
+        suppressedSection: NotifSection?,
+        suppressedSectionIndex: Int,
+        assignedSection: NotifSection?
+    ) {
+        buffer.log(TAG, INFO, {
+            long1 = buildId.toLong()
+            str1 = suppressedSection?.name
+            int1 = suppressedSectionIndex
+            str2 = assignedSection?.name
+        }, {
+            "(Build $long1)     Section change suppressed: '$str1' (#$int1). " +
+                "Keeping section: '$str2'"
+        })
+    }
+
     fun logFinalList(entries: List<ListEntry>) {
         if (entries.isEmpty()) {
             buffer.log(TAG, DEBUG, {}, { "(empty list)" })
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.java
new file mode 100644
index 0000000..58d4b97
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.java
@@ -0,0 +1,55 @@
+/*
+ * 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.listbuilder.pluggable;
+
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
+/**
+ * Pluggable for participating in notif stabilization. In particular, suppressing group and
+ * section changes.
+ *
+ * The stability manager should be invalidated when previously suppressed a group or
+ * section change is now allowed.
+ */
+public abstract class NotifStabilityManager extends Pluggable<NotifStabilityManager> {
+
+    protected NotifStabilityManager(String name) {
+        super(name);
+    }
+
+    /**
+     * Called at the beginning of every pipeline run to perform any necessary cleanup from the
+     * previous run.
+     */
+    public abstract void onBeginRun();
+
+    /**
+     * Returns whether this notification can currently change groups/parents.
+     * Per iteration of the notification pipeline, locally stores this information until the next
+     * run of the pipeline. When this method returns true, it's expected that a group change for
+     * this entry is being suppressed.
+     */
+    public abstract boolean isGroupChangeAllowed(NotificationEntry entry);
+
+    /**
+     * Returns whether this notification entry can currently change sections.
+     * Per iteration of the notification pipeline, locally stores this information until the next
+     * run of the pipeline. When this method returns true, it's expected that a section change is
+     * being suppressed.
+     */
+    public abstract boolean isSectionChangeAllowed(NotificationEntry entry);
+}
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
index 1c076c4..8b803b5 100644
--- 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
@@ -19,6 +19,7 @@
 import android.app.Notification;
 import android.app.NotificationManager;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.notification.collection.GroupEntry;
 import com.android.systemui.statusbar.notification.collection.ListEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -28,7 +29,6 @@
 import java.util.List;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Determines whether a notification is considered 'high priority'.
@@ -36,7 +36,7 @@
  * Notifications that are high priority are visible on the lock screen/status bar and in the top
  * section in the shade.
  */
-@Singleton
+@SysUISingleton
 public class HighPriorityProvider {
     private final PeopleNotificationIdentifier mPeopleNotificationIdentifier;
     private final NotificationGroupManager mGroupManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt
new file mode 100644
index 0000000..67f7b1c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt
@@ -0,0 +1,90 @@
+/*
+ * 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.render
+
+import android.view.View
+import java.lang.RuntimeException
+import java.lang.StringBuilder
+
+/**
+ * A controller that represents a single unit of addable/removable view(s) in the notification
+ * shade. Some nodes are just a single view (such as a header), while some might involve many views
+ * (such as a notification row).
+ *
+ * It's possible for nodes to support having child nodes (for example, some notification rows
+ * contain other notification rows). If so, they must implement all of the child-related methods
+ * below.
+ */
+interface NodeController {
+    /** A string that uniquely(ish) represents the node in the tree. Used for debugging. */
+    val nodeLabel: String
+
+    val view: View
+
+    fun getChildAt(index: Int): View? {
+        throw RuntimeException("Not supported")
+    }
+
+    fun getChildCount(): Int {
+        throw RuntimeException("Not supported")
+    }
+
+    fun addChildAt(child: NodeController, index: Int) {
+        throw RuntimeException("Not supported")
+    }
+
+    fun moveChildTo(child: NodeController, index: Int) {
+        throw RuntimeException("Not supported")
+    }
+
+    fun removeChild(child: NodeController, isTransfer: Boolean) {
+        throw RuntimeException("Not supported")
+    }
+}
+
+/**
+ * Used to specify the tree of [NodeController]s that currently make up the shade.
+ */
+interface NodeSpec {
+    val parent: NodeSpec?
+    val controller: NodeController
+    val children: List<NodeSpec>
+}
+
+class NodeSpecImpl(
+    override val parent: NodeSpec?,
+    override val controller: NodeController
+) : NodeSpec {
+    override val children = mutableListOf<NodeSpec>()
+}
+
+/**
+ * Converts a tree spec to human-readable string, for dumping purposes.
+ */
+fun treeSpecToStr(tree: NodeSpec): String {
+    return StringBuilder().also { treeSpecToStrHelper(tree, it, "") }.toString()
+}
+
+private fun treeSpecToStrHelper(tree: NodeSpec, sb: StringBuilder, indent: String) {
+    sb.append("${indent}ns{${tree.controller.nodeLabel}")
+    if (tree.children.isNotEmpty()) {
+        val childIndent = "$indent  "
+        for (child in tree.children) {
+            treeSpecToStrHelper(child, sb, childIndent)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewBarn.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewBarn.kt
index 5400095..79bc3d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewBarn.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewBarn.kt
@@ -17,19 +17,20 @@
 package com.android.systemui.statusbar.notification.collection.render
 
 import android.view.textclassifier.Log
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.notification.collection.ListEntry
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController
 import javax.inject.Inject
-import javax.inject.Singleton
 
 /**
- * The ViewBarn is just a map from [ListEntry] to an instance of an [ExpandableNotificationRow].
+ * The ViewBarn is just a map from [ListEntry] to an instance of an
+ * [ExpandableNotificationRowController].
  */
-@Singleton
+@SysUISingleton
 class NotifViewBarn @Inject constructor() {
-    private val rowMap = mutableMapOf<String, ExpandableNotificationRow>()
+    private val rowMap = mutableMapOf<String, ExpandableNotificationRowController>()
 
-    fun requireView(forEntry: ListEntry): ExpandableNotificationRow {
+    fun requireView(forEntry: ListEntry): ExpandableNotificationRowController {
         if (DEBUG) {
             Log.d(TAG, "requireView: $forEntry.key")
         }
@@ -41,11 +42,11 @@
         return li
     }
 
-    fun registerViewForEntry(entry: ListEntry, view: ExpandableNotificationRow) {
+    fun registerViewForEntry(entry: ListEntry, controller: ExpandableNotificationRowController) {
         if (DEBUG) {
             Log.d(TAG, "registerViewForEntry: $entry.key")
         }
-        rowMap[entry.key] = view
+        rowMap[entry.key] = controller
     }
 
     fun removeViewForEntry(entry: ListEntry) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewManager.kt
deleted file mode 100644
index f2e2c39..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewManager.kt
+++ /dev/null
@@ -1,240 +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.collection.render
-
-import android.annotation.MainThread
-import android.view.View
-import com.android.systemui.statusbar.notification.collection.GroupEntry
-import com.android.systemui.statusbar.notification.collection.GroupEntry.ROOT_ENTRY
-import com.android.systemui.statusbar.notification.collection.ListEntry
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.collection.ShadeListBuilder
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
-import com.android.systemui.statusbar.notification.stack.NotificationListContainer
-import javax.inject.Inject
-
-/**
- * A consumer of a Notification tree built by [ShadeListBuilder] which will update the notification
- * presenter with the minimum operations required to make the old tree match the new one
- */
-@MainThread
-class NotifViewManager constructor(
-    private val listContainer: NotificationListContainer,
-    private val viewBarn: NotifViewBarn,
-    private val logger: NotifViewManagerLogger
-) {
-    private val rootNode = RootWrapper(listContainer)
-    private val rows = mutableMapOf<ListEntry, RowNode>()
-
-    fun attach(listBuilder: ShadeListBuilder) {
-        listBuilder.setOnRenderListListener(::onNewNotifTree)
-    }
-
-    private fun onNewNotifTree(tree: List<ListEntry>) {
-        // Step 1: Detach all views whose parents have changed
-        detachRowsWithModifiedParents()
-
-        // Step 2: Attach all new views and reattach all views whose parents changed.
-        // Also reorder existing children to match the spec we've received
-        val orderChanged = addAndReorderChildren(rootNode, tree)
-        if (orderChanged) {
-            listContainer.generateChildOrderChangedEvent()
-        }
-    }
-
-    private fun detachRowsWithModifiedParents() {
-        val toRemove = mutableListOf<ListEntry>()
-        for (row in rows.values) {
-            val oldParentEntry = row.nodeParent?.entry
-            val newParentEntry = row.entry.parent
-
-            if (newParentEntry != oldParentEntry) {
-                // If the parent is null, then we should remove the child completely. If not, then
-                // the parent merely changed: we'll detach it for now and then attach it to the
-                // new parent in step 2.
-                val isTransfer = newParentEntry != null
-                if (!isTransfer) {
-                    toRemove.add(row.entry)
-                }
-
-                if (!isTransfer && !isAttachedToRootEntry(oldParentEntry)) {
-                    // If our view parent has also been removed (i.e. is no longer attached to the
-                    // root entry) then we skip removing the child here
-                    logger.logSkippingDetach(row.entry.key, row.nodeParent?.entry?.key)
-                } else {
-                    logger.logDetachingChild(
-                            row.entry.key,
-                            isTransfer,
-                            oldParentEntry?.key,
-                            newParentEntry?.key)
-                    row.nodeParent?.removeChild(row, isTransfer)
-                    row.nodeParent = null
-                }
-            }
-        }
-        rows.keys.removeAll(toRemove)
-    }
-
-    private fun addAndReorderChildren(parent: ParentNode, childEntries: List<ListEntry>): Boolean {
-        var orderChanged = false
-        for ((index, entry) in childEntries.withIndex()) {
-            val row = getRowNode(entry)
-            val currView = parent.getChildViewAt(index)
-            if (currView != row.view) {
-                when (row.nodeParent) {
-                    null -> {
-                        logger.logAttachingChild(row.entry.key, parent.entry.key)
-                        parent.addChildAt(row, index)
-                        row.nodeParent = parent
-                    }
-                    parent -> {
-                        logger.logMovingChild(row.entry.key, parent.entry.key, index)
-                        parent.moveChild(row, index)
-                        orderChanged = true
-                    }
-                    else -> {
-                        throw IllegalStateException("Child ${row.entry.key} should have parent " +
-                                "${parent.entry.key} but is actually " +
-                                "${row.nodeParent?.entry?.key}")
-                    }
-                }
-            }
-            if (row is GroupWrapper) {
-                val childOrderChanged = addAndReorderChildren(row, row.entry.children)
-                orderChanged = orderChanged || childOrderChanged
-            }
-        }
-        // TODO: setUntruncatedChildCount
-
-        return orderChanged
-    }
-
-    private fun getRowNode(entry: ListEntry): RowNode {
-        return rows.getOrPut(entry) {
-            when (entry) {
-                is NotificationEntry -> RowWrapper(entry, viewBarn.requireView(entry))
-                is GroupEntry ->
-                    GroupWrapper(
-                            entry,
-                            viewBarn.requireView(checkNotNull(entry.summary)),
-                            listContainer)
-                else -> throw RuntimeException(
-                        "Unexpected entry type for ${entry.key}: ${entry.javaClass}")
-            }
-        }
-    }
-}
-
-class NotifViewManagerBuilder @Inject constructor(
-    private val viewBarn: NotifViewBarn,
-    private val logger: NotifViewManagerLogger
-) {
-    fun build(listContainer: NotificationListContainer): NotifViewManager {
-        return NotifViewManager(listContainer, viewBarn, logger)
-    }
-}
-
-private fun isAttachedToRootEntry(entry: ListEntry?): Boolean {
-    return when (entry) {
-        null -> false
-        ROOT_ENTRY -> true
-        else -> isAttachedToRootEntry(entry.parent)
-    }
-}
-
-private interface Node {
-    val entry: ListEntry
-    val nodeParent: ParentNode?
-}
-
-private interface ParentNode : Node {
-    fun getChildViewAt(index: Int): View?
-    fun addChildAt(child: RowNode, index: Int)
-    fun moveChild(child: RowNode, index: Int)
-    fun removeChild(child: RowNode, isTransfer: Boolean)
-}
-
-private interface RowNode : Node {
-    val view: ExpandableNotificationRow
-    override var nodeParent: ParentNode?
-}
-
-private class RootWrapper(
-    private val listContainer: NotificationListContainer
-) : ParentNode {
-    override val entry: ListEntry = ROOT_ENTRY
-    override val nodeParent: ParentNode? = null
-
-    override fun getChildViewAt(index: Int): View? {
-        return listContainer.getContainerChildAt(index)
-    }
-
-    override fun addChildAt(child: RowNode, index: Int) {
-        listContainer.addContainerViewAt(child.view, index)
-    }
-
-    override fun moveChild(child: RowNode, index: Int) {
-        listContainer.changeViewPosition(child.view, index)
-    }
-
-    override fun removeChild(child: RowNode, isTransfer: Boolean) {
-        if (isTransfer) {
-            listContainer.setChildTransferInProgress(true)
-        }
-        listContainer.removeContainerView(child.view)
-        if (isTransfer) {
-            listContainer.setChildTransferInProgress(false)
-        }
-    }
-}
-
-private class GroupWrapper(
-    override val entry: GroupEntry,
-    override val view: ExpandableNotificationRow,
-    val listContainer: NotificationListContainer
-) : RowNode, ParentNode {
-
-    override var nodeParent: ParentNode? = null
-
-    override fun getChildViewAt(index: Int): View? {
-        return view.getChildNotificationAt(index)
-    }
-
-    override fun addChildAt(child: RowNode, index: Int) {
-        view.addChildNotification(child.view, index)
-        listContainer.notifyGroupChildAdded(child.view)
-    }
-
-    override fun moveChild(child: RowNode, index: Int) {
-        view.removeChildNotification(child.view)
-        view.addChildNotification(child.view, index)
-    }
-
-    override fun removeChild(child: RowNode, isTransfer: Boolean) {
-        view.removeChildNotification(child.view)
-        if (isTransfer) {
-            listContainer.notifyGroupChildRemoved(child.view, view)
-        }
-    }
-}
-
-private class RowWrapper(
-    override val entry: NotificationEntry,
-    override val view: ExpandableNotificationRow
-) : RowNode {
-    override var nodeParent: ParentNode? = null
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt
new file mode 100644
index 0000000..e812494
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.render
+
+import android.view.View
+import com.android.systemui.statusbar.notification.row.ExpandableView
+import com.android.systemui.statusbar.notification.stack.NotificationListContainer
+
+/**
+ * Temporary wrapper around [NotificationListContainer], for use by [ShadeViewDiffer]. Long term,
+ * we should just modify NLC to implement the NodeController interface.
+ */
+class RootNodeController(
+    private val listContainer: NotificationListContainer
+) : NodeController {
+    override val nodeLabel: String = "<root>"
+    override val view: View = listContainer as View
+
+    override fun getChildAt(index: Int): View? {
+        return listContainer.getContainerChildAt(index)
+    }
+
+    override fun getChildCount(): Int {
+        return listContainer.containerChildCount
+    }
+
+    override fun addChildAt(child: NodeController, index: Int) {
+        listContainer.addContainerViewAt(child.view, index)
+    }
+
+    override fun moveChildTo(child: NodeController, index: Int) {
+        listContainer.changeViewPosition(child.view as ExpandableView, index)
+    }
+
+    override fun removeChild(child: NodeController, isTransfer: Boolean) {
+        if (isTransfer) {
+            listContainer.setChildTransferInProgress(true)
+        }
+        listContainer.removeContainerView(child.view)
+        if (isTransfer) {
+            listContainer.setChildTransferInProgress(false)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
new file mode 100644
index 0000000..019520f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
@@ -0,0 +1,221 @@
+/*
+ * 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.render
+
+import android.annotation.MainThread
+import android.view.View
+import com.android.systemui.util.kotlin.transform
+
+/**
+ * Given a "spec" that describes a "tree" of views, adds and removes views from the
+ * [rootController] and its children until the actual tree matches the spec.
+ *
+ * Every node in the spec tree must specify both a view and its associated [NodeController].
+ * Commands to add/remove/reorder children are sent to the controller. How the controller
+ * interprets these commands is left to its own discretion -- it might add them directly to its
+ * associated view or to some subview container.
+ *
+ * It's possible for nodes to mix "unmanaged" views in alongside managed ones within the same
+ * container. In this case, whenever the differ runs it will move all unmanaged views to the end
+ * of the node's child list.
+ */
+@MainThread
+class ShadeViewDiffer(
+    rootController: NodeController,
+    private val logger: ShadeViewDifferLogger
+) {
+    private val rootNode = ShadeNode(rootController)
+    private val nodes = mutableMapOf(rootController to rootNode)
+    private val views = mutableMapOf<View, ShadeNode>()
+
+    /**
+     * Adds and removes views from the root (and its children) until their structure matches the
+     * provided [spec]. The root node of the spec must match the root controller passed to the
+     * differ's constructor.
+     */
+    fun applySpec(spec: NodeSpec) {
+        val specMap = treeToMap(spec)
+
+        if (spec.controller != rootNode.controller) {
+            throw IllegalArgumentException("Tree root ${spec.controller.nodeLabel} does not " +
+                    "match own root at ${rootNode.label}")
+        }
+
+        detachChildren(rootNode, specMap)
+        attachChildren(rootNode, specMap)
+    }
+
+    /**
+     * If [view] is managed by this differ, then returns the label of the view's controller.
+     * Otherwise returns View.toString().
+     *
+     * For debugging purposes.
+     */
+    fun getViewLabel(view: View): String {
+        return views[view]?.label ?: view.toString()
+    }
+
+    private fun detachChildren(
+        parentNode: ShadeNode,
+        specMap: Map<NodeController, NodeSpec>
+    ) {
+        val parentSpec = specMap[parentNode.controller]
+
+        for (i in parentNode.getChildCount() - 1 downTo 0) {
+            val childView = parentNode.getChildAt(i)
+            views[childView]?.let { childNode ->
+                val childSpec = specMap[childNode.controller]
+
+                maybeDetachChild(parentNode, parentSpec, childNode, childSpec)
+
+                if (childNode.controller.getChildCount() > 0) {
+                    detachChildren(childNode, specMap)
+                }
+            }
+        }
+    }
+
+    private fun maybeDetachChild(
+        parentNode: ShadeNode,
+        parentSpec: NodeSpec?,
+        childNode: ShadeNode,
+        childSpec: NodeSpec?
+    ) {
+        val newParentNode = transform(childSpec?.parent) { getNode(it) }
+
+        if (newParentNode != parentNode) {
+            val childCompletelyRemoved = newParentNode == null
+
+            if (childCompletelyRemoved) {
+                nodes.remove(childNode.controller)
+                views.remove(childNode.controller.view)
+            }
+
+            if (childCompletelyRemoved && parentSpec == null) {
+                // If both the child and the parent are being removed at the same time, then
+                // keep the child attached to the parent for animation purposes
+                logger.logSkippingDetach(childNode.label, parentNode.label)
+            } else {
+                logger.logDetachingChild(
+                        childNode.label,
+                        !childCompletelyRemoved,
+                        parentNode.label,
+                        newParentNode?.label)
+                parentNode.removeChild(childNode, !childCompletelyRemoved)
+                childNode.parent = null
+            }
+        }
+    }
+
+    private fun attachChildren(
+        parentNode: ShadeNode,
+        specMap: Map<NodeController, NodeSpec>
+    ) {
+        val parentSpec = checkNotNull(specMap[parentNode.controller])
+
+        for ((index, childSpec) in parentSpec.children.withIndex()) {
+            val currView = parentNode.getChildAt(index)
+            val childNode = getNode(childSpec)
+
+            if (childNode.view != currView) {
+
+                when (childNode.parent) {
+                    null -> {
+                        // A new child (either newly created or coming from some other parent)
+                        logger.logAttachingChild(childNode.label, parentNode.label)
+                        parentNode.addChildAt(childNode, index)
+                        childNode.parent = parentNode
+                    }
+                    parentNode -> {
+                        // A pre-existing child, just in the wrong position. Move it into place
+                        logger.logMovingChild(childNode.label, parentNode.label, index)
+                        parentNode.moveChildTo(childNode, index)
+                    }
+                    else -> {
+                        // Error: child still has a parent. We should have detached it in the
+                        // previous step.
+                        throw IllegalStateException("Child ${childNode.label} should have " +
+                                "parent ${parentNode.label} but is actually " +
+                                "${childNode.parent?.label}")
+                    }
+                }
+            }
+
+            if (childSpec.children.isNotEmpty()) {
+                attachChildren(childNode, specMap)
+            }
+        }
+    }
+
+    private fun getNode(spec: NodeSpec): ShadeNode {
+        var node = nodes[spec.controller]
+        if (node == null) {
+            node = ShadeNode(spec.controller)
+            nodes[node.controller] = node
+            views[node.view] = node
+        }
+        return node
+    }
+
+    private fun treeToMap(tree: NodeSpec): Map<NodeController, NodeSpec> {
+        val map = mutableMapOf<NodeController, NodeSpec>()
+
+        registerNodes(tree, map)
+
+        return map
+    }
+
+    private fun registerNodes(node: NodeSpec, map: MutableMap<NodeController, NodeSpec>) {
+        if (map.containsKey(node.controller)) {
+            throw RuntimeException("Node ${node.controller.nodeLabel} appears more than once")
+        }
+        map[node.controller] = node
+
+        if (node.children.isNotEmpty()) {
+            for (child in node.children) {
+                registerNodes(child, map)
+            }
+        }
+    }
+}
+
+private class ShadeNode(
+    val controller: NodeController
+) {
+    val view = controller.view
+
+    var parent: ShadeNode? = null
+
+    val label: String
+        get() = controller.nodeLabel
+
+    fun getChildAt(index: Int): View? = controller.getChildAt(index)
+
+    fun getChildCount(): Int = controller.getChildCount()
+
+    fun addChildAt(child: ShadeNode, index: Int) {
+        controller.addChildAt(child.controller, index)
+    }
+
+    fun moveChildTo(child: ShadeNode, index: Int) {
+        controller.moveChildTo(child.controller, index)
+    }
+
+    fun removeChild(child: ShadeNode, isTransfer: Boolean) {
+        controller.removeChild(child.controller, isTransfer)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewManagerLogger.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
index 3d56126..19e156f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewManagerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
@@ -21,7 +21,7 @@
 import com.android.systemui.log.dagger.NotificationLog
 import javax.inject.Inject
 
-class NotifViewManagerLogger @Inject constructor(
+class ShadeViewDifferLogger @Inject constructor(
     @NotificationLog private val buffer: LogBuffer
 ) {
     fun logDetachingChild(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
new file mode 100644
index 0000000..118ff4a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.render
+
+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.collection.ShadeListBuilder
+import com.android.systemui.statusbar.notification.stack.NotificationListContainer
+import com.android.systemui.statusbar.phone.NotificationIconAreaController
+import java.lang.RuntimeException
+import javax.inject.Inject
+
+/**
+ * Responsible for building and applying the "shade node spec": the list (tree) of things that
+ * currently populate the notification shade.
+ */
+class ShadeViewManager constructor(
+    listContainer: NotificationListContainer,
+    logger: ShadeViewDifferLogger,
+    private val viewBarn: NotifViewBarn,
+    private val notificationIconAreaController: NotificationIconAreaController
+) {
+    private val rootController = RootNodeController(listContainer)
+    private val viewDiffer = ShadeViewDiffer(rootController, logger)
+
+    fun attach(listBuilder: ShadeListBuilder) {
+        listBuilder.setOnRenderListListener(::onNewNotifTree)
+    }
+
+    private fun onNewNotifTree(tree: List<ListEntry>) {
+        viewDiffer.applySpec(buildTree(tree))
+    }
+
+    private fun buildTree(notifList: List<ListEntry>): NodeSpec {
+        val root = NodeSpecImpl(null, rootController)
+
+        for (entry in notifList) {
+            // TODO: Add section header logic here
+            root.children.add(buildNotifNode(entry, root))
+        }
+
+        notificationIconAreaController.updateNotificationIcons(notifList)
+        return root
+    }
+
+    private fun buildNotifNode(entry: ListEntry, parent: NodeSpec): NodeSpec {
+        return when (entry) {
+            is NotificationEntry -> {
+                NodeSpecImpl(parent, viewBarn.requireView(entry))
+            }
+            is GroupEntry -> {
+                val groupNode = NodeSpecImpl(
+                        parent,
+                        viewBarn.requireView(checkNotNull(entry.summary)))
+
+                for (childEntry in entry.children) {
+                    groupNode.children.add(buildNotifNode(childEntry, groupNode))
+                }
+
+                groupNode
+            }
+            else -> {
+                throw RuntimeException("Unexpected entry: $entry")
+            }
+        }
+    }
+}
+
+class ShadeViewManagerFactory @Inject constructor(
+    private val logger: ShadeViewDifferLogger,
+    private val viewBarn: NotifViewBarn,
+    private val notificationIconAreaController: NotificationIconAreaController
+) {
+    fun create(listContainer: NotificationListContainer): ShadeViewManager {
+        return ShadeViewManager(listContainer, logger, viewBarn, notificationIconAreaController)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 046dc3c8..6d01324 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -28,9 +28,11 @@
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.R;
 import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.UiBackground;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.settings.CurrentUserContextTracker;
 import com.android.systemui.statusbar.FeatureFlags;
@@ -40,14 +42,16 @@
 import com.android.systemui.statusbar.notification.ForegroundServiceDismissalFeatureController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
+import com.android.systemui.statusbar.notification.collection.coordinator.VisualStabilityCoordinator;
 import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
-import com.android.systemui.statusbar.notification.collection.inflation.OnDismissCallbackImpl;
+import com.android.systemui.statusbar.notification.collection.inflation.OnUserInteractionCallbackImpl;
+import com.android.systemui.statusbar.notification.collection.legacy.OnUserInteractionCallbackImplLegacy;
+import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
@@ -61,7 +65,7 @@
 import com.android.systemui.statusbar.notification.row.ChannelEditorDialogController;
 import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
-import com.android.systemui.statusbar.notification.row.OnDismissCallback;
+import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
 import com.android.systemui.statusbar.notification.row.PriorityOnboardingDialogController;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.StatusBar;
@@ -71,7 +75,6 @@
 import java.util.concurrent.Executor;
 
 import javax.inject.Provider;
-import javax.inject.Singleton;
 
 import dagger.Binds;
 import dagger.Lazy;
@@ -84,7 +87,7 @@
 @Module
 public interface NotificationsModule {
     /** Provides an instance of {@link NotificationEntryManager} */
-    @Singleton
+    @SysUISingleton
     @Provides
     static NotificationEntryManager provideNotificationEntryManager(
             NotificationEntryManagerLogger logger,
@@ -111,11 +114,10 @@
     }
 
     /** Provides an instance of {@link NotificationGutsManager} */
-    @Singleton
+    @SysUISingleton
     @Provides
     static NotificationGutsManager provideNotificationGutsManager(
             Context context,
-            VisualStabilityManager visualStabilityManager,
             Lazy<StatusBar> statusBarLazy,
             @Main Handler mainHandler,
             @Background Handler bgHandler,
@@ -129,10 +131,10 @@
             Provider<PriorityOnboardingDialogController.Builder> builderProvider,
             AssistantFeedbackController assistantFeedbackController,
             BubbleController bubbleController,
-            UiEventLogger uiEventLogger) {
+            UiEventLogger uiEventLogger,
+            OnUserInteractionCallback onUserInteractionCallback) {
         return new NotificationGutsManager(
                 context,
-                visualStabilityManager,
                 statusBarLazy,
                 mainHandler,
                 bgHandler,
@@ -146,19 +148,28 @@
                 builderProvider,
                 assistantFeedbackController,
                 bubbleController,
-                uiEventLogger);
+                uiEventLogger,
+                onUserInteractionCallback);
     }
 
     /** Provides an instance of {@link VisualStabilityManager} */
-    @Singleton
+    @SysUISingleton
     @Provides
     static VisualStabilityManager provideVisualStabilityManager(
-            NotificationEntryManager notificationEntryManager, Handler handler) {
-        return new VisualStabilityManager(notificationEntryManager, handler);
+            FeatureFlags featureFlags,
+            NotificationEntryManager notificationEntryManager,
+            Handler handler,
+            StatusBarStateController statusBarStateController,
+            WakefulnessLifecycle wakefulnessLifecycle) {
+        return new VisualStabilityManager(
+                notificationEntryManager,
+                handler,
+                statusBarStateController,
+                wakefulnessLifecycle);
     }
 
     /** Provides an instance of {@link NotificationLogger} */
-    @Singleton
+    @SysUISingleton
     @Provides
     static NotificationLogger provideNotificationLogger(
             NotificationListener notificationListener,
@@ -177,14 +188,14 @@
     }
 
     /** Provides an instance of {@link NotificationPanelLogger} */
-    @Singleton
+    @SysUISingleton
     @Provides
     static NotificationPanelLogger provideNotificationPanelLogger() {
         return new NotificationPanelLoggerImpl();
     }
 
     /** Provides an instance of {@link NotificationBlockingHelperManager} */
-    @Singleton
+    @SysUISingleton
     @Provides
     static NotificationBlockingHelperManager provideNotificationBlockingHelperManager(
             Context context,
@@ -196,7 +207,7 @@
     }
 
     /** Initializes the notification data pipeline (can be disabled via config). */
-    @Singleton
+    @SysUISingleton
     @Provides
     static NotificationsController provideNotificationsController(
             Context context,
@@ -213,7 +224,7 @@
      * Provide the active notification collection managing the notifications to render.
      */
     @Provides
-    @Singleton
+    @SysUISingleton
     static CommonNotifCollection provideCommonNotifCollection(
             FeatureFlags featureFlags,
             Lazy<NotifPipeline> pipeline,
@@ -226,21 +237,28 @@
      * from the notification shade or it gets auto-cancelled by click.
      */
     @Provides
-    @Singleton
-    static OnDismissCallback provideOnDismissCallback(
+    @SysUISingleton
+    static OnUserInteractionCallback provideOnUserInteractionCallback(
             FeatureFlags featureFlags,
             HeadsUpManager headsUpManager,
             StatusBarStateController statusBarStateController,
             Lazy<NotifPipeline> pipeline,
             Lazy<NotifCollection> notifCollection,
-            NotificationEntryManager entryManager) {
+            Lazy<VisualStabilityCoordinator> visualStabilityCoordinator,
+            NotificationEntryManager entryManager,
+            VisualStabilityManager visualStabilityManager) {
         return featureFlags.isNewNotifPipelineRenderingEnabled()
-                ? new OnDismissCallbackImpl(
-                        pipeline.get(), notifCollection.get(), headsUpManager,
-                        statusBarStateController)
-                : new com.android.systemui.statusbar.notification.collection
-                        .legacy.OnDismissCallbackImpl(
-                                entryManager, headsUpManager, statusBarStateController);
+                ? new OnUserInteractionCallbackImpl(
+                        pipeline.get(),
+                        notifCollection.get(),
+                        headsUpManager,
+                        statusBarStateController,
+                        visualStabilityCoordinator.get())
+                : new OnUserInteractionCallbackImplLegacy(
+                        entryManager,
+                        headsUpManager,
+                        statusBarStateController,
+                        visualStabilityManager);
     }
 
     /** */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
index 6e4fcd5..6460892 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.notification.init
 
 import android.service.notification.StatusBarNotification
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption
 import com.android.systemui.statusbar.FeatureFlags
 import com.android.systemui.statusbar.NotificationListener
@@ -26,10 +27,11 @@
 import com.android.systemui.statusbar.notification.NotificationEntryManager
 import com.android.systemui.statusbar.notification.NotificationListController
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.TargetSdkResolver
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl
 import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer
-import com.android.systemui.statusbar.notification.collection.TargetSdkResolver
 import com.android.systemui.statusbar.notification.interruption.HeadsUpController
+import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
 import com.android.systemui.statusbar.notification.row.NotifBindPipelineInitializer
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
 import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper
@@ -37,14 +39,12 @@
 import com.android.systemui.statusbar.phone.StatusBar
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.statusbar.policy.HeadsUpManager
-import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
 import com.android.systemui.statusbar.policy.RemoteInputUriController
 import dagger.Lazy
 import java.io.FileDescriptor
 import java.io.PrintWriter
 import java.util.Optional
 import javax.inject.Inject
-import javax.inject.Singleton
 
 /**
  * Master controller for all notifications-related work
@@ -53,7 +53,7 @@
  * Once we migrate away from the need for such things, this class becomes primarily a place to do
  * any initialization work that notifications require.
  */
-@Singleton
+@SysUISingleton
 class NotificationsControllerImpl @Inject constructor(
     private val featureFlags: FeatureFlags,
     private val notificationListener: NotificationListener,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt
index 0fd865b..f8d6c6d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt
@@ -20,6 +20,7 @@
 import android.media.MediaMetadata
 import android.provider.Settings
 import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.statusbar.NotificationMediaManager
@@ -30,13 +31,12 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.tuner.TunerService
 import javax.inject.Inject
-import javax.inject.Singleton
 
 /**
  * A class that automatically creates heads up for important notification when bypassing the
  * lockscreen
  */
-@Singleton
+@SysUISingleton
 class BypassHeadsUpNotifier @Inject constructor(
     private val context: Context,
     private val bypassController: KeyguardBypassController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpController.java
index 9b6ae9a..4d56e60 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpController.java
@@ -24,25 +24,25 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Controller class for old pipeline heads up logic. It listens to {@link NotificationEntryManager}
  * entry events and appropriately binds or unbinds the heads up view and promotes it to the top
  * of the screen.
  */
-@Singleton
+@SysUISingleton
 public class HeadsUpController {
     private final HeadsUpViewBinder mHeadsUpViewBinder;
     private final NotificationInterruptStateProvider mInterruptStateProvider;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java
index ff13995..ffec367 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java
@@ -24,6 +24,7 @@
 import androidx.core.os.CancellationSignal;
 
 import com.android.internal.util.NotificationMessagingUtil;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.coordinator.HeadsUpCoordinator;
@@ -34,7 +35,6 @@
 import java.util.Map;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Wrapper around heads up view binding logic. {@link HeadsUpViewBinder} is responsible for
@@ -44,7 +44,7 @@
  * TODO: This should be moved into {@link HeadsUpCoordinator} when the old pipeline is deprecated
  * (i.e. when {@link HeadsUpController} is removed).
  */
-@Singleton
+@SysUISingleton
 public class HeadsUpViewBinder {
     private final RowContentBindStage mStage;
     private final NotificationMessagingUtil mNotificationMessagingUtil;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index 71f6dac..433d5e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -32,6 +32,7 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.StatusBarState;
@@ -44,12 +45,11 @@
 import java.util.List;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Provides heads-up and pulsing state for notification entries.
  */
-@Singleton
+@SysUISingleton
 public class NotificationInterruptStateProviderImpl implements NotificationInterruptStateProvider {
     private static final String TAG = "InterruptionStateProvider";
     private static final boolean DEBUG = true; //false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
index d32d09d..743bf33 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
@@ -35,6 +35,7 @@
 import com.android.internal.widget.MessagingGroup
 import com.android.settingslib.notification.ConversationIconFactory
 import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.NotificationPersonExtractorPlugin
@@ -48,7 +49,6 @@
 import java.util.ArrayDeque
 import java.util.concurrent.Executor
 import javax.inject.Inject
-import javax.inject.Singleton
 
 private const val MAX_STORED_INACTIVE_PEOPLE = 10
 
@@ -58,7 +58,7 @@
     fun isPersonNotification(sbn: StatusBarNotification): Boolean
 }
 
-@Singleton
+@SysUISingleton
 class NotificationPersonExtractorPluginBoundary @Inject constructor(
     extensionController: ExtensionController
 ) : NotificationPersonExtractor {
@@ -87,7 +87,7 @@
             plugin?.isPersonNotification(sbn) ?: false
 }
 
-@Singleton
+@SysUISingleton
 class PeopleHubDataSourceImpl @Inject constructor(
     private val notificationEntryManager: NotificationEntryManager,
     private val extractor: NotificationPersonExtractor,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubViewController.kt
index 7f42fe0..55bd77f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubViewController.kt
@@ -23,10 +23,10 @@
 import android.os.UserHandle
 import android.provider.Settings
 import android.view.View
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.ActivityStarter
 import javax.inject.Inject
-import javax.inject.Singleton
 
 /** Boundary between the View and PeopleHub, as seen by the View. */
 interface PeopleHubViewAdapter {
@@ -68,7 +68,7 @@
  *
  * @param dataSource PeopleHub data pipeline.
  */
-@Singleton
+@SysUISingleton
 class PeopleHubViewAdapterImpl @Inject constructor(
     private val dataSource: DataSource<@JvmSuppressWildcards PeopleHubViewModelFactory>
 ) : PeopleHubViewAdapter {
@@ -99,7 +99,7 @@
  * This class serves as the glue between the View layer (which depends on
  * [PeopleHubViewBoundary]) and the Data layer (which produces [PeopleHubModel]s).
  */
-@Singleton
+@SysUISingleton
 class PeopleHubViewModelFactoryDataSourceImpl @Inject constructor(
     private val activityStarter: ActivityStarter,
     private val dataSource: DataSource<@JvmSuppressWildcards PeopleHubModel>
@@ -151,7 +151,7 @@
     }
 }
 
-@Singleton
+@SysUISingleton
 class PeopleHubSettingChangeDataSourceImpl @Inject constructor(
     @Main private val handler: Handler,
     context: Context
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt
index d36627f..1ac2cb5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt
@@ -19,6 +19,7 @@
 import android.annotation.IntDef
 import android.service.notification.NotificationListenerService.Ranking
 import android.service.notification.StatusBarNotification
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.PeopleNotificationType
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_FULL_PERSON
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_IMPORTANT_PERSON
@@ -26,7 +27,6 @@
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_PERSON
 import com.android.systemui.statusbar.phone.NotificationGroupManager
 import javax.inject.Inject
-import javax.inject.Singleton
 import kotlin.math.max
 
 interface PeopleNotificationIdentifier {
@@ -59,7 +59,7 @@
     }
 }
 
-@Singleton
+@SysUISingleton
 class PeopleNotificationIdentifierImpl @Inject constructor(
     private val personExtractor: NotificationPersonExtractor,
     private val groupManager: NotificationGroupManager
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/AppOpsInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/AppOpsInfo.java
deleted file mode 100644
index 28c53dc..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/AppOpsInfo.java
+++ /dev/null
@@ -1,214 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.notification.row;
-
-import android.app.AppOpsManager;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.graphics.drawable.Drawable;
-import android.service.notification.StatusBarNotification;
-import android.util.ArraySet;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.accessibility.AccessibilityEvent;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.R;
-
-/**
- * The guts of a notification revealed when performing a long press.
- */
-public class AppOpsInfo extends LinearLayout implements NotificationGuts.GutsContent {
-    private static final String TAG = "AppOpsGuts";
-
-    private PackageManager mPm;
-
-    private String mPkg;
-    private String mAppName;
-    private int mAppUid;
-    private StatusBarNotification mSbn;
-    private ArraySet<Integer> mAppOps;
-    private MetricsLogger mMetricsLogger;
-    private OnSettingsClickListener mOnSettingsClickListener;
-    private NotificationGuts mGutsContainer;
-    private UiEventLogger mUiEventLogger;
-
-    private OnClickListener mOnOk = v -> {
-        mGutsContainer.closeControls(v, false);
-    };
-
-    public AppOpsInfo(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public interface OnSettingsClickListener {
-        void onClick(View v, String pkg, int uid, ArraySet<Integer> ops);
-    }
-
-    public void bindGuts(final PackageManager pm,
-            final OnSettingsClickListener onSettingsClick,
-            final StatusBarNotification sbn,
-            final UiEventLogger uiEventLogger,
-            ArraySet<Integer> activeOps) {
-        mPkg = sbn.getPackageName();
-        mSbn = sbn;
-        mPm = pm;
-        mAppName = mPkg;
-        mOnSettingsClickListener = onSettingsClick;
-        mAppOps = activeOps;
-        mUiEventLogger = uiEventLogger;
-
-        bindHeader();
-        bindPrompt();
-        bindButtons();
-
-        logUiEvent(NotificationAppOpsEvent.NOTIFICATION_APP_OPS_OPEN);
-        mMetricsLogger = new MetricsLogger();
-        mMetricsLogger.visibility(MetricsEvent.APP_OPS_GUTS, true);
-    }
-
-    private void bindHeader() {
-        // Package name
-        Drawable pkgicon = null;
-        ApplicationInfo info;
-        try {
-            info = mPm.getApplicationInfo(mPkg,
-                    PackageManager.MATCH_UNINSTALLED_PACKAGES
-                            | PackageManager.MATCH_DISABLED_COMPONENTS
-                            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
-                            | PackageManager.MATCH_DIRECT_BOOT_AWARE);
-            if (info != null) {
-                mAppUid = mSbn.getUid();
-                mAppName = String.valueOf(mPm.getApplicationLabel(info));
-                pkgicon = mPm.getApplicationIcon(info);
-            }
-        } catch (PackageManager.NameNotFoundException e) {
-            // app is gone, just show package name and generic icon
-            pkgicon = mPm.getDefaultActivityIcon();
-        }
-        ((ImageView) findViewById(R.id.pkgicon)).setImageDrawable(pkgicon);
-        ((TextView) findViewById(R.id.pkgname)).setText(mAppName);
-    }
-
-    private void bindPrompt() {
-        final TextView prompt = findViewById(R.id.prompt);
-        prompt.setText(getPrompt());
-    }
-
-    private void bindButtons() {
-        View settings =  findViewById(R.id.settings);
-        settings.setOnClickListener((View view) -> {
-            mOnSettingsClickListener.onClick(view, mPkg, mAppUid, mAppOps);
-        });
-        TextView ok = findViewById(R.id.ok);
-        ok.setOnClickListener(mOnOk);
-        ok.setAccessibilityDelegate(mGutsContainer.getAccessibilityDelegate());
-    }
-
-    private String getPrompt() {
-        if (mAppOps == null || mAppOps.size() == 0) {
-            return "";
-        } else if (mAppOps.size() == 1) {
-            if (mAppOps.contains(AppOpsManager.OP_CAMERA)) {
-                return mContext.getString(R.string.appops_camera);
-            } else if (mAppOps.contains(AppOpsManager.OP_RECORD_AUDIO)) {
-                return mContext.getString(R.string.appops_microphone);
-            } else {
-                return mContext.getString(R.string.appops_overlay);
-            }
-        } else if (mAppOps.size() == 2) {
-            if (mAppOps.contains(AppOpsManager.OP_CAMERA)) {
-                if (mAppOps.contains(AppOpsManager.OP_RECORD_AUDIO)) {
-                    return mContext.getString(R.string.appops_camera_mic);
-                } else {
-                    return mContext.getString(R.string.appops_camera_overlay);
-                }
-            } else {
-                return mContext.getString(R.string.appops_mic_overlay);
-            }
-        } else {
-            return mContext.getString(R.string.appops_camera_mic_overlay);
-        }
-    }
-
-    @Override
-    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
-        super.onInitializeAccessibilityEvent(event);
-        if (mGutsContainer != null &&
-                event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
-            if (mGutsContainer.isExposed()) {
-                event.getText().add(mContext.getString(
-                        R.string.notification_channel_controls_opened_accessibility, mAppName));
-            } else {
-                event.getText().add(mContext.getString(
-                        R.string.notification_channel_controls_closed_accessibility, mAppName));
-            }
-        }
-    }
-
-    @Override
-    public void setGutsParent(NotificationGuts guts) {
-        mGutsContainer = guts;
-    }
-
-    @Override
-    public boolean willBeRemoved() {
-        return false;
-    }
-
-    @Override
-    public boolean shouldBeSaved() {
-        return false;
-    }
-
-    @Override
-    public boolean needsFalsingProtection() {
-        return false;
-    }
-
-    @Override
-    public View getContentView() {
-        return this;
-    }
-
-    @Override
-    public boolean handleCloseControls(boolean save, boolean force) {
-        logUiEvent(NotificationAppOpsEvent.NOTIFICATION_APP_OPS_CLOSE);
-        if (mMetricsLogger != null) {
-            mMetricsLogger.visibility(MetricsEvent.APP_OPS_GUTS, false);
-        }
-        return false;
-    }
-
-    @Override
-    public int getActualHeight() {
-        return getHeight();
-    }
-
-    private void logUiEvent(NotificationAppOpsEvent event) {
-        if (mSbn != null) {
-            mUiEventLogger.logWithInstanceId(event,
-                    mSbn.getUid(), mSbn.getPackageName(), mSbn.getInstanceId());
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
index 93db9cd..9bba7ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
@@ -37,12 +37,10 @@
 import android.view.WindowInsets.Type.statusBars
 import android.view.WindowManager
 import android.widget.TextView
-
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.R
-
+import com.android.systemui.dagger.SysUISingleton
 import javax.inject.Inject
-import javax.inject.Singleton
 
 private const val TAG = "ChannelDialogController"
 
@@ -58,7 +56,7 @@
  *   - the next 3 channels sorted alphabetically for that app   <on/off>
  *   -                                                          <on/off>
  */
-@Singleton
+@SysUISingleton
 class ChannelEditorDialogController @Inject constructor(
     c: Context,
     private val noMan: INotificationManager,
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 22d0357..9c09cba 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
@@ -84,8 +84,8 @@
 import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
 import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
 import com.android.systemui.statusbar.notification.NotificationUtils;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.logging.NotificationCounters;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
@@ -239,7 +239,7 @@
     private boolean mShowNoBackground;
     private ExpandableNotificationRow mNotificationParent;
     private OnExpandClickListener mOnExpandClickListener;
-    private View.OnClickListener mOnAppOpsClickListener;
+    private View.OnClickListener mOnAppClickListener;
     private View.OnClickListener mOnFeedbackClickListener;
 
     // Listener will be called when receiving a long click event.
@@ -323,7 +323,7 @@
     private View mGroupParentWhenDismissed;
     private boolean mShelfIconVisible;
     private boolean mAboveShelf;
-    private OnDismissCallback mOnDismissCallback;
+    private OnUserInteractionCallback mOnUserInteractionCallback;
     private boolean mIsLowPriority;
     private boolean mIsColorized;
     private boolean mUseIncreasedCollapsedHeight;
@@ -1139,7 +1139,6 @@
             items.add(NotificationMenuRow.createPartialConversationItem(mContext));
             items.add(NotificationMenuRow.createInfoItem(mContext));
             items.add(NotificationMenuRow.createSnoozeItem(mContext));
-            items.add(NotificationMenuRow.createAppOpsItem(mContext));
             mMenuRow.setMenuItems(items);
         }
         if (existed) {
@@ -1446,8 +1445,8 @@
         }
         dismiss(fromAccessibility);
         if (mEntry.isClearable()) {
-            if (mOnDismissCallback != null) {
-                mOnDismissCallback.onDismiss(mEntry, REASON_CANCEL);
+            if (mOnUserInteractionCallback != null) {
+                mOnUserInteractionCallback.onDismiss(mEntry, REASON_CANCEL);
             }
         }
     }
@@ -1464,10 +1463,6 @@
         return mIsBlockingHelperShowing && mNotificationTranslationFinished;
     }
 
-    void setOnDismissCallback(OnDismissCallback onDismissCallback) {
-        mOnDismissCallback = onDismissCallback;
-    }
-
     @Override
     public View getShelfTransformationTarget() {
         if (mIsSummaryWithChildren && !shouldShowPublic()) {
@@ -1598,11 +1593,11 @@
             RowContentBindStage rowContentBindStage,
             OnExpandClickListener onExpandClickListener,
             NotificationMediaManager notificationMediaManager,
-            CoordinateOnClickListener onAppOpsClickListener,
             CoordinateOnClickListener onFeedbackClickListener,
             FalsingManager falsingManager,
             StatusBarStateController statusBarStateController,
-            PeopleNotificationIdentifier peopleNotificationIdentifier) {
+            PeopleNotificationIdentifier peopleNotificationIdentifier,
+            OnUserInteractionCallback onUserInteractionCallback) {
         mAppName = appName;
         if (mMenuRow == null) {
             mMenuRow = new NotificationMenuRow(mContext, peopleNotificationIdentifier);
@@ -1619,7 +1614,6 @@
         mRowContentBindStage = rowContentBindStage;
         mOnExpandClickListener = onExpandClickListener;
         mMediaManager = notificationMediaManager;
-        setAppOpsOnClickListener(onAppOpsClickListener);
         setOnFeedbackClickListener(onFeedbackClickListener);
         mFalsingManager = falsingManager;
         mStatusbarStateController = statusBarStateController;
@@ -1627,6 +1621,7 @@
         for (NotificationContentView l : mLayouts) {
             l.setPeopleNotificationIdentifier(mPeopleNotificationIdentifier);
         }
+        mOnUserInteractionCallback = onUserInteractionCallback;
     }
 
     private void initDimens() {
@@ -1677,14 +1672,6 @@
         requestLayout();
     }
 
-    public void showAppOpsIcons(ArraySet<Integer> activeOps) {
-        if (mIsSummaryWithChildren) {
-            mChildrenContainer.showAppOpsIcons(activeOps);
-        }
-        mPrivateLayout.showAppOpsIcons(activeOps);
-        mPublicLayout.showAppOpsIcons(activeOps);
-    }
-
     public void showFeedbackIcon(boolean show) {
         if (mIsSummaryWithChildren) {
             mChildrenContainer.showFeedbackIcon(show);
@@ -1721,24 +1708,6 @@
         mPublicLayout.setRecentlyAudiblyAlerted(audiblyAlertedRecently);
     }
 
-    public View.OnClickListener getAppOpsOnClickListener() {
-        return mOnAppOpsClickListener;
-    }
-
-    void setAppOpsOnClickListener(CoordinateOnClickListener l) {
-        mOnAppOpsClickListener = v -> {
-            createMenu();
-            NotificationMenuRowPlugin provider = getProvider();
-            if (provider == null) {
-                return;
-            }
-            MenuItem menuItem = provider.getAppOpsMenuItem(mContext);
-            if (menuItem != null) {
-                l.onClick(this, v.getWidth() / 2, v.getHeight() / 2, menuItem);
-            }
-        };
-    }
-
     public View.OnClickListener getFeedbackOnClickListener() {
         return mOnFeedbackClickListener;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index 86a3271..f8bc2be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -22,21 +22,27 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import androidx.annotation.NonNull;
+
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.notification.collection.render.NodeController;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.row.dagger.AppName;
 import com.android.systemui.statusbar.notification.row.dagger.NotificationKey;
 import com.android.systemui.statusbar.notification.row.dagger.NotificationRowScope;
+import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.util.time.SystemClock;
 
+import java.util.List;
+
 import javax.inject.Inject;
 import javax.inject.Named;
 
@@ -44,8 +50,9 @@
  * Controller for {@link ExpandableNotificationRow}.
  */
 @NotificationRowScope
-public class ExpandableNotificationRowController {
+public class ExpandableNotificationRowController implements NodeController {
     private final ExpandableNotificationRow mView;
+    private final NotificationListContainer mListContainer;
     private final ActivatableNotificationViewController mActivatableNotificationViewController;
     private final NotificationMediaManager mMediaManager;
     private final PluginManager mPluginManager;
@@ -62,16 +69,16 @@
 
     private final ExpandableNotificationRow.ExpansionLogger mExpansionLogger =
             this::logNotificationExpansion;
-    private final ExpandableNotificationRow.CoordinateOnClickListener mOnAppOpsClickListener;
     private final ExpandableNotificationRow.CoordinateOnClickListener mOnFeedbackClickListener;
     private final NotificationGutsManager mNotificationGutsManager;
-    private final OnDismissCallback mOnDismissCallback;
+    private final OnUserInteractionCallback mOnUserInteractionCallback;
     private final FalsingManager mFalsingManager;
     private final boolean mAllowLongPress;
     private final PeopleNotificationIdentifier mPeopleNotificationIdentifier;
 
     @Inject
     public ExpandableNotificationRowController(ExpandableNotificationRow view,
+            NotificationListContainer listContainer,
             ActivatableNotificationViewController activatableNotificationViewController,
             NotificationMediaManager mediaManager, PluginManager pluginManager,
             SystemClock clock, @AppName String appName, @NotificationKey String notificationKey,
@@ -83,9 +90,10 @@
             StatusBarStateController statusBarStateController,
             NotificationGutsManager notificationGutsManager,
             @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
-            OnDismissCallback onDismissCallback, FalsingManager falsingManager,
+            OnUserInteractionCallback onUserInteractionCallback, FalsingManager falsingManager,
             PeopleNotificationIdentifier peopleNotificationIdentifier) {
         mView = view;
+        mListContainer = listContainer;
         mActivatableNotificationViewController = activatableNotificationViewController;
         mMediaManager = mediaManager;
         mPluginManager = pluginManager;
@@ -100,8 +108,7 @@
         mOnExpandClickListener = onExpandClickListener;
         mStatusBarStateController = statusBarStateController;
         mNotificationGutsManager = notificationGutsManager;
-        mOnDismissCallback = onDismissCallback;
-        mOnAppOpsClickListener = mNotificationGutsManager::openGuts;
+        mOnUserInteractionCallback = onUserInteractionCallback;
         mOnFeedbackClickListener = mNotificationGutsManager::openGuts;
         mAllowLongPress = allowLongPress;
         mFalsingManager = falsingManager;
@@ -123,13 +130,12 @@
                 mRowContentBindStage,
                 mOnExpandClickListener,
                 mMediaManager,
-                mOnAppOpsClickListener,
                 mOnFeedbackClickListener,
                 mFalsingManager,
                 mStatusBarStateController,
-                mPeopleNotificationIdentifier
+                mPeopleNotificationIdentifier,
+                mOnUserInteractionCallback
         );
-        mView.setOnDismissCallback(mOnDismissCallback);
         mView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
         if (mAllowLongPress) {
             mView.setLongPressListener((v, x, y, item) -> {
@@ -162,4 +168,52 @@
     private void logNotificationExpansion(String key, boolean userAction, boolean expanded) {
         mNotificationLogger.onExpansionChanged(key, userAction, expanded);
     }
+
+    @Override
+    @NonNull
+    public String getNodeLabel() {
+        return mView.getEntry().getKey();
+    }
+
+    @Override
+    @NonNull
+    public View getView() {
+        return mView;
+    }
+
+    @Override
+    public View getChildAt(int index) {
+        return mView.getChildNotificationAt(index);
+    }
+
+    @Override
+    public void addChildAt(NodeController child, int index) {
+        ExpandableNotificationRow childView = (ExpandableNotificationRow) child.getView();
+
+        mView.addChildNotification((ExpandableNotificationRow) child.getView());
+        mListContainer.notifyGroupChildAdded(childView);
+    }
+
+    @Override
+    public void moveChildTo(NodeController child, int index) {
+        ExpandableNotificationRow childView = (ExpandableNotificationRow) child.getView();
+        mView.removeChildNotification(childView);
+        mView.addChildNotification(childView, index);
+    }
+
+    @Override
+    public void removeChild(NodeController child, boolean isTransfer) {
+        ExpandableNotificationRow childView = (ExpandableNotificationRow) child.getView();
+
+        mView.removeChildNotification(childView);
+        if (!isTransfer) {
+            mListContainer.notifyGroupChildRemoved(childView, mView);
+        }
+    }
+
+    @Override
+    public int getChildCount() {
+        final List<ExpandableNotificationRow> mChildren = mView.getAttachedChildren();
+        return mChildren != null ? mChildren.size() : 0;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java
index 90d30dc..f693ebb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java
@@ -28,6 +28,7 @@
 import androidx.annotation.Nullable;
 import androidx.core.os.CancellationSignal;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
@@ -41,7 +42,6 @@
 import java.util.Set;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * {@link NotifBindPipeline} is responsible for converting notifications from their data form to
@@ -77,7 +77,7 @@
  * views and assumes that a row is given to it when it's inflated.
  */
 @MainThread
-@Singleton
+@SysUISingleton
 public final class NotifBindPipeline {
     private final Map<NotificationEntry, BindEntry> mBindEntries = new ArrayMap<>();
     private final NotifBindPipelineLogger mLogger;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManager.java
index a3ca084..51eb9f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManager.java
@@ -19,6 +19,7 @@
 import androidx.annotation.NonNull;
 import androidx.collection.ArraySet;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
 import java.util.ArrayList;
@@ -26,14 +27,13 @@
 import java.util.Set;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * A manager handling the error state of a notification when it encounters an exception while
  * inflating. We don't want to show these notifications to the user but may want to keep them
  * around for logging purposes.
  */
-@Singleton
+@SysUISingleton
 public class NotifInflationErrorManager {
 
     Set<NotificationEntry> mErroredNotifs = new ArraySet<>();
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 a7d83b3..9bcac11 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
@@ -36,6 +36,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.widget.ImageMessageConsumer;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.media.MediaDataManagerKt;
 import com.android.systemui.media.MediaFeatureFlag;
@@ -58,7 +59,6 @@
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 
@@ -66,7 +66,7 @@
  * {@link NotificationContentInflater} binds content to a {@link ExpandableNotificationRow} by
  * asynchronously building the content's {@link RemoteViews} and applying it to the row.
  */
-@Singleton
+@SysUISingleton
 @VisibleForTesting(visibility = PACKAGE)
 public class NotificationContentInflater implements NotificationRowContentBinder {
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 2986b9b..c7e44c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -1568,18 +1568,6 @@
         return header;
     }
 
-    public void showAppOpsIcons(ArraySet<Integer> activeOps) {
-        if (mContractedChild != null) {
-            mContractedWrapper.showAppOpsIcons(activeOps);
-        }
-        if (mExpandedChild != null) {
-            mExpandedWrapper.showAppOpsIcons(activeOps);
-        }
-        if (mHeadsUpChild != null) {
-            mHeadsUpWrapper.showAppOpsIcons(activeOps);
-        }
-    }
-
     public void showFeedbackIcon(boolean show) {
         if (mContractedChild != null) {
             mContractedWrapper.showFeedbackIcon(show);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index f543db7..7c7bb5c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -69,7 +69,6 @@
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.statusbar.notification.NotificationChannelHelper;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 
@@ -89,7 +88,7 @@
     private ShortcutManager mShortcutManager;
     private PackageManager mPm;
     private ConversationIconFactory mIconFactory;
-    private VisualStabilityManager mVisualStabilityManager;
+    private OnUserInteractionCallback mOnUserInteractionCallback;
     private Handler mMainHandler;
     private Handler mBgHandler;
     private BubbleController mBubbleController;
@@ -207,7 +206,7 @@
             ShortcutManager shortcutManager,
             PackageManager pm,
             INotificationManager iNotificationManager,
-            VisualStabilityManager visualStabilityManager,
+            OnUserInteractionCallback onUserInteractionCallback,
             String pkg,
             NotificationChannel notificationChannel,
             NotificationEntry entry,
@@ -224,7 +223,7 @@
             BubbleController bubbleController) {
         mSelectedAction = -1;
         mINotificationManager = iNotificationManager;
-        mVisualStabilityManager = visualStabilityManager;
+        mOnUserInteractionCallback = onUserInteractionCallback;
         mPackageName = pkg;
         mEntry = entry;
         mSbn = entry.getSbn();
@@ -513,7 +512,7 @@
                         mAppUid, mSelectedAction, mNotificationChannel));
         mEntry.markForUserTriggeredMovement(true);
         mMainHandler.postDelayed(
-                mVisualStabilityManager::temporarilyAllowReordering,
+                () -> mOnUserInteractionCallback.onImportanceChanged(mEntry),
                 StackStateAnimator.ANIMATION_DURATION_STANDARD);
     }
 
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 729b131..60074f6 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
@@ -60,7 +60,6 @@
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
 import com.android.systemui.statusbar.notification.AssistantFeedbackController;
 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.dagger.NotificationsModule;
@@ -88,10 +87,10 @@
 
     private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
     private final Context mContext;
-    private final VisualStabilityManager mVisualStabilityManager;
     private final AccessibilityManager mAccessibilityManager;
     private final HighPriorityProvider mHighPriorityProvider;
     private final ChannelEditorDialogController mChannelEditorDialogController;
+    private final OnUserInteractionCallback mOnUserInteractionCallback;
 
     // Dependencies:
     private final NotificationLockscreenUserManager mLockscreenUserManager =
@@ -129,8 +128,10 @@
     /**
      * Injected constructor. See {@link NotificationsModule}.
      */
-    public NotificationGutsManager(Context context, VisualStabilityManager visualStabilityManager,
-            Lazy<StatusBar> statusBarLazy, @Main Handler mainHandler, @Background Handler bgHandler,
+    public NotificationGutsManager(Context context,
+            Lazy<StatusBar> statusBarLazy,
+            @Main Handler mainHandler,
+            @Background Handler bgHandler,
             AccessibilityManager accessibilityManager,
             HighPriorityProvider highPriorityProvider,
             INotificationManager notificationManager,
@@ -141,9 +142,9 @@
             Provider<PriorityOnboardingDialogController.Builder> builderProvider,
             AssistantFeedbackController assistantFeedbackController,
             BubbleController bubbleController,
-            UiEventLogger uiEventLogger) {
+            UiEventLogger uiEventLogger,
+            OnUserInteractionCallback onUserInteractionCallback) {
         mContext = context;
-        mVisualStabilityManager = visualStabilityManager;
         mStatusBarLazy = statusBarLazy;
         mMainHandler = mainHandler;
         mBgHandler = bgHandler;
@@ -158,6 +159,7 @@
         mAssistantFeedbackController = assistantFeedbackController;
         mBubbleController = bubbleController;
         mUiEventLogger = uiEventLogger;
+        mOnUserInteractionCallback = onUserInteractionCallback;
     }
 
     public void setUpWithPresenter(NotificationPresenter presenter,
@@ -268,8 +270,6 @@
         try {
             if (gutsView instanceof NotificationSnooze) {
                 initializeSnoozeView(row, (NotificationSnooze) gutsView);
-            } else if (gutsView instanceof AppOpsInfo) {
-                initializeAppOpsInfo(row, (AppOpsInfo) gutsView);
             } else if (gutsView instanceof NotificationInfo) {
                 initializeNotificationInfo(row, (NotificationInfo) gutsView);
             } else if (gutsView instanceof NotificationConversationInfo) {
@@ -309,36 +309,6 @@
     }
 
     /**
-     * Sets up the {@link AppOpsInfo} inside the notification row's guts.
-     *
-     * @param row view to set up the guts for
-     * @param appOpsInfoView view to set up/bind within {@code row}
-     */
-    private void initializeAppOpsInfo(
-            final ExpandableNotificationRow row,
-            AppOpsInfo appOpsInfoView) {
-        NotificationGuts guts = row.getGuts();
-        StatusBarNotification sbn = row.getEntry().getSbn();
-        UserHandle userHandle = sbn.getUser();
-        PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext,
-                userHandle.getIdentifier());
-
-        AppOpsInfo.OnSettingsClickListener onSettingsClick =
-                (View v, String pkg, int uid, ArraySet<Integer> ops) -> {
-                    mUiEventLogger.logWithInstanceId(
-                            NotificationAppOpsEvent.NOTIFICATION_APP_OPS_SETTINGS_CLICK,
-                            sbn.getUid(), sbn.getPackageName(), sbn.getInstanceId());
-                    mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_OPS_GUTS_SETTINGS);
-                    guts.resetFalsingCheck();
-                    startAppOpsSettingsActivity(pkg, uid, ops, row);
-        };
-        if (!row.getEntry().mActiveAppOps.isEmpty()) {
-            appOpsInfoView.bindGuts(pmUser, onSettingsClick, sbn, mUiEventLogger,
-                    row.getEntry().mActiveAppOps);
-        }
-    }
-
-    /**
      * Sets up the {@link FeedbackInfo} inside the notification row's guts.
      *
      * @param row view to set up the guts for
@@ -395,7 +365,7 @@
         notificationInfoView.bindNotification(
                 pmUser,
                 mNotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 packageName,
                 row.getEntry().getChannel(),
@@ -506,7 +476,7 @@
                 mShortcutManager,
                 pmUser,
                 mNotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 packageName,
                 entry.getChannel(),
                 entry,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
index a6ba85f..7a976ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
@@ -60,7 +60,6 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
 import java.lang.annotation.Retention;
@@ -93,9 +92,9 @@
     private TextView mAutomaticDescriptionView;
 
     private INotificationManager mINotificationManager;
+    private OnUserInteractionCallback mOnUserInteractionCallback;
     private PackageManager mPm;
     private MetricsLogger mMetricsLogger;
-    private VisualStabilityManager mVisualStabilityManager;
     private ChannelEditorDialogController mChannelEditorDialogController;
 
     private String mPackageName;
@@ -119,6 +118,7 @@
     private boolean mIsAutomaticChosen;
     private boolean mIsSingleDefaultChannel;
     private boolean mIsNonblockable;
+    private NotificationEntry mEntry;
     private StatusBarNotification mSbn;
     private boolean mIsDeviceProvisioned;
 
@@ -188,7 +188,7 @@
     public void bindNotification(
             PackageManager pm,
             INotificationManager iNotificationManager,
-            VisualStabilityManager visualStabilityManager,
+            OnUserInteractionCallback onUserInteractionCallback,
             ChannelEditorDialogController channelEditorDialogController,
             String pkg,
             NotificationChannel notificationChannel,
@@ -204,11 +204,12 @@
             throws RemoteException {
         mINotificationManager = iNotificationManager;
         mMetricsLogger = Dependency.get(MetricsLogger.class);
-        mVisualStabilityManager = visualStabilityManager;
+        mOnUserInteractionCallback = onUserInteractionCallback;
         mChannelEditorDialogController = channelEditorDialogController;
         mPackageName = pkg;
         mUniqueChannelsInRow = uniqueChannelsInRow;
         mNumUniqueChannelsInRow = uniqueChannelsInRow.size();
+        mEntry = entry;
         mSbn = entry.getSbn();
         mPm = pm;
         mAppSettingsClickListener = onAppSettingsClick;
@@ -438,7 +439,7 @@
                     new UpdateImportanceRunnable(mINotificationManager, mPackageName, mAppUid,
                             mNumUniqueChannelsInRow == 1 ? mSingleNotificationChannel : null,
                             mStartingChannelImportance, newImportance, mIsAutomaticChosen));
-            mVisualStabilityManager.temporarilyAllowReordering();
+            mOnUserInteractionCallback.onImportanceChanged(mEntry);
         }
     }
 
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 d264af9..205cecc 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
@@ -76,7 +76,6 @@
     private Context mContext;
     private FrameLayout mMenuContainer;
     private NotificationMenuItem mInfoItem;
-    private MenuItem mAppOpsItem;
     private MenuItem mFeedbackItem;
     private MenuItem mSnoozeItem;
     private ArrayList<MenuItem> mLeftMenuItems;
@@ -138,11 +137,6 @@
     }
 
     @Override
-    public MenuItem getAppOpsMenuItem(Context context) {
-        return mAppOpsItem;
-    }
-
-    @Override
     public MenuItem getFeedbackMenuItem(Context context) {
         return mFeedbackItem;
     }
@@ -264,7 +258,6 @@
             // Only show snooze for non-foreground notifications, and if the setting is on
             mSnoozeItem = createSnoozeItem(mContext);
         }
-        mAppOpsItem = createAppOpsItem(mContext);
         mFeedbackItem = createFeedbackItem(mContext);
         NotificationEntry entry = mParent.getEntry();
         int personNotifType = mPeopleNotificationIdentifier
@@ -281,7 +274,6 @@
             mRightMenuItems.add(mSnoozeItem);
         }
         mRightMenuItems.add(mInfoItem);
-        mRightMenuItems.add(mAppOpsItem);
         mRightMenuItems.add(mFeedbackItem);
         mLeftMenuItems.addAll(mRightMenuItems);
 
@@ -690,14 +682,6 @@
                 R.drawable.ic_settings);
     }
 
-    static MenuItem createAppOpsItem(Context context) {
-        AppOpsInfo appOpsContent = (AppOpsInfo) LayoutInflater.from(context).inflate(
-                R.layout.app_ops_info, null, false);
-        MenuItem info = new NotificationMenuItem(context, null, appOpsContent,
-                -1 /*don't show in slow swipe menu */);
-        return info;
-    }
-
     static MenuItem createFeedbackItem(Context context) {
         FeedbackInfo feedbackContent = (FeedbackInfo) LayoutInflater.from(context).inflate(
                 R.layout.feedback_info, null, false);
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
index df8653c..111b575 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
@@ -16,7 +16,7 @@
 
 package com.android.systemui.statusbar.notification.row;
 
-import javax.inject.Singleton;
+import com.android.systemui.dagger.SysUISingleton;
 
 import dagger.Binds;
 import dagger.Module;
@@ -30,7 +30,7 @@
      * Provides notification row content binder instance.
      */
     @Binds
-    @Singleton
+    @SysUISingleton
     public abstract NotificationRowContentBinder provideNotificationRowContentBinder(
             NotificationContentInflater contentBinderImpl);
 
@@ -38,7 +38,7 @@
      * Provides notification remote view cache instance.
      */
     @Binds
-    @Singleton
+    @SysUISingleton
     public abstract NotifRemoteViewCache provideNotifRemoteViewCache(
             NotifRemoteViewCacheImpl cacheImpl);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/OnDismissCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/OnUserInteractionCallback.java
similarity index 70%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/row/OnDismissCallback.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/row/OnUserInteractionCallback.java
index f1aed89..0c3f553 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/OnDismissCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/OnUserInteractionCallback.java
@@ -21,15 +21,21 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
 /**
- * Callback when a user clicks on an auto-cancelled notification or manually swipes to dismiss the
- * notification.
+ * Callbacks for when a user interacts with an {@link ExpandableNotificationRow}.
  */
-public interface OnDismissCallback {
+public interface OnUserInteractionCallback {
 
     /**
-     * Handle a user interaction that triggers a notification dismissal.
+     * Handle a user interaction that triggers a notification dismissal. Called when a user clicks
+     * on an auto-cancelled notification or manually swipes to dismiss the notification.
      */
     void onDismiss(
             NotificationEntry entry,
             @NotificationListenerService.NotificationCancelReason int cancellationReason);
+
+    /**
+     * Triggered after a user has changed the importance of the notification via its
+     * {@link NotificationGuts}.
+     */
+    void onImportanceChanged(NotificationEntry entry);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
index c6f0a13..3616f8f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
@@ -20,13 +20,13 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * A stage that binds all content views for an already inflated {@link ExpandableNotificationRow}.
@@ -34,7 +34,7 @@
  * In the farther future, the binder logic and consequently this stage should be broken into
  * smaller stages.
  */
-@Singleton
+@SysUISingleton
 public class RowContentBindStage extends BindStage<RowContentBindParams> {
     private final NotificationRowContentBinder mBinder;
     private final NotifInflationErrorManager mNotifInflationErrorManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java
index 28ddf59..becc9a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java
@@ -25,6 +25,7 @@
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController;
+import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.StatusBar;
 
 import dagger.Binds;
@@ -55,6 +56,8 @@
         Builder notificationEntry(NotificationEntry entry);
         @BindsInstance
         Builder onExpandClickListener(ExpandableNotificationRow.OnExpandClickListener presenter);
+        @BindsInstance
+        Builder listContainer(NotificationListContainer listContainer);
         ExpandableNotificationRowComponent build();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index 4651771..3f58674 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -18,7 +18,6 @@
 
 import static com.android.systemui.statusbar.notification.TransformState.TRANSFORM_Y;
 
-import android.app.AppOpsManager;
 import android.app.Notification;
 import android.content.Context;
 import android.util.ArraySet;
@@ -64,10 +63,6 @@
     private TextView mHeaderText;
     private TextView mAppNameText;
     private ImageView mWorkProfileImage;
-    private View mCameraIcon;
-    private View mMicIcon;
-    private View mOverlayIcon;
-    private View mAppOps;
     private View mAudiblyAlertedIcon;
     private FrameLayout mIconContainer;
     private View mFeedbackIcon;
@@ -109,7 +104,6 @@
                     }
                 }, TRANSFORMING_VIEW_TITLE);
         resolveHeaderViews();
-        addAppOpsOnClickListener(row);
         addFeedbackOnClickListener(row);
     }
 
@@ -121,10 +115,6 @@
         mExpandButton = mView.findViewById(com.android.internal.R.id.expand_button);
         mWorkProfileImage = mView.findViewById(com.android.internal.R.id.profile_badge);
         mNotificationHeader = mView.findViewById(com.android.internal.R.id.notification_header);
-        mCameraIcon = mView.findViewById(com.android.internal.R.id.camera);
-        mMicIcon = mView.findViewById(com.android.internal.R.id.mic);
-        mOverlayIcon = mView.findViewById(com.android.internal.R.id.overlay);
-        mAppOps = mView.findViewById(com.android.internal.R.id.app_ops);
         mAudiblyAlertedIcon = mView.findViewById(com.android.internal.R.id.alerted_icon);
         mFeedbackIcon = mView.findViewById(com.android.internal.R.id.feedback);
         if (mNotificationHeader != null) {
@@ -133,38 +123,6 @@
         }
     }
 
-    private void addAppOpsOnClickListener(ExpandableNotificationRow row) {
-        View.OnClickListener listener = row.getAppOpsOnClickListener();
-        if (mNotificationHeader != null) {
-            mNotificationHeader.setAppOpsOnClickListener(listener);
-        }
-        if (mAppOps != null) {
-            mAppOps.setOnClickListener(listener);
-        }
-    }
-
-    /**
-     * Shows or hides 'app op in use' icons based on app usage.
-     */
-    @Override
-    public void showAppOpsIcons(ArraySet<Integer> appOps) {
-        if (appOps == null) {
-            return;
-        }
-        if (mOverlayIcon != null) {
-            mOverlayIcon.setVisibility(appOps.contains(AppOpsManager.OP_SYSTEM_ALERT_WINDOW)
-                    ? View.VISIBLE : View.GONE);
-        }
-        if (mCameraIcon != null) {
-            mCameraIcon.setVisibility(appOps.contains(AppOpsManager.OP_CAMERA)
-                    ? View.VISIBLE : View.GONE);
-        }
-        if (mMicIcon != null) {
-            mMicIcon.setVisibility(appOps.contains(AppOpsManager.OP_RECORD_AUDIO)
-                    ? View.VISIBLE : View.GONE);
-        }
-    }
-
     private void addFeedbackOnClickListener(ExpandableNotificationRow row) {
         View.OnClickListener listener = row.getFeedbackOnClickListener();
         if (mNotificationHeader != null) {
@@ -304,15 +262,6 @@
             mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TITLE,
                     mHeaderText);
         }
-        if (mCameraIcon != null) {
-            mTransformationHelper.addViewTransformingToSimilar(mCameraIcon);
-        }
-        if (mMicIcon != null) {
-            mTransformationHelper.addViewTransformingToSimilar(mMicIcon);
-        }
-        if (mOverlayIcon != null) {
-            mTransformationHelper.addViewTransformingToSimilar(mOverlayIcon);
-        }
         if (mAudiblyAlertedIcon != null) {
             mTransformationHelper.addViewTransformingToSimilar(mAudiblyAlertedIcon);
         }
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 93d3f3b..33c93905 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
@@ -22,14 +22,12 @@
 import android.app.Notification;
 import android.content.Context;
 import android.content.res.ColorStateList;
-import android.graphics.drawable.Drawable;
 import android.media.MediaMetadata;
 import android.media.session.MediaController;
 import android.media.session.MediaSession;
 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;
@@ -43,13 +41,9 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.widget.MediaNotificationView;
 import com.android.systemui.Dependency;
-import com.android.systemui.qs.QSPanel;
-import com.android.systemui.qs.QuickQSPanel;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.TransformableView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
-import com.android.systemui.util.Utils;
 
 import java.util.Timer;
 import java.util.TimerTask;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
index 605fbc0..42f5e38 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
@@ -97,14 +97,6 @@
     }
 
     /**
-     * Show a set of app opp icons in the layout.
-     *
-     * @param appOps which app ops to show
-     */
-    public void showAppOpsIcons(ArraySet<Integer> appOps) {
-    }
-
-    /**
      * Shows or hides feedback icon.
      */
     public void showFeedbackIcon(boolean show) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ForegroundServiceSectionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ForegroundServiceSectionController.kt
index 5757fe8..32d41a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ForegroundServiceSectionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ForegroundServiceSectionController.kt
@@ -26,23 +26,21 @@
 import android.view.LayoutInflater
 import android.view.View
 import android.widget.LinearLayout
-
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.notification.ForegroundServiceDismissalFeatureController
 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.DungeonRow
 import com.android.systemui.util.Assert
-
 import javax.inject.Inject
-import javax.inject.Singleton
 
 /**
  * Controller for the bottom area of NotificationStackScrollLayout. It owns swiped-away foreground
  * service notifications and can reinstantiate them when requested.
  */
-@Singleton
+@SysUISingleton
 class ForegroundServiceSectionController @Inject constructor(
     val entryManager: NotificationEntryManager,
     val featureController: ForegroundServiceDismissalFeatureController
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index 93c2377..a396305 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -22,7 +22,6 @@
 import android.content.res.Resources;
 import android.graphics.drawable.ColorDrawable;
 import android.service.notification.StatusBarNotification;
-import android.util.ArraySet;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.NotificationHeaderView;
@@ -36,7 +35,7 @@
 import com.android.systemui.statusbar.CrossFadeHelper;
 import com.android.systemui.statusbar.NotificationHeaderUtil;
 import com.android.systemui.statusbar.notification.NotificationUtils;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.HybridGroupManager;
 import com.android.systemui.statusbar.notification.row.HybridNotificationView;
@@ -1303,20 +1302,6 @@
     }
 
     /**
-     * Show a set of app opp icons in the layout.
-     *
-     * @param appOps which app ops to show
-     */
-    public void showAppOpsIcons(ArraySet<Integer> appOps) {
-        if (mNotificationHeaderWrapper != null) {
-            mNotificationHeaderWrapper.showAppOpsIcons(appOps);
-        }
-        if (mNotificationHeaderWrapperLowPriority != null) {
-            mNotificationHeaderWrapperLowPriority.showAppOpsIcons(appOps);
-        }
-    }
-
-    /**
      * Shows or hides feedback icon.
      */
     public void showFeedbackIcon(boolean show) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
index 2c3239a..fe66669 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
@@ -18,6 +18,7 @@
 
 import android.util.MathUtils;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -28,12 +29,11 @@
 import java.util.HashSet;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * A class that manages the roundness for notification views
  */
-@Singleton
+@SysUISingleton
 public class NotificationRoundnessManager implements OnHeadsUpChangedListener {
 
     private final ExpandableView[] mFirstInSectionViews;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt
index 17b4143..cb7dfe8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt
@@ -16,15 +16,15 @@
 
 package com.android.systemui.statusbar.notification.stack
 
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.LogLevel
 import com.android.systemui.log.dagger.NotificationSectionLog
 import javax.inject.Inject
-import javax.inject.Singleton
 
 private const val TAG = "NotifSections"
 
-@Singleton
+@SysUISingleton
 class NotificationSectionsLogger @Inject constructor(
     @NotificationSectionLog private val logBuffer: LogBuffer
 ) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 2ff67ab..d4c270f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -125,10 +125,10 @@
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.ShadeViewRefactor;
 import com.android.systemui.statusbar.notification.ShadeViewRefactor.RefactorComponent;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
@@ -150,7 +150,6 @@
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.NotificationGroupManager.OnGroupChangeListener;
-import com.android.systemui.statusbar.phone.NotificationIconAreaController;
 import com.android.systemui.statusbar.phone.NotificationPanelViewController;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.ShadeController;
@@ -179,9 +178,7 @@
 /**
  * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack.
  */
-public class NotificationStackScrollLayout extends ViewGroup implements ScrollAdapter,
-        NotificationListContainer, ConfigurationListener, Dumpable,
-        DynamicPrivacyController.Listener {
+public class NotificationStackScrollLayout extends ViewGroup implements ScrollAdapter, Dumpable {
 
     public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
     private static final String TAG = "StackScroller";
@@ -213,6 +210,7 @@
 
     private float mExpandedHeight;
     private int mOwnScrollY;
+
     private View mScrollAnchorView;
     private int mScrollAnchorViewY;
     private int mMaxLayoutHeight;
@@ -500,7 +498,6 @@
     private ArrayList<BiConsumer<Float, Float>> mExpandedHeightListeners = new ArrayList<>();
     private int mHeadsUpInset;
     private HeadsUpAppearanceController mHeadsUpAppearanceController;
-    private NotificationIconAreaController mIconAreaController;
     private final NotificationLockscreenUserManager mLockscreenUserManager;
     private final Rect mTmpRect = new Rect();
     private final FeatureFlags mFeatureFlags;
@@ -545,6 +542,19 @@
                 updateDecorViews(useDarkText);
             };
 
+    private final ExpandableView.OnHeightChangedListener mOnChildHeightChangedListener =
+            new ExpandableView.OnHeightChangedListener() {
+                @Override
+                public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
+                    onChildHeightChanged(view, needsAnimation);
+                }
+
+                @Override
+                public void onReset(ExpandableView view) {
+                    onChildHeightReset(view);
+                }
+            };
+
     @Inject
     public NotificationStackScrollLayout(
             @Named(VIEW_CONTEXT) Context context,
@@ -576,7 +586,6 @@
         mLockscreenUserManager = notificationLockscreenUserManager;
         mNotificationGutsManager = notificationGutsManager;
         mHeadsUpManager = headsUpManager;
-        mHeadsUpManager.addListener(mRoundnessManager);
         mHeadsUpManager.setAnimationStateHandler(this::setHeadsUpGoingAwayAnimationsAllowed);
         mKeyguardBypassController = keyguardBypassController;
         mFalsingManager = falsingManager;
@@ -608,9 +617,6 @@
                 res.getBoolean(R.bool.config_drawNotificationBackground);
         mFadeNotificationsOnDismiss =
                 res.getBoolean(R.bool.config_fadeNotificationsOnDismiss);
-        mRoundnessManager.setAnimatedChildren(mChildrenToAddAnimated);
-        mRoundnessManager.setOnRoundingChangedCallback(this::invalidate);
-        addOnExpandedHeightChangedListener(mRoundnessManager::setExpanded);
         mLockscreenUserManager.addUserChangedListener(mLockscreenUserChangeListener);
         setOutlineProvider(mOutlineProvider);
 
@@ -637,13 +643,8 @@
         tunerService.addTunable((key, newValue) -> {
             if (key.equals(HIGH_PRIORITY)) {
                 mHighPriorityBeforeSpeedBump = "1".equals(newValue);
-            } else if (key.equals(Settings.Secure.NOTIFICATION_DISMISS_RTL)) {
-                updateDismissRtlSetting("1".equals(newValue));
-            } else if (key.equals(Settings.Secure.NOTIFICATION_HISTORY_ENABLED)) {
-                updateFooter();
             }
-        }, HIGH_PRIORITY, Settings.Secure.NOTIFICATION_DISMISS_RTL,
-                Settings.Secure.NOTIFICATION_HISTORY_ENABLED);
+        }, HIGH_PRIORITY);
 
         mFeatureFlags = featureFlags;
         mNotifPipeline = notifPipeline;
@@ -665,7 +666,6 @@
             });
         }
 
-        dynamicPrivacyController.addListener(this);
         mDynamicPrivacyController = dynamicPrivacyController;
         mStatusbarStateController = statusBarStateController;
         initializeForegroundServiceSection(fgsFeatureController);
@@ -693,7 +693,7 @@
         }
     }
 
-    private void updateDismissRtlSetting(boolean dismissRtl) {
+    void updateDismissRtlSetting(boolean dismissRtl) {
         mDismissRtl = dismissRtl;
         for (int i = 0; i < getChildCount(); i++) {
             View child = getChildAt(i);
@@ -728,36 +728,13 @@
         return 0f;
     }
 
-    @Override
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    public void onDensityOrFontScaleChanged() {
-        reinflateViews();
-    }
-
-    private void reinflateViews() {
+    void reinflateViews() {
         inflateFooterView();
         inflateEmptyShadeView();
         updateFooter();
         mSectionsManager.reinflateViews(LayoutInflater.from(mContext));
     }
 
-    @Override
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    public void onThemeChanged() {
-        updateFooter();
-    }
-
-    @Override
-    public void onOverlayChanged() {
-        int newRadius = mContext.getResources().getDimensionPixelSize(
-                Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius));
-        if (mCornerRadius != newRadius) {
-            mCornerRadius = newRadius;
-            invalidate();
-        }
-        reinflateViews();
-    }
-
     @VisibleForTesting
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void updateFooter() {
@@ -823,7 +800,6 @@
         super.onAttachedToWindow();
         ((SysuiStatusBarStateController) Dependency.get(StatusBarStateController.class))
                 .addCallback(mStateListener, SysuiStatusBarStateController.RANK_STACK_SCROLLER);
-        Dependency.get(ConfigurationController.class).addCallback(this);
     }
 
     @Override
@@ -831,18 +807,14 @@
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
         Dependency.get(StatusBarStateController.class).removeCallback(mStateListener);
-        Dependency.get(ConfigurationController.class).removeCallback(this);
     }
 
-    @Override
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public NotificationSwipeActionHelper getSwipeActionHelper() {
         return mSwipeHelper;
     }
 
-    @Override
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    public void onUiModeChanged() {
+    void updateBgColor() {
         mBgColor = mContext.getColor(R.color.notification_shade_background_color);
         updateBackgroundDimming();
         mShelf.onUiModeChanged();
@@ -1075,6 +1047,15 @@
                 R.dimen.heads_up_status_bar_padding);
     }
 
+    void updateCornerRadius() {
+        int newRadius = getResources().getDimensionPixelSize(
+                Utils.getThemeAttr(getContext(), android.R.attr.dialogCornerRadius));
+        if (mCornerRadius != newRadius) {
+            mCornerRadius = newRadius;
+            invalidate();
+        }
+    }
+
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     private void notifyHeightChangeListener(ExpandableView view) {
         notifyHeightChangeListener(view, false /* needsAnimation */);
@@ -1150,14 +1131,16 @@
         mNoAmbient = noAmbient;
     }
 
-    @Override
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     public void setChildLocationsChangedListener(
             NotificationLogger.OnChildLocationsChangedListener listener) {
         mListener = listener;
     }
 
-    @Override
+    public void setScrollAnchorView(View scrollAnchorView) {
+        mScrollAnchorView = scrollAnchorView;
+    }
+
     @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM)
     public boolean isInVisibleLocation(NotificationEntry entry) {
         ExpandableNotificationRow row = entry.getRow();
@@ -1856,7 +1839,6 @@
         }
     }
 
-    @Override
     @ShadeViewRefactor(RefactorComponent.ADAPTER)
     public ViewGroup getViewParentForNotification(NotificationEntry entry) {
         return this;
@@ -2546,7 +2528,6 @@
                 previous);
     }
 
-    @Override
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public boolean hasPulsingNotifications() {
         return mPulsing;
@@ -3050,7 +3031,6 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
-    @Override
     public void cleanUpViewStateForEntry(NotificationEntry entry) {
         View child = entry.getRow();
         if (child == mSwipeHelper.getTranslatingParentView()) {
@@ -3317,7 +3297,9 @@
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void onViewAdded(View child) {
         super.onViewAdded(child);
-        onViewAddedInternal((ExpandableView) child);
+        if (child instanceof ExpandableView) {
+            onViewAddedInternal((ExpandableView) child);
+        }
     }
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
@@ -3350,9 +3332,9 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
-    private void onViewAddedInternal(ExpandableView child) {
+    void onViewAddedInternal(ExpandableView child) {
         updateHideSensitiveForChild(child);
-        child.setOnHeightChangedListener(this);
+        child.setOnHeightChangedListener(mOnChildHeightChangedListener);
         generateAddAnimation(child, false /* fromMoreCard */);
         updateAnimationState(child);
         updateChronometerForChild(child);
@@ -3374,18 +3356,11 @@
         child.setHideSensitiveForIntrinsicHeight(mAmbientState.isHideSensitive());
     }
 
-    @Override
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void notifyGroupChildRemoved(ExpandableView row, ViewGroup childrenContainer) {
         onViewRemovedInternal(row, childrenContainer);
     }
 
-    @Override
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    public void notifyGroupChildAdded(ExpandableView row) {
-        onViewAddedInternal(row);
-    }
-
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     public void setAnimationsEnabled(boolean animationsEnabled) {
         mAnimationsEnabled = animationsEnabled;
@@ -3410,33 +3385,29 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
-    private void updateAnimationState(View child) {
+    void updateAnimationState(View child) {
         updateAnimationState((mAnimationsEnabled || hasPulsingNotifications())
                 && (mIsExpanded || isPinnedHeadsUp(child)), child);
     }
 
-    @Override
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    public void setExpandingNotification(ExpandableNotificationRow row) {
+    void setExpandingNotification(ExpandableNotificationRow row) {
         mAmbientState.setExpandingNotification(row);
         requestChildrenUpdate();
     }
 
-    @Override
     @ShadeViewRefactor(RefactorComponent.ADAPTER)
-    public void bindRow(ExpandableNotificationRow row) {
+    void bindRow(ExpandableNotificationRow row) {
         row.setHeadsUpAnimatingAwayListener(animatingAway -> {
             mRoundnessManager.onHeadsupAnimatingAwayChanged(row, animatingAway);
             mHeadsUpAppearanceController.updateHeader(row.getEntry());
         });
     }
 
-    @Override
     public boolean containsView(View v) {
         return v.getParent() == this;
     }
 
-    @Override
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     public void applyExpandAnimationParams(ExpandAnimationParameters params) {
         mAmbientState.setExpandAnimationTopChange(params == null ? 0 : params.getTopChange());
@@ -3452,12 +3423,11 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
-    public boolean isAddOrRemoveAnimationPending() {
+    boolean isAddOrRemoveAnimationPending() {
         return mNeedsAnimation
                 && (!mChildrenToAddAnimated.isEmpty() || !mChildrenToRemoveAnimated.isEmpty());
     }
 
-    @Override
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     public void generateAddAnimation(ExpandableView child, boolean fromMoreCard) {
         if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress && !isFullyHidden()) {
@@ -3475,7 +3445,6 @@
         }
     }
 
-    @Override
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     public void changeViewPosition(ExpandableView child, int newIndex) {
         Assert.isMainThread();
@@ -4471,12 +4440,12 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
-    public int getEmptyBottomMargin() {
+    int getEmptyBottomMargin() {
         return Math.max(mMaxLayoutHeight - mContentHeight, 0);
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    public void checkSnoozeLeavebehind() {
+    void checkSnoozeLeavebehind() {
         if (mCheckForLeavebehind) {
             mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */,
                     false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
@@ -4486,19 +4455,19 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    public void resetCheckSnoozeLeavebehind() {
+    void resetCheckSnoozeLeavebehind() {
         mCheckForLeavebehind = true;
     }
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
-    public void onExpansionStarted() {
+    void onExpansionStarted() {
         mIsExpansionChanging = true;
         mAmbientState.setExpansionChanging(true);
         checkSnoozeLeavebehind();
     }
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
-    public void onExpansionStopped() {
+    void onExpansionStopped() {
         mIsExpansionChanging = false;
         resetCheckSnoozeLeavebehind();
         mAmbientState.setExpansionChanging(false);
@@ -4547,20 +4516,20 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
-    public void onPanelTrackingStarted() {
+    void onPanelTrackingStarted() {
         mPanelTracking = true;
         mAmbientState.setPanelTracking(true);
         resetExposedMenuView(true /* animate */, true /* force */);
     }
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
-    public void onPanelTrackingStopped() {
+    void onPanelTrackingStopped() {
         mPanelTracking = false;
         mAmbientState.setPanelTracking(false);
     }
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
-    public void resetScrollPosition() {
+    void resetScrollPosition() {
         mScroller.abortAnimation();
         if (ANCHOR_SCROLLING) {
             // TODO: once we're recycling this will need to modify the adapter position instead
@@ -4601,15 +4570,14 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
-    private void updateChronometerForChild(View child) {
+    void updateChronometerForChild(View child) {
         if (child instanceof ExpandableNotificationRow) {
             ExpandableNotificationRow row = (ExpandableNotificationRow) child;
             row.setChronometerRunning(mIsExpanded);
         }
     }
 
-    @Override
-    public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
+    void onChildHeightChanged(ExpandableView view, boolean needsAnimation) {
         updateContentHeight();
         updateScrollPositionOnExpandInBottom(view);
         clampScrollPosition();
@@ -4632,8 +4600,7 @@
         requestChildrenUpdate();
     }
 
-    @Override
-    public void onReset(ExpandableView view) {
+    void onChildHeightReset(ExpandableView view) {
         updateAnimationState(view);
         updateChronometerForChild(view);
     }
@@ -4674,13 +4641,13 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    public void setOnHeightChangedListener(
+    void setOnHeightChangedListener(
             ExpandableView.OnHeightChangedListener onHeightChangedListener) {
         this.mOnHeightChangedListener = onHeightChangedListener;
     }
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
-    public void onChildAnimationFinished() {
+    void onChildAnimationFinished() {
         setAnimationRunning(false);
         requestChildrenUpdate();
         runAnimationFinishedRunnables();
@@ -4724,7 +4691,7 @@
      * See {@link AmbientState#setDimmed}.
      */
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    public void setDimmed(boolean dimmed, boolean animate) {
+    void setDimmed(boolean dimmed, boolean animate) {
         dimmed &= onKeyguard();
         mAmbientState.setDimmed(dimmed);
         if (animate && mAnimationsEnabled) {
@@ -4789,7 +4756,7 @@
      * See {@link AmbientState#setActivatedChild}.
      */
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    public void setActivatedChild(ActivatableNotificationView activatedChild) {
+    void setActivatedChild(ActivatableNotificationView activatedChild) {
         mAmbientState.setActivatedChild(activatedChild);
         if (mAnimationsEnabled) {
             mActivateNeedsAnimation = true;
@@ -4865,7 +4832,7 @@
      * @param lightTheme True if light theme should be used.
      */
     @ShadeViewRefactor(RefactorComponent.DECORATOR)
-    public void updateDecorViews(boolean lightTheme) {
+    void updateDecorViews(boolean lightTheme) {
         if (lightTheme == mUsingLightTheme) {
             return;
         }
@@ -4880,7 +4847,7 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    public void goToFullShade(long delay) {
+    void goToFullShade(long delay) {
         mGoToFullShadeNeedsAnimation = true;
         mGoToFullShadeDelay = delay;
         mNeedsAnimation = true;
@@ -4893,13 +4860,13 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
-    public void setIntrinsicPadding(int intrinsicPadding) {
+    void setIntrinsicPadding(int intrinsicPadding) {
         mIntrinsicPadding = intrinsicPadding;
         mAmbientState.setIntrinsicPadding(intrinsicPadding);
     }
 
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
-    public int getIntrinsicPadding() {
+    int getIntrinsicPadding() {
         return mIntrinsicPadding;
     }
 
@@ -4933,7 +4900,7 @@
      *                               animation curve.
      */
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    public void setHideAmount(float linearHideAmount, float interpolatedHideAmount) {
+    void setHideAmount(float linearHideAmount, float interpolatedHideAmount) {
         mLinearHideAmount = linearHideAmount;
         mInterpolatedHideAmount = interpolatedHideAmount;
         boolean wasFullyHidden = mAmbientState.isFullyHidden();
@@ -4975,7 +4942,7 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
-    public void notifyHideAnimationStart(boolean hide) {
+    void notifyHideAnimationStart(boolean hide) {
         // We only swap the scaling factor if we're fully hidden or fully awake to avoid
         // interpolation issues when playing with the power button.
         if (mInterpolatedHideAmount == 0 || mInterpolatedHideAmount == 1) {
@@ -5003,7 +4970,7 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    public void setFooterView(@NonNull FooterView footerView) {
+    void setFooterView(@NonNull FooterView footerView) {
         int index = -1;
         if (mFooterView != null) {
             index = indexOfChild(mFooterView);
@@ -5014,7 +4981,7 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    public void setEmptyShadeView(EmptyShadeView emptyShadeView) {
+    void setEmptyShadeView(EmptyShadeView emptyShadeView) {
         int index = -1;
         if (mEmptyShadeView != null) {
             index = indexOfChild(mEmptyShadeView);
@@ -5025,7 +4992,7 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    public void updateEmptyShadeView(boolean visible) {
+    void updateEmptyShadeView(boolean visible) {
         mEmptyShadeView.setVisible(visible, mIsExpanded && mAnimationsEnabled);
 
         int oldTextRes = mEmptyShadeView.getTextResource();
@@ -5209,33 +5176,28 @@
         }
     }
 
-    @Override
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public int getContainerChildCount() {
         return getChildCount();
     }
 
-    @Override
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public View getContainerChildAt(int i) {
         return getChildAt(i);
     }
 
-    @Override
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void removeContainerView(View v) {
         Assert.isMainThread();
         removeView(v);
     }
 
-    @Override
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void addContainerView(View v) {
         Assert.isMainThread();
         addView(v);
     }
 
-    @Override
     public void addContainerViewAt(View v, int index) {
         Assert.isMainThread();
         addView(v, index);
@@ -5277,7 +5239,6 @@
         requestChildrenUpdate();
     }
 
-    @Override
     public void setWillExpand(boolean willExpand) {
         mWillExpand = willExpand;
     }
@@ -5613,11 +5574,6 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    public void setIconAreaController(NotificationIconAreaController controller) {
-        mIconAreaController = controller;
-    }
-
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     @VisibleForTesting
     void clearNotifications(
             @SelectedRows int selection,
@@ -5742,7 +5698,6 @@
         }
     }
 
-    @Override
     public void setNotificationActivityStarter(
             NotificationActivityStarter notificationActivityStarter) {
         mNotificationActivityStarter = notificationActivityStarter;
@@ -5808,10 +5763,6 @@
         mNotificationPanelController = notificationPanelViewController;
     }
 
-    public void updateIconAreaViews() {
-        mIconAreaController.updateNotificationIcons();
-    }
-
     /**
      * Set how far the wake up is when waking up from pulsing. This is a height and will adjust the
      * notification positions accordingly.
@@ -5870,17 +5821,8 @@
         mDimmedNeedsAnimation = true;
     }
 
-    @Override
-    public void onDynamicPrivacyChanged() {
-        if (mIsExpanded) {
-            // The bottom might change because we're using the final actual height of the view
-            mAnimateBottomOnLayout = true;
-        }
-        // Let's update the footer once the notifications have been updated (in the next frame)
-        post(() -> {
-            updateFooter();
-            updateSectionBoundaries("dynamic privacy changed");
-        });
+    void setAnimateBottomOnLayout(boolean animateBottomOnLayout) {
+        mAnimateBottomOnLayout = animateBottomOnLayout;
     }
 
     public void setOnPulseHeightChangedListener(Runnable listener) {
@@ -5900,6 +5842,7 @@
     public void setController(
             NotificationStackScrollLayoutController notificationStackScrollLayoutController) {
         mController = notificationStackScrollLayoutController;
+        mController.getNoticationRoundessManager().setAnimatedChildren(mChildrenToAddAnimated);
     }
 
     public NotificationStackScrollLayoutController getController() {
@@ -6000,7 +5943,6 @@
         }
     }
 
-    @Override
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void resetExposedMenuView(boolean animate, boolean force) {
         mSwipeHelper.resetExposedMenuView(animate, force);
@@ -6752,7 +6694,7 @@
             }
             changedRow.setChildrenExpanded(expanded, animated);
             if (!mGroupExpandedForMeasure) {
-                onHeightChanged(changedRow, false /* needsAnimation */);
+                onChildHeightChanged(changedRow, false /* needsAnimation */);
             }
             runAfterAnimationFinished(new Runnable() {
                 @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 8150593..7c29ee2b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -19,27 +19,37 @@
 import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
 
 import android.graphics.PointF;
+import android.provider.Settings;
 import android.view.Display;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowInsets;
 import android.widget.FrameLayout;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
 import com.android.systemui.statusbar.NotificationShelfController;
 import com.android.systemui.statusbar.RemoteInputController;
+import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
+import com.android.systemui.statusbar.notification.DynamicPrivacyController;
+import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.phone.NotificationIconAreaController;
 import com.android.systemui.statusbar.phone.NotificationPanelViewController;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
+import com.android.systemui.tuner.TunerService;
 
 import java.util.function.BiConsumer;
 
@@ -53,14 +63,81 @@
 public class NotificationStackScrollLayoutController {
     private final boolean mAllowLongPress;
     private final NotificationGutsManager mNotificationGutsManager;
+    private final HeadsUpManagerPhone mHeadsUpManager;
+    private final NotificationRoundnessManager mNotificationRoundnessManager;
+    private final TunerService mTunerService;
+    private final DynamicPrivacyController mDynamicPrivacyController;
+    private final ConfigurationController mConfigurationController;
+    private final NotificationListContainerImpl mNotificationListContainer =
+            new NotificationListContainerImpl();
     private NotificationStackScrollLayout mView;
 
+    @VisibleForTesting
+    final View.OnAttachStateChangeListener mOnAttachStateChangeListener =
+            new View.OnAttachStateChangeListener() {
+                @Override
+                public void onViewAttachedToWindow(View v) {
+                    mConfigurationController.addCallback(mConfigurationListener);
+                }
+
+                @Override
+                public void onViewDetachedFromWindow(View v) {
+                    mConfigurationController.removeCallback(mConfigurationListener);
+                }
+            };
+
+    private final DynamicPrivacyController.Listener mDynamicPrivacyControllerListener = () -> {
+        if (mView.isExpanded()) {
+            // The bottom might change because we're using the final actual height of the view
+            mView.setAnimateBottomOnLayout(true);
+        }
+        // Let's update the footer once the notifications have been updated (in the next frame)
+        mView.post(() -> {
+            updateFooter();
+            updateSectionBoundaries("dynamic privacy changed");
+        });
+    };
+
+    @VisibleForTesting
+    final ConfigurationListener mConfigurationListener = new ConfigurationListener() {
+        @Override
+        public void onDensityOrFontScaleChanged() {
+            mView.reinflateViews();
+        }
+
+        @Override
+        public void onOverlayChanged() {
+            mView.updateCornerRadius();
+            mView.reinflateViews();
+        }
+
+        @Override
+        public void onUiModeChanged() {
+            mView.updateBgColor();
+        }
+
+        @Override
+        public void onThemeChanged() {
+            updateFooter();
+        }
+    };
+
     @Inject
     public NotificationStackScrollLayoutController(
             @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
-            NotificationGutsManager notificationGutsManager) {
+            NotificationGutsManager notificationGutsManager,
+            HeadsUpManagerPhone headsUpManager,
+            NotificationRoundnessManager notificationRoundnessManager,
+            TunerService tunerService,
+            DynamicPrivacyController dynamicPrivacyController,
+            ConfigurationController configurationController) {
         mAllowLongPress = allowLongPress;
         mNotificationGutsManager = notificationGutsManager;
+        mHeadsUpManager = headsUpManager;
+        mNotificationRoundnessManager = notificationRoundnessManager;
+        mTunerService = tunerService;
+        mDynamicPrivacyController = dynamicPrivacyController;
+        mConfigurationController = configurationController;
     }
 
     public void attach(NotificationStackScrollLayout view) {
@@ -70,6 +147,28 @@
         if (mAllowLongPress) {
             mView.setLongPressListener(mNotificationGutsManager::openGuts);
         }
+
+        mHeadsUpManager.addListener(mNotificationRoundnessManager); // TODO: why is this here?
+        mDynamicPrivacyController.addListener(mDynamicPrivacyControllerListener);
+
+        mNotificationRoundnessManager.setOnRoundingChangedCallback(mView::invalidate);
+        mView.addOnExpandedHeightChangedListener(mNotificationRoundnessManager::setExpanded);
+
+        mTunerService.addTunable(
+                (key, newValue) -> {
+                    if (key.equals(Settings.Secure.NOTIFICATION_DISMISS_RTL)) {
+                        mView.updateDismissRtlSetting("1".equals(newValue));
+                    } else if (key.equals(Settings.Secure.NOTIFICATION_HISTORY_ENABLED)) {
+                        updateFooter();
+                    }
+                },
+                Settings.Secure.NOTIFICATION_DISMISS_RTL,
+                Settings.Secure.NOTIFICATION_HISTORY_ENABLED);
+
+        if (mView.isAttachedToWindow()) {
+            mOnAttachStateChangeListener.onViewAttachedToWindow(mView);
+        }
+        mView.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
     }
 
     public void addOnExpandedHeightChangedListener(BiConsumer<Float, Float> listener) {
@@ -449,18 +548,10 @@
         mView.updateFooter();
     }
 
-    public void updateIconAreaViews() {
-        mView.updateIconAreaViews();
-    }
-
     public void onUpdateRowStates() {
         mView.onUpdateRowStates();
     }
 
-    public boolean hasPulsingNotifications() {
-        return mView.hasPulsingNotifications();
-    }
-
     public ActivatableNotificationView getActivatedChild() {
         return mView.getActivatedChild();
     }
@@ -478,10 +569,6 @@
         mView.setNotificationPanelController(notificationPanelViewController);
     }
 
-    public void setIconAreaController(NotificationIconAreaController controller) {
-        mView.setIconAreaController(controller);
-    }
-
     public void setStatusBar(StatusBar statusBar) {
         mView.setStatusBar(statusBar);
     }
@@ -537,4 +624,151 @@
     public float calculateGapHeight(ExpandableView previousView, ExpandableView child, int count) {
         return mView.calculateGapHeight(previousView, child, count);
     }
+
+    public NotificationRoundnessManager getNoticationRoundessManager() {
+        return mNotificationRoundnessManager;
+    }
+
+    public NotificationListContainer getNotificationListContainer() {
+        return mNotificationListContainer;
+    }
+
+    private class NotificationListContainerImpl implements NotificationListContainer {
+        @Override
+        public void setChildTransferInProgress(boolean childTransferInProgress) {
+            mView.setChildTransferInProgress(childTransferInProgress);
+        }
+
+        @Override
+        public void changeViewPosition(ExpandableView child, int newIndex) {
+            mView.changeViewPosition(child, newIndex);
+        }
+
+        @Override
+        public void notifyGroupChildAdded(ExpandableView row) {
+            mView.onViewAddedInternal(row);
+        }
+
+        @Override
+        public void notifyGroupChildRemoved(ExpandableView row, ViewGroup childrenContainer) {
+            mView.notifyGroupChildRemoved(row, childrenContainer);
+        }
+
+        @Override
+        public void generateAddAnimation(ExpandableView child, boolean fromMoreCard) {
+            mView.generateAddAnimation(child, fromMoreCard);
+        }
+
+        @Override
+        public void generateChildOrderChangedEvent() {
+            mView.generateChildOrderChangedEvent();
+        }
+
+        @Override
+        public int getContainerChildCount() {
+            return mView.getContainerChildCount();
+        }
+
+        @Override
+        public void setNotificationActivityStarter(
+                NotificationActivityStarter notificationActivityStarter) {
+            mView.setNotificationActivityStarter(notificationActivityStarter);
+        }
+
+        @Override
+        public View getContainerChildAt(int i) {
+            return mView.getContainerChildAt(i);
+        }
+
+        @Override
+        public void removeContainerView(View v) {
+            mView.removeContainerView(v);
+        }
+
+        @Override
+        public void addContainerView(View v) {
+            mView.addContainerView(v);
+        }
+
+        @Override
+        public void addContainerViewAt(View v, int index) {
+            mView.addContainerViewAt(v, index);
+        }
+
+        @Override
+        public void setMaxDisplayedNotifications(int maxNotifications) {
+            mView.setMaxDisplayedNotifications(maxNotifications);
+        }
+
+        @Override
+        public ViewGroup getViewParentForNotification(NotificationEntry entry) {
+            return mView.getViewParentForNotification(entry);
+        }
+
+        @Override
+        public void resetExposedMenuView(boolean animate, boolean force) {
+            mView.resetExposedMenuView(animate, force);
+        }
+
+        @Override
+        public NotificationSwipeActionHelper getSwipeActionHelper() {
+            return mView.getSwipeActionHelper();
+        }
+
+        @Override
+        public void cleanUpViewStateForEntry(NotificationEntry entry) {
+            mView.cleanUpViewStateForEntry(entry);
+        }
+
+        @Override
+        public void setChildLocationsChangedListener(
+                NotificationLogger.OnChildLocationsChangedListener listener) {
+            mView.setChildLocationsChangedListener(listener);
+        }
+
+        public boolean hasPulsingNotifications() {
+            return mView.hasPulsingNotifications();
+        }
+
+        @Override
+        public boolean isInVisibleLocation(NotificationEntry entry) {
+            return mView.isInVisibleLocation(entry);
+        }
+
+        @Override
+        public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
+            mView.onChildHeightChanged(view, needsAnimation);
+        }
+
+        @Override
+        public void onReset(ExpandableView view) {
+            mView.onChildHeightReset(view);
+        }
+
+        @Override
+        public void bindRow(ExpandableNotificationRow row) {
+            mView.bindRow(row);
+        }
+
+        @Override
+        public void applyExpandAnimationParams(
+                ActivityLaunchAnimator.ExpandAnimationParameters params) {
+            mView.applyExpandAnimationParams(params);
+        }
+
+        @Override
+        public void setExpandingNotification(ExpandableNotificationRow row) {
+            mView.setExpandingNotification(row);
+        }
+
+        @Override
+        public boolean containsView(View v) {
+            return mView.containsView(v);
+        }
+
+        @Override
+        public void setWillExpand(boolean willExpand) {
+            mView.setWillExpand(willExpand);
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AppButtonData.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AppButtonData.java
deleted file mode 100644
index f6c1062..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AppButtonData.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2015 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.phone;
-
-import android.app.ActivityManager.RecentTaskInfo;
-
-import java.util.ArrayList;
-
-/**
- * Data associated with an app button.
- */
-class AppButtonData {
-    public final AppInfo appInfo;
-    public boolean pinned;
-    // Recent tasks for this app, sorted by lastActiveTime, descending.
-    public ArrayList<RecentTaskInfo> tasks;
-
-    public AppButtonData(AppInfo appInfo, boolean pinned) {
-        this.appInfo = appInfo;
-        this.pinned = pinned;
-    }
-
-    public int getTaskCount() {
-        return tasks == null ? 0 : tasks.size();
-    }
-
-    /**
-     * Returns true if the button contains no useful information and should be removed.
-     */
-    public boolean isEmpty() {
-        return !pinned && getTaskCount() == 0;
-    }
-
-    public void addTask(RecentTaskInfo task) {
-        if (tasks == null) {
-            tasks = new ArrayList<RecentTaskInfo>();
-        }
-        tasks.add(task);
-    }
-
-    public void clearTasks() {
-        if (tasks != null) {
-            tasks.clear();
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AppInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AppInfo.java
deleted file mode 100644
index 8f0b532..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AppInfo.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2015 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.phone;
-
-import android.content.ComponentName;
-import android.os.UserHandle;
-
-/**
- * Navigation bar app information.
- */
-class AppInfo {
-    private final ComponentName mComponentName;
-    private final UserHandle mUser;
-
-    public AppInfo(ComponentName componentName, UserHandle user) {
-        if (componentName == null || user == null) throw new IllegalArgumentException();
-        mComponentName = componentName;
-        mUser = user;
-    }
-
-    public ComponentName getComponentName() {
-        return mComponentName;
-    }
-
-    public UserHandle getUser() {
-        return mUser;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (obj == null) {
-            return false;
-        }
-        if (getClass() != obj.getClass()) {
-            return false;
-        }
-        final AppInfo other = (AppInfo) obj;
-        return mComponentName.equals(other.mComponentName) && mUser.equals(other.mUser);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
index d6039af..aeb2efd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
@@ -138,7 +138,7 @@
         mHandler.postDelayed(mAutoHide, AUTO_HIDE_TIMEOUT_MS);
     }
 
-    void checkUserAutoHide(MotionEvent event) {
+    public void checkUserAutoHide(MotionEvent event) {
         boolean shouldHide = isAnyTransientBarShown()
                 && event.getAction() == MotionEvent.ACTION_OUTSIDE // touch outside the source bar.
                 && event.getX() == 0 && event.getY() == 0;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 0e76c90..e996378 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -40,6 +40,7 @@
 import com.android.keyguard.KeyguardViewController;
 import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.KeyguardViewMediator;
@@ -47,6 +48,7 @@
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import java.io.FileDescriptor;
@@ -57,12 +59,11 @@
 import java.util.Optional;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Controller which coordinates all the biometric unlocking actions with the UI.
  */
-@Singleton
+@SysUISingleton
 public class BiometricUnlockController extends KeyguardUpdateMonitorCallback implements Dumpable {
 
     private static final String TAG = "BiometricUnlockCtrl";
@@ -157,11 +158,11 @@
     private DozeScrimController mDozeScrimController;
     private KeyguardViewMediator mKeyguardViewMediator;
     private ScrimController mScrimController;
-    private StatusBar mStatusBar;
     private PendingAuthenticated mPendingAuthenticated = null;
     private boolean mPendingShowBouncer;
     private boolean mHasScreenTurnedOnSinceAuthenticating;
     private boolean mFadedAwayAfterWakeAndUnlock;
+    private BiometricModeListener mBiometricModeListener;
 
     private final MetricsLogger mMetricsLogger;
 
@@ -243,7 +244,7 @@
     @Inject
     public BiometricUnlockController(Context context, DozeScrimController dozeScrimController,
             KeyguardViewMediator keyguardViewMediator, ScrimController scrimController,
-            StatusBar statusBar, ShadeController shadeController,
+            ShadeController shadeController,
             NotificationShadeWindowController notificationShadeWindowController,
             KeyguardStateController keyguardStateController, Handler handler,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -264,7 +265,6 @@
         mDozeScrimController = dozeScrimController;
         mKeyguardViewMediator = keyguardViewMediator;
         mScrimController = scrimController;
-        mStatusBar = statusBar;
         mKeyguardStateController = keyguardStateController;
         mHandler = handler;
         mWakeUpDelay = resources.getInteger(com.android.internal.R.integer.config_wakeUpDelayDoze);
@@ -278,6 +278,11 @@
         mKeyguardViewController = keyguardViewController;
     }
 
+    /** Sets a {@link BiometricModeListener}. */
+    public void setBiometricModeListener(BiometricModeListener biometricModeListener) {
+        mBiometricModeListener = biometricModeListener;
+    }
+
     private final Runnable mReleaseBiometricWakeLockRunnable = new Runnable() {
         @Override
         public void run() {
@@ -434,19 +439,25 @@
                 } else {
                     mKeyguardViewMediator.onWakeAndUnlocking();
                 }
-                if (mStatusBar.getNavigationBarView() != null) {
-                    mStatusBar.getNavigationBarView().setWakeAndUnlocking(true);
-                }
                 Trace.endSection();
                 break;
             case MODE_ONLY_WAKE:
             case MODE_NONE:
                 break;
         }
-        mStatusBar.notifyBiometricAuthModeChanged();
+        onModeChanged(mMode);
+        if (mBiometricModeListener != null) {
+            mBiometricModeListener.notifyBiometricAuthModeChanged();
+        }
         Trace.endSection();
     }
 
+    private void onModeChanged(@WakeAndUnlockMode int mode) {
+        if (mBiometricModeListener != null) {
+            mBiometricModeListener.onModeChanged(mode);
+        }
+    }
+
     private void showBouncer() {
         if (mMode == MODE_SHOW_BOUNCER) {
             mKeyguardViewController.showBouncer(false);
@@ -619,10 +630,10 @@
         mMode = MODE_NONE;
         mBiometricType = null;
         mNotificationShadeWindowController.setForceDozeBrightness(false);
-        if (mStatusBar.getNavigationBarView() != null) {
-            mStatusBar.getNavigationBarView().setWakeAndUnlocking(false);
+        if (mBiometricModeListener != null) {
+            mBiometricModeListener.onResetMode();
+            mBiometricModeListener.notifyBiometricAuthModeChanged();
         }
-        mStatusBar.notifyBiometricAuthModeChanged();
     }
 
     @VisibleForTesting
@@ -702,4 +713,14 @@
                 return 3;
         }
     }
+
+    /** An interface to interact with the {@link BiometricUnlockController}. */
+    public interface BiometricModeListener {
+        /** Called when {@code mMode} is reset to {@link #MODE_NONE}. */
+        void onResetMode();
+        /** Called when {@code mMode} has changed in {@link #startWakeAndUnlock(int)}. */
+        void onModeChanged(@WakeAndUnlockMode int mode);
+        /** Called after processing {@link #onModeChanged(int)}. */
+        void notifyBiometricAuthModeChanged();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
index ef0f7cd..f25359e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
@@ -14,7 +14,6 @@
 
 package com.android.systemui.statusbar.phone;
 
-import static com.android.systemui.plugins.DarkIconDispatcher.DEFAULT_ICON_TINT;
 import static com.android.systemui.plugins.DarkIconDispatcher.getTint;
 
 import android.animation.ArgbEvaluator;
@@ -25,18 +24,17 @@
 import android.widget.ImageView;
 
 import com.android.systemui.R;
-import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.CommandQueue;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  */
-@Singleton
+@SysUISingleton
 public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher,
         LightBarTransitionsController.DarkIntensityApplier {
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
index 0731a56..31965d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
@@ -27,8 +27,8 @@
 import android.widget.LinearLayout;
 
 import com.android.internal.statusbar.StatusBarIcon;
-import com.android.systemui.DemoMode;
 import com.android.systemui.R;
+import com.android.systemui.demomode.DemoMode;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
 import com.android.systemui.statusbar.StatusBarIconView;
@@ -39,7 +39,9 @@
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
 
 import java.util.ArrayList;
+import java.util.List;
 
+//TODO: This should be a controller, not its own view
 public class DemoStatusIcons extends StatusIconContainer implements DemoMode, DarkReceiver {
     private static final String TAG = "DemoStatusIcons";
 
@@ -90,73 +92,84 @@
     }
 
     @Override
+    public List<String> demoCommands() {
+        List<String> commands = new ArrayList<>();
+        commands.add(COMMAND_STATUS);
+        return commands;
+    }
+
+    @Override
+    public void onDemoModeStarted() {
+        mDemoMode = true;
+        mStatusIcons.setVisibility(View.GONE);
+        setVisibility(View.VISIBLE);
+    }
+
+    @Override
+    public void onDemoModeFinished() {
+        mDemoMode = false;
+        mStatusIcons.setVisibility(View.VISIBLE);
+        setVisibility(View.GONE);
+    }
+
+    @Override
     public void dispatchDemoCommand(String command, Bundle args) {
-        if (!mDemoMode && command.equals(COMMAND_ENTER)) {
-            mDemoMode = true;
-            mStatusIcons.setVisibility(View.GONE);
-            setVisibility(View.VISIBLE);
-        } else if (mDemoMode && command.equals(COMMAND_EXIT)) {
-            mDemoMode = false;
-            mStatusIcons.setVisibility(View.VISIBLE);
-            setVisibility(View.GONE);
-        } else if (mDemoMode && command.equals(COMMAND_STATUS)) {
-            String volume = args.getString("volume");
-            if (volume != null) {
-                int iconId = volume.equals("vibrate") ? R.drawable.stat_sys_ringer_vibrate
-                        : 0;
-                updateSlot("volume", null, iconId);
-            }
-            String zen = args.getString("zen");
-            if (zen != null) {
-                int iconId = zen.equals("dnd") ? R.drawable.stat_sys_dnd : 0;
-                updateSlot("zen", null, iconId);
-            }
-            String bt = args.getString("bluetooth");
-            if (bt != null) {
-                int iconId = bt.equals("connected")
-                        ? R.drawable.stat_sys_data_bluetooth_connected : 0;
-                updateSlot("bluetooth", null, iconId);
-            }
-            String location = args.getString("location");
-            if (location != null) {
-                int iconId = location.equals("show") ? PhoneStatusBarPolicy.LOCATION_STATUS_ICON_ID
-                        : 0;
-                updateSlot("location", null, iconId);
-            }
-            String alarm = args.getString("alarm");
-            if (alarm != null) {
-                int iconId = alarm.equals("show") ? R.drawable.stat_sys_alarm
-                        : 0;
-                updateSlot("alarm_clock", null, iconId);
-            }
-            String tty = args.getString("tty");
-            if (tty != null) {
-                int iconId = tty.equals("show") ? R.drawable.stat_sys_tty_mode
-                        : 0;
-                updateSlot("tty", null, iconId);
-            }
-            String mute = args.getString("mute");
-            if (mute != null) {
-                int iconId = mute.equals("show") ? android.R.drawable.stat_notify_call_mute
-                        : 0;
-                updateSlot("mute", null, iconId);
-            }
-            String speakerphone = args.getString("speakerphone");
-            if (speakerphone != null) {
-                int iconId = speakerphone.equals("show") ? android.R.drawable.stat_sys_speakerphone
-                        : 0;
-                updateSlot("speakerphone", null, iconId);
-            }
-            String cast = args.getString("cast");
-            if (cast != null) {
-                int iconId = cast.equals("show") ? R.drawable.stat_sys_cast : 0;
-                updateSlot("cast", null, iconId);
-            }
-            String hotspot = args.getString("hotspot");
-            if (hotspot != null) {
-                int iconId = hotspot.equals("show") ? R.drawable.stat_sys_hotspot : 0;
-                updateSlot("hotspot", null, iconId);
-            }
+        String volume = args.getString("volume");
+        if (volume != null) {
+            int iconId = volume.equals("vibrate") ? R.drawable.stat_sys_ringer_vibrate
+                    : 0;
+            updateSlot("volume", null, iconId);
+        }
+        String zen = args.getString("zen");
+        if (zen != null) {
+            int iconId = zen.equals("dnd") ? R.drawable.stat_sys_dnd : 0;
+            updateSlot("zen", null, iconId);
+        }
+        String bt = args.getString("bluetooth");
+        if (bt != null) {
+            int iconId = bt.equals("connected")
+                    ? R.drawable.stat_sys_data_bluetooth_connected : 0;
+            updateSlot("bluetooth", null, iconId);
+        }
+        String location = args.getString("location");
+        if (location != null) {
+            int iconId = location.equals("show") ? PhoneStatusBarPolicy.LOCATION_STATUS_ICON_ID
+                    : 0;
+            updateSlot("location", null, iconId);
+        }
+        String alarm = args.getString("alarm");
+        if (alarm != null) {
+            int iconId = alarm.equals("show") ? R.drawable.stat_sys_alarm
+                    : 0;
+            updateSlot("alarm_clock", null, iconId);
+        }
+        String tty = args.getString("tty");
+        if (tty != null) {
+            int iconId = tty.equals("show") ? R.drawable.stat_sys_tty_mode
+                    : 0;
+            updateSlot("tty", null, iconId);
+        }
+        String mute = args.getString("mute");
+        if (mute != null) {
+            int iconId = mute.equals("show") ? android.R.drawable.stat_notify_call_mute
+                    : 0;
+            updateSlot("mute", null, iconId);
+        }
+        String speakerphone = args.getString("speakerphone");
+        if (speakerphone != null) {
+            int iconId = speakerphone.equals("show") ? android.R.drawable.stat_sys_speakerphone
+                    : 0;
+            updateSlot("speakerphone", null, iconId);
+        }
+        String cast = args.getString("cast");
+        if (cast != null) {
+            int iconId = cast.equals("show") ? R.drawable.stat_sys_cast : 0;
+            updateSlot("cast", null, iconId);
+        }
+        String hotspot = args.getString("hotspot");
+        if (hotspot != null) {
+            int iconId = hotspot.equals("show") ? R.drawable.stat_sys_hotspot : 0;
+            updateSlot("hotspot", null, iconId);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 5fab4be..6495144 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -25,6 +25,7 @@
 import android.util.MathUtils;
 
 import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.doze.AlwaysOnDisplayPolicy;
 import com.android.systemui.doze.DozeScreenState;
@@ -34,12 +35,11 @@
 import java.io.PrintWriter;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Retrieve doze information
  */
-@Singleton
+@SysUISingleton
 public class DozeParameters implements TunerService.Tunable,
         com.android.systemui.plugins.statusbar.DozeParameters {
     private static final int MAX_DURATION = 60 * 1000;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
index e7d6eba..b2cf72a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
@@ -22,18 +22,18 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Dependency;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.doze.DozeHost;
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Controller which handles all the doze animations of the scrims.
  */
-@Singleton
+@SysUISingleton
 public class DozeScrimController implements StateListener {
     private static final String TAG = "DozeScrimController";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index 4afeba8..efb2469 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -31,16 +31,18 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.assist.AssistManager;
+import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.doze.DozeHost;
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.doze.DozeReceiver;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.PulseExpansionHandler;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -48,14 +50,13 @@
 import java.util.ArrayList;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 
 /**
  * Implementation of DozeHost for SystemUI.
  */
-@Singleton
+@SysUISingleton
 public final class DozeServiceHost implements DozeHost {
     private static final String TAG = "DozeServiceHost";
     private final ArrayList<Callback> mCallbacks = new ArrayList<>();
@@ -81,12 +82,12 @@
     private final Lazy<AssistManager> mAssistManagerLazy;
     private final DozeScrimController mDozeScrimController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    private final VisualStabilityManager mVisualStabilityManager;
     private final PulseExpansionHandler mPulseExpansionHandler;
     private final NotificationShadeWindowController mNotificationShadeWindowController;
     private final NotificationWakeUpCoordinator mNotificationWakeUpCoordinator;
     private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
     private final LockscreenLockIconController mLockscreenLockIconController;
+    private final AuthController mAuthController;
     private NotificationIconAreaController mNotificationIconAreaController;
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private NotificationPanelViewController mNotificationPanel;
@@ -105,11 +106,12 @@
             KeyguardViewMediator keyguardViewMediator,
             Lazy<AssistManager> assistManagerLazy,
             DozeScrimController dozeScrimController, KeyguardUpdateMonitor keyguardUpdateMonitor,
-            VisualStabilityManager visualStabilityManager,
             PulseExpansionHandler pulseExpansionHandler,
             NotificationShadeWindowController notificationShadeWindowController,
             NotificationWakeUpCoordinator notificationWakeUpCoordinator,
-            LockscreenLockIconController lockscreenLockIconController) {
+            LockscreenLockIconController lockscreenLockIconController,
+            AuthController authController,
+            NotificationIconAreaController notificationIconAreaController) {
         super();
         mDozeLog = dozeLog;
         mPowerManager = powerManager;
@@ -124,11 +126,12 @@
         mAssistManagerLazy = assistManagerLazy;
         mDozeScrimController = dozeScrimController;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
-        mVisualStabilityManager = visualStabilityManager;
         mPulseExpansionHandler = pulseExpansionHandler;
         mNotificationShadeWindowController = notificationShadeWindowController;
         mNotificationWakeUpCoordinator = notificationWakeUpCoordinator;
         mLockscreenLockIconController = lockscreenLockIconController;
+        mAuthController = authController;
+        mNotificationIconAreaController = notificationIconAreaController;
     }
 
     // TODO: we should try to not pass status bar in here if we can avoid it.
@@ -136,13 +139,13 @@
     /**
      * Initialize instance with objects only available later during execution.
      */
-    public void initialize(StatusBar statusBar,
-            NotificationIconAreaController notificationIconAreaController,
+    public void initialize(
+            StatusBar statusBar,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             NotificationShadeWindowViewController notificationShadeWindowViewController,
-            NotificationPanelViewController notificationPanel, View ambientIndicationContainer) {
+            NotificationPanelViewController notificationPanel,
+            View ambientIndicationContainer) {
         mStatusBar = statusBar;
-        mNotificationIconAreaController = notificationIconAreaController;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
         mNotificationPanel = notificationPanel;
         mNotificationShadeWindowViewController = notificationShadeWindowViewController;
@@ -254,11 +257,10 @@
             }
 
             private void setPulsing(boolean pulsing) {
-                mStatusBarStateController.setPulsing(pulsing);
                 mStatusBarKeyguardViewManager.setPulsing(pulsing);
                 mKeyguardViewMediator.setPulsing(pulsing);
                 mNotificationPanel.setPulsing(pulsing);
-                mVisualStabilityManager.setPulsing(pulsing);
+                mStatusBarStateController.setPulsing(pulsing);
                 mIgnoreTouchWhilePulsing = false;
                 if (mKeyguardUpdateMonitor != null && passiveAuthInterrupt) {
                     mKeyguardUpdateMonitor.onAuthInterruptDetected(pulsing /* active */);
@@ -296,6 +298,7 @@
     @Override
     public void dozeTimeTick() {
         mNotificationPanel.dozeTimeTick();
+        mAuthController.dozeTimeTick();
         if (mAmbientIndicationContainer instanceof DozeReceiver) {
             ((DozeReceiver) mAmbientIndicationContainer).dozeTimeTick();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 1d82e08..8092cb9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -31,8 +31,8 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -58,6 +58,7 @@
     private final NotificationGroupManager mGroupManager;
     private final List<OnHeadsUpPhoneListenerChange> mHeadsUpPhoneListeners = new ArrayList<>();
     private final int mAutoHeadsUpNotificationDecay;
+    // TODO (b/162832756): remove visual stability manager when migrating to new pipeline
     private VisualStabilityManager mVisualStabilityManager;
     private boolean mReleaseOnExpandFinish;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
index 0827511..242bd0a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
@@ -21,6 +21,7 @@
 import android.hardware.biometrics.BiometricSourceType
 import android.provider.Settings
 import com.android.systemui.Dumpable
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
@@ -30,9 +31,8 @@
 import java.io.FileDescriptor
 import java.io.PrintWriter
 import javax.inject.Inject
-import javax.inject.Singleton
 
-@Singleton
+@SysUISingleton
 open class KeyguardBypassController : Dumpable {
 
     private val mKeyguardStateController: KeyguardStateController
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissUtil.java
index 834d2a5..c0181f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissUtil.java
@@ -18,16 +18,16 @@
 
 import android.util.Log;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Executes actions that require the screen to be unlocked. Delegates the actual handling to an
  * implementation passed via {@link #setDismissHandler}.
  */
-@Singleton
+@SysUISingleton
 public class KeyguardDismissUtil implements KeyguardDismissHandler {
     private static final String TAG = "KeyguardDismissUtil";
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardEnvironmentImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardEnvironmentImpl.java
index e763496..817b86b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardEnvironmentImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardEnvironmentImpl.java
@@ -21,14 +21,14 @@
 import android.util.Log;
 
 import com.android.systemui.Dependency;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
-@Singleton
+@SysUISingleton
 public class KeyguardEnvironmentImpl implements KeyguardEnvironment {
 
     private static final String TAG = "KeyguardEnvironmentImpl";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
index 3e5eb5f..24c9021 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
@@ -32,6 +32,8 @@
 import com.android.internal.view.AppearanceRegion;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -40,12 +42,11 @@
 import java.io.PrintWriter;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Controls how light status bar flag applies to the icons.
  */
-@Singleton
+@SysUISingleton
 public class LightBarController implements BatteryController.BatteryStateChangeCallback, Dumpable {
 
     private static final float NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD = 0.1f;
@@ -125,7 +126,7 @@
         updateStatus();
     }
 
-    void onNavigationBarAppearanceChanged(@Appearance int appearance, boolean nbModeChanged,
+    public void onNavigationBarAppearanceChanged(@Appearance int appearance, boolean nbModeChanged,
             int navigationBarMode, boolean navbarColorManagedByIme) {
         int diff = appearance ^ mAppearance;
         if ((diff & APPEARANCE_LIGHT_NAVIGATION_BARS) != 0 || nbModeChanged) {
@@ -144,7 +145,7 @@
         mNavbarColorManagedByIme = navbarColorManagedByIme;
     }
 
-    void onNavigationBarModeChanged(int newBarMode) {
+    public void onNavigationBarModeChanged(int newBarMode) {
         mHasLightNavigationBar = isLight(mAppearance, newBarMode, APPEARANCE_LIGHT_NAVIGATION_BARS);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
index 8e192c5..d27a3d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
@@ -29,13 +29,13 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.internal.view.AppearanceRegion;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Apps can request a low profile mode {@link View.SYSTEM_UI_FLAG_LOW_PROFILE}
@@ -45,7 +45,7 @@
  * This controller shows and hides the notification dot in the status bar to indicate
  * whether there are notifications when the device is in {@link View.SYSTEM_UI_FLAG_LOW_PROFILE}.
  */
-@Singleton
+@SysUISingleton
 public class LightsOutNotifController {
     private final CommandQueue mCommandQueue;
     private final NotificationEntryManager mEntryManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java
index 0d6597f..094ebb9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java
@@ -27,15 +27,15 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.EventLogConstants;
 import com.android.systemui.EventLogTags;
+import com.android.systemui.dagger.SysUISingleton;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Wrapper that emits both new- and old-style gesture logs.
  * TODO: delete this once the old logs are no longer needed.
  */
-@Singleton
+@SysUISingleton
 public class LockscreenGestureLogger {
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java
index 1dc0f07..11ceedf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java
@@ -37,6 +37,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -54,10 +55,9 @@
 import java.util.Optional;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /** Controls the {@link LockIcon} in the lockscreen. */
-@Singleton
+@SysUISingleton
 public class LockscreenLockIconController {
 
     private final LockscreenGestureLogger mLockscreenGestureLogger;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
index 04211df..a6811c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
@@ -42,6 +42,7 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
@@ -53,12 +54,11 @@
 import java.util.Objects;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Manages the lockscreen wallpaper.
  */
-@Singleton
+@SysUISingleton
 public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implements Runnable,
         Dumpable {
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
index 07e9f94..94d1bf4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
@@ -24,18 +24,20 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 
+import androidx.annotation.NonNull;
+
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 
 import java.util.ArrayList;
 import java.util.LinkedList;
 import java.util.List;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  */
-@Singleton
+@SysUISingleton
 public class ManagedProfileControllerImpl implements ManagedProfileController {
 
     private final List<Callback> mCallbacks = new ArrayList<>();
@@ -57,7 +59,8 @@
         mProfiles = new LinkedList<UserInfo>();
     }
 
-    public void addCallback(Callback callback) {
+    @Override
+    public void addCallback(@NonNull Callback callback) {
         mCallbacks.add(callback);
         if (mCallbacks.size() == 1) {
             setListening(true);
@@ -65,7 +68,8 @@
         callback.onManagedProfileChanged();
     }
 
-    public void removeCallback(Callback callback) {
+    @Override
+    public void removeCallback(@NonNull Callback callback) {
         if (mCallbacks.remove(callback) && mCallbacks.size() == 0) {
             setListening(false);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
index 80785db..c44c59c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
@@ -23,6 +23,7 @@
 
 import com.android.systemui.Dependency;
 import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.statusbar.StatusBarState;
@@ -40,14 +41,13 @@
 import java.util.Objects;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 
 /**
  * A class to handle notifications and their corresponding groups.
  */
-@Singleton
+@SysUISingleton
 public class NotificationGroupManager implements OnHeadsUpChangedListener, StateListener {
 
     private static final String TAG = "NotificationGroupManager";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index 7bbe1c9..bda35fb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -5,9 +5,9 @@
 import android.content.res.Resources;
 import android.graphics.Color;
 import android.graphics.Rect;
+import android.os.Bundle;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.view.ViewGroup;
 import android.widget.FrameLayout;
 
 import androidx.annotation.NonNull;
@@ -20,6 +20,9 @@
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.demomode.DemoMode;
+import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -31,20 +34,26 @@
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.notification.collection.ListEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 
 import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
 import java.util.function.Function;
 
+import javax.inject.Inject;
+
 /**
  * A controller for the space in the status bar to the left of the system icons. This area is
  * normally reserved for notifications.
  */
-public class NotificationIconAreaController implements DarkReceiver,
+@SysUISingleton
+public class NotificationIconAreaController implements
+        DarkReceiver,
         StatusBarStateController.StateListener,
-        NotificationWakeUpCoordinator.WakeUpListener {
+        NotificationWakeUpCoordinator.WakeUpListener,
+        DemoMode {
 
     public static final String HIGH_PRIORITY = "high_priority";
     private static final long AOD_ICONS_APPEAR_DURATION = 200;
@@ -57,13 +66,14 @@
     private final KeyguardBypassController mBypassController;
     private final DozeParameters mDozeParameters;
     private final BubbleController mBubbleController;
+    private final StatusBarWindowController mStatusBarWindowController;
 
     private int mIconSize;
     private int mIconHPadding;
     private int mIconTint = Color.WHITE;
     private int mCenteredIconTint = Color.WHITE;
 
-    private StatusBar mStatusBar;
+    private List<ListEntry> mNotificationEntries = List.of();
     protected View mNotificationIconArea;
     private NotificationIconContainer mNotificationIcons;
     private NotificationIconContainer mShelfIcons;
@@ -72,8 +82,10 @@
     private NotificationIconContainer mAodIcons;
     private StatusBarIconView mCenteredIconView;
     private final Rect mTintArea = new Rect();
-    private ViewGroup mNotificationScrollLayout;
     private Context mContext;
+
+    private final DemoModeController mDemoModeController;
+
     private int mAodIconAppearTranslation;
 
     private boolean mAnimationsEnabled;
@@ -88,24 +100,24 @@
             new NotificationListener.NotificationSettingsListener() {
                 @Override
                 public void onStatusBarIconsBehaviorChanged(boolean hideSilentStatusIcons) {
-                        mShowLowPriority = !hideSilentStatusIcons;
-                        if (mNotificationScrollLayout != null) {
-                            updateStatusBarIcons();
-                        }
+                    mShowLowPriority = !hideSilentStatusIcons;
+                    updateStatusBarIcons();
                 }
             };
 
+    @Inject
     public NotificationIconAreaController(
             Context context,
-            StatusBar statusBar,
             StatusBarStateController statusBarStateController,
             NotificationWakeUpCoordinator wakeUpCoordinator,
             KeyguardBypassController keyguardBypassController,
             NotificationMediaManager notificationMediaManager,
             NotificationListener notificationListener,
             DozeParameters dozeParameters,
-            BubbleController bubbleController) {
-        mStatusBar = statusBar;
+            BubbleController bubbleController,
+            DemoModeController demoModeController,
+            DarkIconDispatcher darkIconDispatcher,
+            StatusBarWindowController statusBarWindowController) {
         mContrastColorUtil = ContrastColorUtil.getInstance(context);
         mContext = context;
         mStatusBarStateController = statusBarStateController;
@@ -116,10 +128,14 @@
         wakeUpCoordinator.addListener(this);
         mBypassController = keyguardBypassController;
         mBubbleController = bubbleController;
+        mDemoModeController = demoModeController;
+        mDemoModeController.addCallback(this);
+        mStatusBarWindowController = statusBarWindowController;
         notificationListener.addNotificationSettingsListener(mSettingsListener);
 
         initializeNotificationAreaViews(context);
         reloadAodColor();
+        darkIconDispatcher.addDarkReceiver(this);
     }
 
     protected View inflateIconArea(LayoutInflater inflater) {
@@ -136,22 +152,21 @@
         mNotificationIconArea = inflateIconArea(layoutInflater);
         mNotificationIcons = mNotificationIconArea.findViewById(R.id.notificationIcons);
 
-        mNotificationScrollLayout = mStatusBar.getNotificationScrollLayout();
-
         mCenteredIconArea = layoutInflater.inflate(R.layout.center_icon_area, null);
         mCenteredIcon = mCenteredIconArea.findViewById(R.id.centeredIcon);
-
-        initAodIcons();
     }
 
-    public void initAodIcons() {
+    /**
+     * Called by the StatusBar. The StatusBar passes the NotificationIconContainer which holds
+     * the aod icons.
+     */
+    void setupAodIcons(@NonNull NotificationIconContainer aodIcons) {
         boolean changed = mAodIcons != null;
         if (changed) {
             mAodIcons.setAnimationsEnabled(false);
             mAodIcons.removeAllViews();
         }
-        mAodIcons = mStatusBar.getNotificationShadeWindowView().findViewById(
-                R.id.clock_notification_icon_container);
+        mAodIcons = aodIcons;
         mAodIcons.setOnLockScreen(true);
         updateAodIconsVisibility(false /* animate */);
         updateAnimations();
@@ -189,7 +204,7 @@
     @NonNull
     private FrameLayout.LayoutParams generateIconLayoutParams() {
         return new FrameLayout.LayoutParams(
-                mIconSize + 2 * mIconHPadding, getHeight());
+                mIconSize + 2 * mIconHPadding, mStatusBarWindowController.getStatusBarHeight());
     }
 
     private void reloadDimens(Context context) {
@@ -228,29 +243,17 @@
             mTintArea.set(tintArea);
         }
 
-        if (mNotificationIconArea != null) {
-            if (DarkIconDispatcher.isInArea(tintArea, mNotificationIconArea)) {
-                mIconTint = iconTint;
-            }
-        } else {
+        if (DarkIconDispatcher.isInArea(tintArea, mNotificationIconArea)) {
             mIconTint = iconTint;
         }
 
-        if (mCenteredIconArea != null) {
-            if (DarkIconDispatcher.isInArea(tintArea, mCenteredIconArea)) {
-                mCenteredIconTint = iconTint;
-            }
-        } else {
+        if (DarkIconDispatcher.isInArea(tintArea, mCenteredIconArea)) {
             mCenteredIconTint = iconTint;
         }
 
         applyNotificationIconsTint();
     }
 
-    protected int getHeight() {
-        return mStatusBar.getStatusBarHeight();
-    }
-
     protected boolean shouldShowNotificationIcon(NotificationEntry entry,
             boolean showAmbient, boolean showLowPriority, boolean hideDismissed,
             boolean hideRepliedMessages, boolean hideCurrentMedia, boolean hideCenteredIcon,
@@ -300,11 +303,15 @@
         }
         return true;
     }
-
     /**
      * Updates the notifications with the given list of notifications to display.
      */
-    public void updateNotificationIcons() {
+    public void updateNotificationIcons(List<ListEntry> entries) {
+        mNotificationEntries = entries;
+        updateNotificationIcons();
+    }
+
+    private void updateNotificationIcons() {
         updateStatusBarIcons();
         updateShelfIcons();
         updateCenterIcon();
@@ -381,18 +388,15 @@
             NotificationIconContainer hostLayout, boolean showAmbient, boolean showLowPriority,
             boolean hideDismissed, boolean hideRepliedMessages, boolean hideCurrentMedia,
             boolean hideCenteredIcon, boolean hidePulsing, boolean onlyShowCenteredIcon) {
-        ArrayList<StatusBarIconView> toShow = new ArrayList<>(
-                mNotificationScrollLayout.getChildCount());
-
+        ArrayList<StatusBarIconView> toShow = new ArrayList<>(mNotificationEntries.size());
         // Filter out ambient notifications and notification children.
-        for (int i = 0; i < mNotificationScrollLayout.getChildCount(); i++) {
-            View view = mNotificationScrollLayout.getChildAt(i);
-            if (view instanceof ExpandableNotificationRow) {
-                NotificationEntry ent = ((ExpandableNotificationRow) view).getEntry();
-                if (shouldShowNotificationIcon(ent, showAmbient, showLowPriority, hideDismissed,
+        for (int i = 0; i < mNotificationEntries.size(); i++) {
+            NotificationEntry entry = mNotificationEntries.get(i).getRepresentativeEntry();
+            if (entry != null && entry.getRow() != null) {
+                if (shouldShowNotificationIcon(entry, showAmbient, showLowPriority, hideDismissed,
                         hideRepliedMessages, hideCurrentMedia, hideCenteredIcon, hidePulsing,
                         onlyShowCenteredIcon)) {
-                    StatusBarIconView iconView = function.apply(ent);
+                    StatusBarIconView iconView = function.apply(entry);
                     if (iconView != null) {
                         toShow.add(iconView);
                     }
@@ -597,13 +601,16 @@
         mAodIconTint = Utils.getColorAttrDefaultColor(mContext,
                 R.attr.wallpaperTextColor);
     }
+
     private void updateAodIconColors() {
-        for (int i = 0; i < mAodIcons.getChildCount(); i++) {
-            final StatusBarIconView iv = (StatusBarIconView) mAodIcons.getChildAt(i);
-            if (iv.getWidth() != 0) {
-                updateTintForIcon(iv, mAodIconTint);
-            } else {
-                iv.executeOnLayout(() -> updateTintForIcon(iv, mAodIconTint));
+        if (mAodIcons != null) {
+            for (int i = 0; i < mAodIcons.getChildCount(); i++) {
+                final StatusBarIconView iv = (StatusBarIconView) mAodIcons.getChildAt(i);
+                if (iv.getWidth() != 0) {
+                    updateTintForIcon(iv, mAodIconTint);
+                } else {
+                    iv.executeOnLayout(() -> updateTintForIcon(iv, mAodIconTint));
+                }
             }
         }
     }
@@ -666,4 +673,27 @@
             }
         }
     }
+
+    @Override
+    public List<String> demoCommands() {
+        ArrayList<String> commands = new ArrayList<>();
+        commands.add(DemoMode.COMMAND_NOTIFICATIONS);
+        return commands;
+    }
+
+    @Override
+    public void dispatchDemoCommand(String command, Bundle args) {
+        if (mNotificationIconArea != null) {
+            String visible = args.getString("visible");
+            int vis = "false".equals(visible) ? View.INVISIBLE : View.VISIBLE;
+            mNotificationIconArea.setVisibility(vis);
+        }
+    }
+
+    @Override
+    public void onDemoModeFinished() {
+        if (mNotificationIconArea != null) {
+            mNotificationIconArea.setVisibility(View.VISIBLE);
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 70fb71a..5974a53 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -100,6 +100,7 @@
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
 import com.android.systemui.statusbar.notification.ViewGroupFadeHelper;
+import com.android.systemui.statusbar.notification.collection.ListEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -177,6 +178,7 @@
     private final ConfigurationController mConfigurationController;
     private final FlingAnimationUtils.Builder mFlingAnimationUtilsBuilder;
     private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
+    private final NotificationIconAreaController mNotificationIconAreaController;
 
     // Cap and total height of Roboto font. Needs to be adjusted when font for the big clock is
     // changed.
@@ -502,7 +504,8 @@
             BiometricUnlockController biometricUnlockController,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             Provider<KeyguardClockSwitchController> keyguardClockSwitchControllerProvider,
-            NotificationStackScrollLayoutController notificationStackScrollLayoutController) {
+            NotificationStackScrollLayoutController notificationStackScrollLayoutController,
+            NotificationIconAreaController notificationIconAreaController) {
         super(view, falsingManager, dozeLog, keyguardStateController,
                 (SysuiStatusBarStateController) statusBarStateController, vibratorHelper,
                 latencyTracker, flingAnimationUtilsBuilder, statusBarTouchableRegionManager);
@@ -516,6 +519,7 @@
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
         mKeyguardClockSwitchControllerProvider = keyguardClockSwitchControllerProvider;
         mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
+        mNotificationIconAreaController = notificationIconAreaController;
         mView.setWillNotDraw(!DEBUG);
         mInjectionInflationController = injectionInflationController;
         mFalsingManager = falsingManager;
@@ -3066,7 +3070,19 @@
         mNotificationStackScrollLayoutController.updateSpeedBumpIndex();
         mNotificationStackScrollLayoutController.updateFooter();
         updateShowEmptyShadeView();
-        mNotificationStackScrollLayoutController.updateIconAreaViews();
+        mNotificationIconAreaController.updateNotificationIcons(createVisibleEntriesList());
+    }
+
+    private List<ListEntry> createVisibleEntriesList() {
+        List<ListEntry> entries = new ArrayList<>(
+                mNotificationStackScrollLayoutController.getChildCount());
+        for (int i = 0; i < mNotificationStackScrollLayoutController.getChildCount(); i++) {
+            View view = mNotificationStackScrollLayoutController.getChildAt(i);
+            if (view instanceof ExpandableNotificationRow) {
+                entries.add(((ExpandableNotificationRow) view).getEntry());
+            }
+        }
+        return entries;
     }
 
     public void onUpdateRowStates() {
@@ -3074,7 +3090,8 @@
     }
 
     public boolean hasPulsingNotifications() {
-        return mNotificationStackScrollLayoutController.hasPulsingNotifications();
+        return mNotificationStackScrollLayoutController
+                .getNotificationListContainer().hasPulsingNotifications();
     }
 
     public ActivatableNotificationView getActivatedChild() {
@@ -3093,15 +3110,17 @@
         mNotificationStackScrollLayoutController.setScrollingEnabled(b);
     }
 
-    public void initDependencies(StatusBar statusBar, NotificationGroupManager groupManager,
+    /**
+     * Initialize objects instead of injecting to avoid circular dependencies.
+     */
+    public void initDependencies(
+            StatusBar statusBar,
+            NotificationGroupManager groupManager,
             NotificationShelfController notificationShelfController,
-            NotificationIconAreaController notificationIconAreaController,
             ScrimController scrimController) {
         setStatusBar(statusBar);
         setGroupManager(mGroupManager);
         mNotificationStackScrollLayoutController.setNotificationPanelController(this);
-        mNotificationStackScrollLayoutController
-                .setIconAreaController(notificationIconAreaController);
         mNotificationStackScrollLayoutController.setStatusBar(statusBar);
         mNotificationStackScrollLayoutController.setGroupManager(groupManager);
         mNotificationStackScrollLayoutController.setShelfController(notificationShelfController);
@@ -3259,6 +3278,10 @@
         return new OnConfigurationChangedListener();
     }
 
+    public NotificationStackScrollLayoutController getNotificationStackScrollLayoutController() {
+        return mNotificationStackScrollLayoutController;
+    }
+
     private class OnHeightChangedListener implements ExpandableView.OnHeightChangedListener {
         @Override
         public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
index bc73be1..3c43a17 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
@@ -43,11 +43,12 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
-import com.android.systemui.statusbar.RemoteInputController.Callback;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -66,14 +67,13 @@
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Encapsulates all logic for the notification shade window state management.
  */
-@Singleton
-public class NotificationShadeWindowController implements Callback, Dumpable,
-        ConfigurationListener {
+@SysUISingleton
+public class NotificationShadeWindowControllerImpl implements NotificationShadeWindowController,
+        Dumpable, ConfigurationListener {
 
     private static final String TAG = "NotificationShadeWindowController";
     private static final boolean DEBUG = false;
@@ -103,7 +103,7 @@
     private final SysuiColorExtractor mColorExtractor;
 
     @Inject
-    public NotificationShadeWindowController(Context context, WindowManager windowManager,
+    public NotificationShadeWindowControllerImpl(Context context, WindowManager windowManager,
             IActivityManager activityManager, DozeParameters dozeParameters,
             StatusBarStateController statusBarStateController,
             ConfigurationController configurationController,
@@ -147,6 +147,7 @@
     /**
      * Register to receive notifications about status bar window state changes.
      */
+    @Override
     public void registerCallback(StatusBarWindowCallback callback) {
         // Prevent adding duplicate callbacks
         for (int i = 0; i < mCallbacks.size(); i++) {
@@ -161,6 +162,7 @@
      * Register a listener to monitor scrims visibility
      * @param listener A listener to monitor scrims visibility
      */
+    @Override
     public void setScrimsVisibilityListener(Consumer<Integer> listener) {
         if (listener != null && mScrimsVisibilityListener != listener) {
             mScrimsVisibilityListener = listener;
@@ -176,6 +178,7 @@
     /**
      * Adds the notification shade view to the window manager.
      */
+    @Override
     public void attach() {
         // Now that the notification shade encompasses the sliding panel and its
         // translucent backdrop, the entire thing is made TRANSLUCENT and is
@@ -214,14 +217,17 @@
         }
     }
 
+    @Override
     public void setNotificationShadeView(ViewGroup view) {
         mNotificationShadeView = view;
     }
 
+    @Override
     public ViewGroup getNotificationShadeView() {
         return mNotificationShadeView;
     }
 
+    @Override
     public void setDozeScreenBrightness(int value) {
         mScreenBrightnessDoze = value / 255f;
     }
@@ -406,6 +412,7 @@
         notifyStateChangedCallbacks();
     }
 
+    @Override
     public void notifyStateChangedCallbacks() {
         for (int i = 0; i < mCallbacks.size(); i++) {
             StatusBarWindowCallback cb = mCallbacks.get(i).get();
@@ -445,62 +452,74 @@
         }
     }
 
+    @Override
     public void setKeyguardShowing(boolean showing) {
         mCurrentState.mKeyguardShowing = showing;
         apply(mCurrentState);
     }
 
+    @Override
     public void setKeyguardOccluded(boolean occluded) {
         mCurrentState.mKeyguardOccluded = occluded;
         apply(mCurrentState);
     }
 
+    @Override
     public void setKeyguardNeedsInput(boolean needsInput) {
         mCurrentState.mKeyguardNeedsInput = needsInput;
         apply(mCurrentState);
     }
 
+    @Override
     public void setPanelVisible(boolean visible) {
         mCurrentState.mPanelVisible = visible;
         mCurrentState.mNotificationShadeFocusable = visible;
         apply(mCurrentState);
     }
 
+    @Override
     public void setNotificationShadeFocusable(boolean focusable) {
         mCurrentState.mNotificationShadeFocusable = focusable;
         apply(mCurrentState);
     }
 
+    @Override
     public void setBouncerShowing(boolean showing) {
         mCurrentState.mBouncerShowing = showing;
         apply(mCurrentState);
     }
 
+    @Override
     public void setBackdropShowing(boolean showing) {
         mCurrentState.mBackdropShowing = showing;
         apply(mCurrentState);
     }
 
+    @Override
     public void setKeyguardFadingAway(boolean keyguardFadingAway) {
         mCurrentState.mKeyguardFadingAway = keyguardFadingAway;
         apply(mCurrentState);
     }
 
+    @Override
     public void setQsExpanded(boolean expanded) {
         mCurrentState.mQsExpanded = expanded;
         apply(mCurrentState);
     }
 
+    @Override
     public void setForceUserActivity(boolean forceUserActivity) {
         mCurrentState.mForceUserActivity = forceUserActivity;
         apply(mCurrentState);
     }
 
-    void setLaunchingActivity(boolean launching) {
+    @Override
+    public void setLaunchingActivity(boolean launching) {
         mCurrentState.mLaunchingActivity = launching;
         apply(mCurrentState);
     }
 
+    @Override
     public void setScrimsVisibility(int scrimsVisibility) {
         mCurrentState.mScrimsVisibility = scrimsVisibility;
         apply(mCurrentState);
@@ -512,6 +531,7 @@
      * {@link com.android.systemui.statusbar.NotificationShadeDepthController}.
      * @param backgroundBlurRadius Radius in pixels.
      */
+    @Override
     public void setBackgroundBlurRadius(int backgroundBlurRadius) {
         if (mCurrentState.mBackgroundBlurRadius == backgroundBlurRadius) {
             return;
@@ -520,11 +540,13 @@
         apply(mCurrentState);
     }
 
+    @Override
     public void setHeadsUpShowing(boolean showing) {
         mCurrentState.mHeadsUpShowing = showing;
         apply(mCurrentState);
     }
 
+    @Override
     public void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) {
         mCurrentState.mWallpaperSupportsAmbientMode = supportsAmbientMode;
         apply(mCurrentState);
@@ -543,11 +565,13 @@
      * Used for when a heads-up comes in but we still need to wait for the touchable regions to
      * be computed.
      */
+    @Override
     public void setForceWindowCollapsed(boolean force) {
         mCurrentState.mForceCollapsed = force;
         apply(mCurrentState);
     }
 
+    @Override
     public void setPanelExpanded(boolean isExpanded) {
         mCurrentState.mPanelExpanded = isExpanded;
         apply(mCurrentState);
@@ -563,16 +587,19 @@
      * Set whether the screen brightness is forced to the value we use for doze mode by the status
      * bar window.
      */
+    @Override
     public void setForceDozeBrightness(boolean forceDozeBrightness) {
         mCurrentState.mForceDozeBrightness = forceDozeBrightness;
         apply(mCurrentState);
     }
 
+    @Override
     public void setDozing(boolean dozing) {
         mCurrentState.mDozing = dozing;
         apply(mCurrentState);
     }
 
+    @Override
     public void setForcePluginOpen(boolean forcePluginOpen) {
         mCurrentState.mForcePluginOpen = forcePluginOpen;
         apply(mCurrentState);
@@ -584,10 +611,12 @@
     /**
      * The forcePluginOpen state for the status bar.
      */
+    @Override
     public boolean getForcePluginOpen() {
         return mCurrentState.mForcePluginOpen;
     }
 
+    @Override
     public void setNotTouchable(boolean notTouchable) {
         mCurrentState.mNotTouchable = notTouchable;
         apply(mCurrentState);
@@ -596,24 +625,29 @@
     /**
      * Whether the status bar panel is expanded or not.
      */
+    @Override
     public boolean getPanelExpanded() {
         return mCurrentState.mPanelExpanded;
     }
 
+    @Override
     public void setStateListener(OtherwisedCollapsedListener listener) {
         mListener = listener;
     }
 
+    @Override
     public void setForcePluginOpenListener(ForcePluginOpenListener listener) {
         mForcePluginOpenListener = listener;
     }
 
+    @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println(TAG + ":");
         pw.println("  mKeyguardDisplayMode=" + mKeyguardDisplayMode);
         pw.println(mCurrentState);
     }
 
+    @Override
     public boolean isShowingWallpaper() {
         return !mCurrentState.mBackdropShowing;
     }
@@ -632,6 +666,7 @@
     /**
      * When keyguard will be dismissed but didn't start animation yet.
      */
+    @Override
     public void setKeyguardGoingAway(boolean goingAway) {
         mCurrentState.mKeyguardGoingAway = goingAway;
         apply(mCurrentState);
@@ -642,6 +677,7 @@
      * animation is performed, the component should remove itself from the list of features that
      * are forcing SystemUI to be top-ui.
      */
+    @Override
     public void setRequestTopUi(boolean requestTopUi, String componentTag) {
         if (requestTopUi) {
             mCurrentState.mComponentsForcingTopUi.add(componentTag);
@@ -725,23 +761,4 @@
             setDozing(isDozing);
         }
     };
-
-    /**
-     * Custom listener to pipe data back to plugins about whether or not the status bar would be
-     * collapsed if not for the plugin.
-     * TODO: Find cleaner way to do this.
-     */
-    public interface OtherwisedCollapsedListener {
-        void setWouldOtherwiseCollapse(boolean otherwiseCollapse);
-    }
-
-    /**
-     * Listener to indicate forcePluginOpen has changed
-     */
-    public interface ForcePluginOpenListener {
-        /**
-         * Called when mState.forcePluginOpen is changed
-         */
-        void onChange(boolean forceOpen);
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
index 42222d7..53cc267 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
@@ -44,6 +44,7 @@
 import com.android.systemui.statusbar.DragDownHelper;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.PulseExpansionHandler;
 import com.android.systemui.statusbar.SuperStatusBarViewFactory;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 686b871..11d0583 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -46,6 +46,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.statusbar.BlurUtils;
 import com.android.systemui.statusbar.ScrimView;
@@ -62,13 +63,12 @@
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Controls both the scrim behind the notifications and in front of the notifications (when a
  * security method gets shown).
  */
-@Singleton
+@SysUISingleton
 public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnColorsChangedListener,
         Dumpable {
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
index 3330615..1ce2219 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
@@ -23,20 +23,21 @@
 
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationPresenter;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.StatusBarState;
 
 import java.util.ArrayList;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 
 /** An implementation of {@link com.android.systemui.statusbar.phone.ShadeController}. */
-@Singleton
+@SysUISingleton
 public class ShadeControllerImpl implements ShadeController {
 
     private static final String TAG = "ShadeControllerImpl";
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 6cd33c6..6e37f90 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -138,14 +138,12 @@
 import com.android.systemui.ActivityIntentHelper;
 import com.android.systemui.AutoReinflateContainer;
 import com.android.systemui.DejankUtils;
-import com.android.systemui.DemoMode;
 import com.android.systemui.Dumpable;
 import com.android.systemui.EventLogTags;
 import com.android.systemui.InitController;
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
-import com.android.systemui.SystemUIFactory;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.bubbles.BubbleController;
@@ -153,12 +151,17 @@
 import com.android.systemui.classifier.FalsingLog;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.qualifiers.UiBackground;
+import com.android.systemui.demomode.DemoMode;
+import com.android.systemui.demomode.DemoModeCommandReceiver;
+import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.fragments.ExtensionFragmentListener;
 import com.android.systemui.fragments.FragmentHostManager;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.navigationbar.NavigationBarController;
+import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.FalsingManager;
@@ -182,12 +185,12 @@
 import com.android.systemui.statusbar.GestureRecorder;
 import com.android.systemui.statusbar.KeyboardShortcuts;
 import com.android.systemui.statusbar.KeyguardIndicationController;
-import com.android.systemui.statusbar.NavigationBarController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.NotificationShelfController;
 import com.android.systemui.statusbar.NotificationViewHierarchyManager;
 import com.android.systemui.statusbar.PulseExpansionHandler;
@@ -200,8 +203,8 @@
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
@@ -210,6 +213,7 @@
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
 import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneModule;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -231,6 +235,8 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.concurrent.Executor;
@@ -389,12 +395,13 @@
     private final SuperStatusBarViewFactory mSuperStatusBarViewFactory;
     private final LightsOutNotifController mLightsOutNotifController;
     private final InitController mInitController;
-    private final DarkIconDispatcher mDarkIconDispatcher;
+
     private final PluginDependencyProvider mPluginDependencyProvider;
     private final KeyguardDismissUtil mKeyguardDismissUtil;
     private final ExtensionController mExtensionController;
     private final UserInfoControllerImpl mUserInfoControllerImpl;
     private final DismissCallbackRegistry mDismissCallbackRegistry;
+    private final DemoModeController mDemoModeController;
     private NotificationsController mNotificationsController;
 
     // expanded notifications
@@ -601,7 +608,7 @@
     private UiModeManager mUiModeManager;
     protected boolean mIsKeyguard;
     private LogMaker mStatusBarStateLog;
-    protected NotificationIconAreaController mNotificationIconAreaController;
+    protected final NotificationIconAreaController mNotificationIconAreaController;
     @Nullable private View mAmbientIndicationContainer;
     private final SysuiColorExtractor mColorExtractor;
     private final ScreenLifecycle mScreenLifecycle;
@@ -646,6 +653,7 @@
     private final BubbleController.BubbleExpandListener mBubbleExpandListener;
 
     private ActivityIntentHelper mActivityIntentHelper;
+    private NotificationStackScrollLayoutController mStackScrollerController;
 
     /**
      * Public constructor for StatusBar.
@@ -722,7 +730,6 @@
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             ViewMediatorCallback viewMediatorCallback,
             InitController initController,
-            DarkIconDispatcher darkIconDispatcher,
             @Named(TIME_TICK_HANDLER_NAME) Handler timeTickHandler,
             PluginDependencyProvider pluginDependencyProvider,
             KeyguardDismissUtil keyguardDismissUtil,
@@ -731,8 +738,10 @@
             PhoneStatusBarPolicy phoneStatusBarPolicy,
             KeyguardIndicationController keyguardIndicationController,
             DismissCallbackRegistry dismissCallbackRegistry,
+            DemoModeController demoModeController,
             Lazy<NotificationShadeDepthController> notificationShadeDepthControllerLazy,
-            StatusBarTouchableRegionManager statusBarTouchableRegionManager) {
+            StatusBarTouchableRegionManager statusBarTouchableRegionManager,
+            NotificationIconAreaController notificationIconAreaController) {
         super(context);
         mNotificationsController = notificationsController;
         mLightBarController = lightBarController;
@@ -802,13 +811,14 @@
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
         mKeyguardViewMediatorCallback = viewMediatorCallback;
         mInitController = initController;
-        mDarkIconDispatcher = darkIconDispatcher;
         mPluginDependencyProvider = pluginDependencyProvider;
         mKeyguardDismissUtil = keyguardDismissUtil;
         mExtensionController = extensionController;
         mUserInfoControllerImpl = userInfoControllerImpl;
         mIconPolicy = phoneStatusBarPolicy;
         mDismissCallbackRegistry = dismissCallbackRegistry;
+        mDemoModeController = demoModeController;
+        mNotificationIconAreaController = notificationIconAreaController;
 
         mBubbleExpandListener =
                 (isExpanding, key) -> {
@@ -863,6 +873,9 @@
         // Connect in to the status bar manager service
         mCommandQueue.addCallback(this);
 
+        // Listen for demo mode changes
+        mDemoModeController.addCallback(this);
+
         RegisterStatusBarResult result = null;
         try {
             result = mBarService.registerStatusBar(mCommandQueue);
@@ -939,9 +952,12 @@
         startKeyguard();
 
         mKeyguardUpdateMonitor.registerCallback(mUpdateCallback);
-        mDozeServiceHost.initialize(this, mNotificationIconAreaController,
-                mStatusBarKeyguardViewManager, mNotificationShadeWindowViewController,
-                mNotificationPanelViewController, mAmbientIndicationContainer);
+        mDozeServiceHost.initialize(
+                this,
+                mStatusBarKeyguardViewManager,
+                mNotificationShadeWindowViewController,
+                mNotificationPanelViewController,
+                mAmbientIndicationContainer);
 
         mConfigurationController.addCallback(this);
 
@@ -1016,25 +1032,19 @@
 
         // TODO: Deal with the ugliness that comes from having some of the statusbar broken out
         // into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
-        mStackScroller = mNotificationShadeWindowView.findViewById(
-                R.id.notification_stack_scroller);
-        NotificationListContainer notifListContainer = (NotificationListContainer) mStackScroller;
+        mStackScrollerController =
+                mNotificationPanelViewController.getNotificationStackScrollLayoutController();
+        mStackScroller = mStackScrollerController.getView();
+        NotificationListContainer notifListContainer =
+                mStackScrollerController.getNotificationListContainer();
         mNotificationLogger.setUpWithContainer(notifListContainer);
 
-        // TODO: make this injectable. Currently that would create a circular dependency between
-        // NotificationIconAreaController and StatusBar.
-        mNotificationIconAreaController = SystemUIFactory.getInstance()
-                .createNotificationIconAreaController(context, this,
-                        mWakeUpCoordinator, mKeyguardBypassController,
-                        mStatusBarStateController);
-        mWakeUpCoordinator.setIconAreaController(mNotificationIconAreaController);
+        updateAodIconArea();
         inflateShelf();
         mNotificationIconAreaController.setupShelf(mNotificationShelfController);
-        mNotificationPanelViewController.setOnReinflationListener(
-                mNotificationIconAreaController::initAodIcons);
+        mNotificationPanelViewController.setOnReinflationListener(this::updateAodIconArea);
         mNotificationPanelViewController.addExpansionListener(mWakeUpCoordinator);
 
-        mDarkIconDispatcher.addDarkReceiver(mNotificationIconAreaController);
         // Allow plugins to reference DarkIconDispatcher and StatusBarStateController
         mPluginDependencyProvider.allowPluginDependency(DarkIconDispatcher.class);
         mPluginDependencyProvider.allowPluginDependency(StatusBarStateController.class);
@@ -1147,9 +1157,11 @@
         });
         mScrimController.attachViews(scrimBehind, scrimInFront, scrimForBubble);
 
-        mNotificationPanelViewController.initDependencies(this, mGroupManager,
+        mNotificationPanelViewController.initDependencies(
+                this,
+                mGroupManager,
                 mNotificationShelfController,
-                mNotificationIconAreaController, mScrimController);
+                mScrimController);
 
         BackDropView backdrop = mNotificationShadeWindowView.findViewById(R.id.backdrop);
         mMediaManager.setup(backdrop, backdrop.findViewById(R.id.backdrop_front),
@@ -1249,7 +1261,6 @@
         if (DEBUG_MEDIA_FAKE_ARTWORK) {
             demoFilter.addAction(ACTION_FAKE_ARTWORK);
         }
-        demoFilter.addAction(ACTION_DEMO);
         context.registerReceiverAsUser(mDemoReceiver, UserHandle.ALL, demoFilter,
                 android.Manifest.permission.DUMP, null);
 
@@ -1264,6 +1275,12 @@
         ThreadedRenderer.overrideProperty("ambientRatio", String.valueOf(1.5f));
     }
 
+    private void updateAodIconArea() {
+        mNotificationIconAreaController.setupAodIcons(
+                getNotificationShadeWindowView()
+                        .findViewById(R.id.clock_notification_icon_container));
+    }
+
     @NonNull
     @Override
     public Lifecycle getLifecycle() {
@@ -1301,13 +1318,15 @@
         mActivityLaunchAnimator = new ActivityLaunchAnimator(
                 mNotificationShadeWindowViewController, this, mNotificationPanelViewController,
                 mNotificationShadeDepthControllerLazy.get(),
-                (NotificationListContainer) mStackScroller, mContext.getMainExecutor());
+                mStackScrollerController.getNotificationListContainer(),
+                mContext.getMainExecutor());
 
         // TODO: inject this.
         mPresenter = new StatusBarNotificationPresenter(mContext, mNotificationPanelViewController,
-                mHeadsUpManager, mNotificationShadeWindowView, mStackScroller, mDozeScrimController,
-                mScrimController, mActivityLaunchAnimator, mDynamicPrivacyController,
-                mKeyguardStateController, mKeyguardIndicationController,
+                mHeadsUpManager, mNotificationShadeWindowView, mStackScrollerController,
+                mDozeScrimController, mScrimController, mActivityLaunchAnimator,
+                mDynamicPrivacyController, mKeyguardStateController,
+                mKeyguardIndicationController,
                 this /* statusBar */, mShadeController, mCommandQueue, mInitController,
                 mNotificationInterruptStateProvider);
 
@@ -1321,16 +1340,13 @@
                         .setNotificationPresenter(mPresenter)
                         .setNotificationPanelViewController(mNotificationPanelViewController)
                         .build();
-
-        ((NotificationListContainer) mStackScroller)
-                .setNotificationActivityStarter(mNotificationActivityStarter);
-
+        mStackScroller.setNotificationActivityStarter(mNotificationActivityStarter);
         mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
 
         mNotificationsController.initialize(
                 this,
                 mPresenter,
-                (NotificationListContainer) mStackScroller,
+                mStackScrollerController.getNotificationListContainer(),
                 mNotificationActivityStarter,
                 mPresenter);
     }
@@ -1460,6 +1476,34 @@
     protected void startKeyguard() {
         Trace.beginSection("StatusBar#startKeyguard");
         mBiometricUnlockController = mBiometricUnlockControllerLazy.get();
+        mBiometricUnlockController.setBiometricModeListener(
+                new BiometricUnlockController.BiometricModeListener() {
+                    @Override
+                    public void onResetMode() {
+                        setWakeAndUnlocking(false);
+                    }
+
+                    @Override
+                    public void onModeChanged(int mode) {
+                        switch (mode) {
+                            case BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM:
+                            case BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING:
+                            case BiometricUnlockController.MODE_WAKE_AND_UNLOCK:
+                                setWakeAndUnlocking(true);
+                        }
+                    }
+
+                    @Override
+                    public void notifyBiometricAuthModeChanged() {
+                        StatusBar.this.notifyBiometricAuthModeChanged();
+                    }
+
+                    private void setWakeAndUnlocking(boolean wakeAndUnlocking) {
+                        if (getNavigationBarView() != null) {
+                            getNavigationBarView().setWakeAndUnlocking(wakeAndUnlocking);
+                        }
+                    }
+                });
         mStatusBarKeyguardViewManager.registerStatusBar(
                 /* statusBar= */ this, getBouncerContainer(),
                 mNotificationPanelViewController, mBiometricUnlockController,
@@ -1502,7 +1546,7 @@
         return mStatusBarWindowController.getStatusBarHeight();
     }
 
-    protected boolean toggleSplitScreenMode(int metricsDockAction, int metricsUndockAction) {
+    public boolean toggleSplitScreenMode(int metricsDockAction, int metricsUndockAction) {
         if (!mRecentsOptional.isPresent()) {
             return false;
         }
@@ -1805,7 +1849,7 @@
         mPanelExpanded = isExpanded;
         updateHideIconsForBouncer(false /* animate */);
         mNotificationShadeWindowController.setPanelExpanded(isExpanded);
-        mVisualStabilityManager.setPanelExpanded(isExpanded);
+        mStatusBarStateController.setPanelExpanded(isExpanded);
         if (isExpanded && mStatusBarStateController.getState() != StatusBarState.KEYGUARD) {
             if (DEBUG) {
                 Log.v(TAG, "clearing notification effects from setExpandedHeight");
@@ -2026,7 +2070,7 @@
                     mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
                 }
                 mNotificationPanelViewController.expand(true /* animate */);
-                ((NotificationListContainer) mStackScroller).setWillExpand(true);
+                mStackScroller.setWillExpand(true);
                 mHeadsUpManager.unpinAll(true /* userUnpinned */);
                 mMetricsLogger.count(NotificationPanelView.COUNTER_PANEL_OPEN, 1);
             } else if (!mNotificationPanelViewController.isInSettings()
@@ -2435,8 +2479,8 @@
         return mNotificationShadeWindowViewController.getBarTransitions();
     }
 
-    void checkBarModes() {
-        if (mDemoMode) return;
+    public void checkBarModes() {
+        if (mDemoModeController.isInDemoMode()) return;
         if (mNotificationShadeWindowViewController != null && getStatusBarTransitions() != null) {
             checkBarMode(mStatusBarMode, mStatusBarWindowState, getStatusBarTransitions());
         }
@@ -2445,7 +2489,7 @@
     }
 
     // Called by NavigationBarFragment
-    void setQsScrimEnabled(boolean scrimEnabled) {
+    public void setQsScrimEnabled(boolean scrimEnabled) {
         mNotificationPanelViewController.setQsScrimEnabled(scrimEnabled);
     }
 
@@ -2618,7 +2662,7 @@
         }
     }
 
-    static void dumpBarTransitions(PrintWriter pw, String var, BarTransitions transitions) {
+    public static void dumpBarTransitions(PrintWriter pw, String var, BarTransitions transitions) {
         pw.print("  "); pw.print(var); pw.print(".BarTransitions.mMode=");
         pw.println(BarTransitions.modeToString(transitions.getMode()));
     }
@@ -2809,19 +2853,7 @@
         public void onReceive(Context context, Intent intent) {
             if (DEBUG) Log.v(TAG, "onReceive: " + intent);
             String action = intent.getAction();
-            if (ACTION_DEMO.equals(action)) {
-                Bundle bundle = intent.getExtras();
-                if (bundle != null) {
-                    String command = bundle.getString("command", "").trim().toLowerCase();
-                    if (command.length() > 0) {
-                        try {
-                            dispatchDemoCommand(command, bundle);
-                        } catch (Throwable t) {
-                            Log.w(TAG, "Error running demo command, intent=" + intent, t);
-                        }
-                    }
-                }
-            } else if (ACTION_FAKE_ARTWORK.equals(action)) {
+            if (ACTION_FAKE_ARTWORK.equals(action)) {
                 if (DEBUG_MEDIA_FAKE_ARTWORK) {
                     mPresenter.updateMediaMetaData(true, true);
                 }
@@ -3085,50 +3117,34 @@
         startActivityDismissingKeyguard(intent, onlyProvisioned, true /* dismissShade */);
     }
 
-    private boolean mDemoModeAllowed;
-    private boolean mDemoMode;
+    @Override
+    public List<String> demoCommands() {
+        List<String> s = new ArrayList<>();
+        s.add(DemoMode.COMMAND_BARS);
+        s.add(DemoMode.COMMAND_CLOCK);
+        s.add(DemoMode.COMMAND_OPERATOR);
+        return s;
+    }
 
     @Override
-    public void dispatchDemoCommand(String command, Bundle args) {
-        if (!mDemoModeAllowed) {
-            mDemoModeAllowed = Settings.Global.getInt(mContext.getContentResolver(),
-                    DEMO_MODE_ALLOWED, 0) != 0;
-        }
-        if (!mDemoModeAllowed) return;
-        if (command.equals(COMMAND_ENTER)) {
-            mDemoMode = true;
-        } else if (command.equals(COMMAND_EXIT)) {
-            mDemoMode = false;
-            checkBarModes();
-        } else if (!mDemoMode) {
-            // automatically enter demo mode on first demo command
-            dispatchDemoCommand(COMMAND_ENTER, new Bundle());
-        }
-        boolean modeChange = command.equals(COMMAND_ENTER) || command.equals(COMMAND_EXIT);
-        if ((modeChange || command.equals(COMMAND_VOLUME)) && mVolumeComponent != null) {
-            mVolumeComponent.dispatchDemoCommand(command, args);
-        }
-        if (modeChange || command.equals(COMMAND_CLOCK)) {
+    public void onDemoModeStarted() {
+        // Must send this message to any view that we delegate to via dispatchDemoCommandToView
+        dispatchDemoModeStartedToView(R.id.clock);
+        dispatchDemoModeStartedToView(R.id.operator_name);
+    }
+
+    @Override
+    public void onDemoModeFinished() {
+        dispatchDemoModeFinishedToView(R.id.clock);
+        dispatchDemoModeFinishedToView(R.id.operator_name);
+        checkBarModes();
+    }
+
+    @Override
+    public void dispatchDemoCommand(String command, @NonNull Bundle args) {
+        if (command.equals(COMMAND_CLOCK)) {
             dispatchDemoCommandToView(command, args, R.id.clock);
         }
-        if (modeChange || command.equals(COMMAND_BATTERY)) {
-            mBatteryController.dispatchDemoCommand(command, args);
-        }
-        if (modeChange || command.equals(COMMAND_STATUS)) {
-            ((StatusBarIconControllerImpl) mIconController).dispatchDemoCommand(command, args);
-        }
-        if (mNetworkController != null && (modeChange || command.equals(COMMAND_NETWORK))) {
-            mNetworkController.dispatchDemoCommand(command, args);
-        }
-        if (modeChange || command.equals(COMMAND_NOTIFICATIONS)) {
-            View notifications = mStatusBarView == null ? null
-                    : mStatusBarView.findViewById(R.id.notification_icon_area);
-            if (notifications != null) {
-                String visible = args.getString("visible");
-                int vis = mDemoMode && "false".equals(visible) ? View.INVISIBLE : View.VISIBLE;
-                notifications.setVisibility(vis);
-            }
-        }
         if (command.equals(COMMAND_BARS)) {
             String mode = args.getString("mode");
             int barMode = "opaque".equals(mode) ? MODE_OPAQUE :
@@ -3147,16 +3163,33 @@
                 mNavigationBarController.transitionTo(mDisplayId, barMode, animate);
             }
         }
-        if (modeChange || command.equals(COMMAND_OPERATOR)) {
+        if (command.equals(COMMAND_OPERATOR)) {
             dispatchDemoCommandToView(command, args, R.id.operator_name);
         }
     }
 
+    //TODO: these should have controllers, and this method should be removed
     private void dispatchDemoCommandToView(String command, Bundle args, int id) {
         if (mStatusBarView == null) return;
         View v = mStatusBarView.findViewById(id);
-        if (v instanceof DemoMode) {
-            ((DemoMode)v).dispatchDemoCommand(command, args);
+        if (v instanceof DemoModeCommandReceiver) {
+            ((DemoModeCommandReceiver) v).dispatchDemoCommand(command, args);
+        }
+    }
+
+    private void dispatchDemoModeStartedToView(int id) {
+        if (mStatusBarView == null) return;
+        View v = mStatusBarView.findViewById(id);
+        if (v instanceof DemoModeCommandReceiver) {
+            ((DemoModeCommandReceiver) v).onDemoModeStarted();
+        }
+    }
+
+    private void dispatchDemoModeFinishedToView(int id) {
+        if (mStatusBarView == null) return;
+        View v = mStatusBarView.findViewById(id);
+        if (v instanceof DemoModeCommandReceiver) {
+            ((DemoModeCommandReceiver) v).onDemoModeFinished();
         }
     }
 
@@ -3754,7 +3787,6 @@
             mDeviceInteractive = false;
             mWakeUpComingFromTouch = false;
             mWakeUpTouchLocation = null;
-            mVisualStabilityManager.setScreenOn(false);
             updateVisibleToUser();
 
             updateNotificationPanelTouchState();
@@ -3791,7 +3823,6 @@
             if (!mKeyguardBypassController.getBypassEnabled()) {
                 mHeadsUpManager.releaseAllImmediately();
             }
-            mVisualStabilityManager.setScreenOn(true);
             updateVisibleToUser();
             updateIsKeyguard();
             mDozeServiceHost.stopDozing();
@@ -4132,7 +4163,7 @@
         toggleSplitScreenMode(-1 /* metricsDockAction */, -1 /* metricsUndockAction */);
     }
 
-    void awakenDreams() {
+    public void awakenDreams() {
         mUiBgExecutor.execute(() -> {
             try {
                 mDreamManager.awaken();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index 8ff7a41..f0efed3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -32,9 +32,9 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.statusbar.StatusBarIcon;
-import com.android.systemui.DemoMode;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
+import com.android.systemui.demomode.DemoModeCommandReceiver;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
 import com.android.systemui.statusbar.CommandQueue;
@@ -198,7 +198,7 @@
     /**
      * Turns info from StatusBarIconController into ImageViews in a ViewGroup.
      */
-    public static class IconManager implements DemoMode {
+    class IconManager implements DemoModeCommandReceiver {
         protected final ViewGroup mGroup;
         protected final Context mContext;
         protected final int mIconSize;
@@ -390,18 +390,24 @@
                 return;
             }
 
-            if (command.equals(COMMAND_EXIT)) {
-                if (mDemoStatusIcons != null) {
-                    mDemoStatusIcons.dispatchDemoCommand(command, args);
-                    exitDemoMode();
-                }
+            mDemoStatusIcons.dispatchDemoCommand(command, args);
+        }
+
+        @Override
+        public void onDemoModeStarted() {
+            mIsInDemoMode = true;
+            if (mDemoStatusIcons == null) {
+                mDemoStatusIcons = createDemoStatusIcons();
+            }
+            mDemoStatusIcons.onDemoModeStarted();
+        }
+
+        @Override
+        public void onDemoModeFinished() {
+            if (mDemoStatusIcons != null) {
+                mDemoStatusIcons.onDemoModeFinished();
+                exitDemoMode();
                 mIsInDemoMode = false;
-            } else {
-                if (mDemoStatusIcons == null) {
-                    mIsInDemoMode = true;
-                    mDemoStatusIcons = createDemoStatusIcons();
-                }
-                mDemoStatusIcons.dispatchDemoCommand(command, args);
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 21e1d31..2870152 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -29,6 +29,9 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.demomode.DemoMode;
+import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.StatusIconDisplayable;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
@@ -45,31 +48,28 @@
 import java.util.List;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Receives the callbacks from CommandQueue related to icons and tracks the state of
  * all the icons. Dispatches this state to any IconManagers that are currently
  * registered with it.
  */
-@Singleton
+@SysUISingleton
 public class StatusBarIconControllerImpl extends StatusBarIconList implements Tunable,
-        ConfigurationListener, Dumpable, CommandQueue.Callbacks, StatusBarIconController {
+        ConfigurationListener, Dumpable, CommandQueue.Callbacks, StatusBarIconController, DemoMode {
 
     private static final String TAG = "StatusBarIconController";
 
     private final ArrayList<IconManager> mIconGroups = new ArrayList<>();
     private final ArraySet<String> mIconHideList = new ArraySet<>();
 
-    // Points to light or dark context depending on the... context?
     private Context mContext;
-    private Context mLightContext;
-    private Context mDarkContext;
-
-    private boolean mIsDark = false;
 
     @Inject
-    public StatusBarIconControllerImpl(Context context, CommandQueue commandQueue) {
+    public StatusBarIconControllerImpl(
+            Context context,
+            CommandQueue commandQueue,
+            DemoModeController demoModeController) {
         super(context.getResources().getStringArray(
                 com.android.internal.R.array.config_statusBarIcons));
         Dependency.get(ConfigurationController.class).addCallback(this);
@@ -80,6 +80,7 @@
 
         commandQueue.addCallback(this);
         Dependency.get(TunerService.class).addTunable(this, ICON_HIDE_LIST);
+        demoModeController.addCallback(this);
     }
 
     @Override
@@ -339,6 +340,25 @@
         super.dump(pw);
     }
 
+    @Override
+    public void onDemoModeStarted() {
+        for (IconManager manager : mIconGroups) {
+            if (manager.isDemoable()) {
+                manager.onDemoModeStarted();
+            }
+        }
+    }
+
+    @Override
+    public void onDemoModeFinished() {
+        for (IconManager manager : mIconGroups) {
+            if (manager.isDemoable()) {
+                manager.onDemoModeFinished();
+            }
+        }
+    }
+
+    @Override
     public void dispatchDemoCommand(String command, Bundle args) {
         for (IconManager manager : mIconGroups) {
             if (manager.isDemoable()) {
@@ -348,6 +368,13 @@
     }
 
     @Override
+    public List<String> demoCommands() {
+        List<String> s = new ArrayList<>();
+        s.add(DemoMode.COMMAND_STATUS);
+        return s;
+    }
+
+    @Override
     public void onDensityOrFontScaleChanged() {
         loadDimens();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 81d0699..7ee501c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -47,14 +47,17 @@
 import com.android.settingslib.animation.AppearAnimationUtils;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.SystemUIFactory;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.statusbar.CrossFadeHelper;
 import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -67,7 +70,6 @@
 import java.util.ArrayList;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Manages creating, showing, hiding and resetting the keyguard within the status bar. Calls back
@@ -75,7 +77,7 @@
  * which is in turn, reported to this class by the current
  * {@link com.android.keyguard.KeyguardViewBase}.
  */
-@Singleton
+@SysUISingleton
 public class StatusBarKeyguardViewManager implements RemoteInputController.Callback,
         StatusBarStateController.StateListener, ConfigurationController.ConfigurationListener,
         PanelExpansionListener, NavigationModeController.ModeChangedListener,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 4de6484..de11c90 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -49,7 +49,7 @@
 import com.android.systemui.EventLogTags;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.bubbles.BubbleController;
-import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.plugins.ActivityStarter;
@@ -73,14 +73,13 @@
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.OnDismissCallback;
+import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
 import com.android.systemui.statusbar.policy.HeadsUpUtil;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 
@@ -93,7 +92,6 @@
 
     private final CommandQueue mCommandQueue;
     private final Handler mMainThreadHandler;
-    private final Handler mBackgroundHandler;
     private final Executor mUiBgExecutor;
 
     private final NotificationEntryManager mEntryManager;
@@ -126,7 +124,7 @@
     private final NotificationPresenter mPresenter;
     private final NotificationPanelViewController mNotificationPanel;
     private final ActivityLaunchAnimator mActivityLaunchAnimator;
-    private final OnDismissCallback mOnDismissCallback;
+    private final OnUserInteractionCallback mOnUserInteractionCallback;
 
     private boolean mIsCollapsingToShowActivityOverLockscreen;
 
@@ -134,7 +132,6 @@
             Context context,
             CommandQueue commandQueue,
             Handler mainThreadHandler,
-            Handler backgroundHandler,
             Executor uiBgExecutor,
             NotificationEntryManager entryManager,
             NotifPipeline notifPipeline,
@@ -161,7 +158,7 @@
             FeatureFlags featureFlags,
             MetricsLogger metricsLogger,
             StatusBarNotificationActivityStarterLogger logger,
-            OnDismissCallback onDismissCallback,
+            OnUserInteractionCallback onUserInteractionCallback,
 
             StatusBar statusBar,
             NotificationPresenter presenter,
@@ -170,7 +167,6 @@
         mContext = context;
         mCommandQueue = commandQueue;
         mMainThreadHandler = mainThreadHandler;
-        mBackgroundHandler = backgroundHandler;
         mUiBgExecutor = uiBgExecutor;
         mEntryManager = entryManager;
         mNotifPipeline = notifPipeline;
@@ -197,7 +193,7 @@
         mFeatureFlags = featureFlags;
         mMetricsLogger = metricsLogger;
         mLogger = logger;
-        mOnDismissCallback = onDismissCallback;
+        mOnUserInteractionCallback = onUserInteractionCallback;
 
         // TODO: use KeyguardStateController#isOccluded to remove this dependency
         mStatusBar = statusBar;
@@ -307,7 +303,7 @@
             mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable);
             mShadeController.collapsePanel();
         } else {
-            mBackgroundHandler.postAtFrontOfQueue(runnable);
+            runnable.run();
         }
         return !mNotificationPanel.isFullyCollapsed();
     }
@@ -579,10 +575,10 @@
                 // To avoid lags we're only performing the remove
                 // after the shade was collapsed
                 mShadeController.addPostCollapseAction(
-                        () -> mOnDismissCallback.onDismiss(entry, REASON_CLICK)
+                        () -> mOnUserInteractionCallback.onDismiss(entry, REASON_CLICK)
                 );
             } else {
-                mOnDismissCallback.onDismiss(entry, REASON_CLICK);
+                mOnUserInteractionCallback.onDismiss(entry, REASON_CLICK);
             }
         });
     }
@@ -600,12 +596,12 @@
     /**
      * Public builder for {@link StatusBarNotificationActivityStarter}.
      */
-    @Singleton
+    @SysUISingleton
     public static class Builder {
         private final Context mContext;
         private final CommandQueue mCommandQueue;
         private final Handler mMainThreadHandler;
-        private final Handler mBackgroundHandler;
+
         private final Executor mUiBgExecutor;
         private final NotificationEntryManager mEntryManager;
         private final NotifPipeline mNotifPipeline;
@@ -632,7 +628,7 @@
         private final FeatureFlags mFeatureFlags;
         private final MetricsLogger mMetricsLogger;
         private final StatusBarNotificationActivityStarterLogger mLogger;
-        private final OnDismissCallback mOnDismissCallback;
+        private final OnUserInteractionCallback mOnUserInteractionCallback;
 
         private StatusBar mStatusBar;
         private NotificationPresenter mNotificationPresenter;
@@ -644,7 +640,6 @@
                 Context context,
                 CommandQueue commandQueue,
                 @Main Handler mainThreadHandler,
-                @Background Handler backgroundHandler,
                 @UiBackground Executor uiBgExecutor,
                 NotificationEntryManager entryManager,
                 NotifPipeline notifPipeline,
@@ -671,12 +666,11 @@
                 FeatureFlags featureFlags,
                 MetricsLogger metricsLogger,
                 StatusBarNotificationActivityStarterLogger logger,
-                OnDismissCallback onDismissCallback) {
+                OnUserInteractionCallback onUserInteractionCallback) {
 
             mContext = context;
             mCommandQueue = commandQueue;
             mMainThreadHandler = mainThreadHandler;
-            mBackgroundHandler = backgroundHandler;
             mUiBgExecutor = uiBgExecutor;
             mEntryManager = entryManager;
             mNotifPipeline = notifPipeline;
@@ -703,7 +697,7 @@
             mFeatureFlags = featureFlags;
             mMetricsLogger = metricsLogger;
             mLogger = logger;
-            mOnDismissCallback = onDismissCallback;
+            mOnUserInteractionCallback = onUserInteractionCallback;
         }
 
         /** Sets the status bar to use as {@link StatusBar}. */
@@ -734,7 +728,6 @@
                     mContext,
                     mCommandQueue,
                     mMainThreadHandler,
-                    mBackgroundHandler,
                     mUiBgExecutor,
                     mEntryManager,
                     mNotifPipeline,
@@ -761,7 +754,7 @@
                     mFeatureFlags,
                     mMetricsLogger,
                     mLogger,
-                    mOnDismissCallback,
+                    mOnUserInteractionCallback,
 
                     mStatusBar,
                     mNotificationPresenter,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 45f0c49..67adaaa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -30,7 +30,6 @@
 import android.util.Log;
 import android.util.Slog;
 import android.view.View;
-import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityManager;
 import android.widget.TextView;
 
@@ -53,6 +52,7 @@
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.NotificationViewHierarchyManager;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -61,9 +61,9 @@
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
+import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
@@ -71,7 +71,7 @@
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
 import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener;
-import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -133,7 +133,7 @@
             NotificationPanelViewController panel,
             HeadsUpManagerPhone headsUp,
             NotificationShadeWindowView statusBarWindow,
-            ViewGroup stackScroller,
+            NotificationStackScrollLayoutController stackScrollerController,
             DozeScrimController dozeScrimController,
             ScrimController scrimController,
             ActivityLaunchAnimator activityLaunchAnimator,
@@ -155,7 +155,7 @@
         mStatusBar = statusBar;
         mShadeController = shadeController;
         mCommandQueue = commandQueue;
-        mAboveShelfObserver = new AboveShelfObserver(stackScroller);
+        mAboveShelfObserver = new AboveShelfObserver(stackScrollerController.getView());
         mActivityLaunchAnimator = activityLaunchAnimator;
         mAboveShelfObserver.setListener(statusBarWindow.findViewById(
                 R.id.notification_container_parent));
@@ -190,7 +190,6 @@
         remoteInputManager.getController().addCallback(
                 Dependency.get(NotificationShadeWindowController.class));
 
-        NotificationListContainer notifListContainer = (NotificationListContainer) stackScroller;
         initController.addPostInitTask(() -> {
             NotificationEntryListener notificationEntryListener = new NotificationEntryListener() {
                 @Override
@@ -207,7 +206,8 @@
                 }
             };
 
-            mViewHierarchyManager.setUpWithPresenter(this, notifListContainer);
+            mViewHierarchyManager.setUpWithPresenter(this,
+                    stackScrollerController.getNotificationListContainer());
             mEntryManager.setUpWithPresenter(this);
             mEntryManager.addNotificationEntryListener(notificationEntryListener);
             mEntryManager.addNotificationLifetimeExtender(mHeadsUpManager);
@@ -217,9 +217,9 @@
             notificationInterruptStateProvider.addSuppressor(mInterruptSuppressor);
             mLockscreenUserManager.setUpWithPresenter(this);
             mMediaManager.setUpWithPresenter(this);
-            mVisualStabilityManager.setUpWithPresenter(this);
             mGutsManager.setUpWithPresenter(this,
-                    notifListContainer, mCheckSaveListener, mOnSettingsClickListener);
+                    stackScrollerController.getNotificationListContainer(), mCheckSaveListener,
+                    mOnSettingsClickListener);
             // ForegroundServiceNotificationListener adds its listener in its constructor
             // but we need to request it here in order for it to be instantiated.
             // TODO: figure out how to do this correctly once Dependency.get() is gone.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
index ac69d9c..8a89429 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
@@ -34,6 +34,7 @@
 import android.view.ViewParent;
 
 import com.android.systemui.ActivityIntentHelper;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.ActionClickLogger;
@@ -49,11 +50,10 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  */
-@Singleton
+@SysUISingleton
 public class StatusBarRemoteInputCallback implements Callback, Callbacks,
         StatusBarStateController.StateListener {
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
index 9c7f490..b859250 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
@@ -32,6 +32,8 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.ScreenDecorations;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
@@ -40,14 +42,13 @@
 import java.io.PrintWriter;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Manages what parts of the status bar are touchable. Clients are primarily UI that display in the
  * status bar even though the UI doesn't look like part of the status bar. Currently this consists
  * of HeadsUpNotifications.
  */
-@Singleton
+@SysUISingleton
 public final class StatusBarTouchableRegionManager implements Dumpable {
     private static final String TAG = "TouchableRegionManager";
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
index d4e1aa4..2f7278b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
@@ -29,16 +29,16 @@
 import android.view.ViewGroup;
 import android.view.WindowManager;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.statusbar.SuperStatusBarViewFactory;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Encapsulates all logic for the status bar window state management.
  */
-@Singleton
+@SysUISingleton
 public class StatusBarWindowController {
     private static final String TAG = "StatusBarWindowController";
     private static final boolean DEBUG = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneDependenciesModule.java
index 69c6814..79d72b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneDependenciesModule.java
@@ -16,12 +16,11 @@
 
 package com.android.systemui.statusbar.phone.dagger;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.notification.row.RowContentBindStage;
 import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper;
 import com.android.systemui.statusbar.phone.StatusBar;
 
-import javax.inject.Singleton;
-
 import dagger.Module;
 import dagger.Provides;
 
@@ -34,7 +33,7 @@
 public interface StatusBarPhoneDependenciesModule {
 
     /** */
-    @Singleton
+    @SysUISingleton
     @Provides
     static NotificationGroupAlertTransferHelper provideNotificationGroupAlertTransferHelper(
             RowContentBindStage bindStage) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index 02e0312..2768b82 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -33,12 +33,14 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.UiBackground;
+import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
-import com.android.systemui.plugins.DarkIconDispatcher;
+import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.PluginDependencyProvider;
 import com.android.systemui.recents.Recents;
@@ -47,11 +49,11 @@
 import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.KeyguardIndicationController;
-import com.android.systemui.statusbar.NavigationBarController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.NotificationViewHierarchyManager;
 import com.android.systemui.statusbar.PulseExpansionHandler;
 import com.android.systemui.statusbar.SuperStatusBarViewFactory;
@@ -59,7 +61,7 @@
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
@@ -79,7 +81,7 @@
 import com.android.systemui.statusbar.phone.LockscreenLockIconController;
 import com.android.systemui.statusbar.phone.LockscreenWallpaper;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.NotificationIconAreaController;
 import com.android.systemui.statusbar.phone.PhoneStatusBarPolicy;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.ShadeController;
@@ -104,7 +106,6 @@
 
 import javax.inject.Named;
 import javax.inject.Provider;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 import dagger.Module;
@@ -119,7 +120,7 @@
      * Provides our instance of StatusBar which is considered optional.
      */
     @Provides
-    @Singleton
+    @SysUISingleton
     static StatusBar provideStatusBar(
             Context context,
             NotificationsController notificationsController,
@@ -188,7 +189,6 @@
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             ViewMediatorCallback viewMediatorCallback,
             InitController initController,
-            DarkIconDispatcher darkIconDispatcher,
             @Named(TIME_TICK_HANDLER_NAME) Handler timeTickHandler,
             PluginDependencyProvider pluginDependencyProvider,
             KeyguardDismissUtil keyguardDismissUtil,
@@ -196,9 +196,11 @@
             UserInfoControllerImpl userInfoControllerImpl,
             PhoneStatusBarPolicy phoneStatusBarPolicy,
             KeyguardIndicationController keyguardIndicationController,
+            DemoModeController demoModeController,
             Lazy<NotificationShadeDepthController> notificationShadeDepthController,
             DismissCallbackRegistry dismissCallbackRegistry,
-            StatusBarTouchableRegionManager statusBarTouchableRegionManager) {
+            StatusBarTouchableRegionManager statusBarTouchableRegionManager,
+            NotificationIconAreaController notificationIconAreaController) {
         return new StatusBar(
                 context,
                 notificationsController,
@@ -266,7 +268,6 @@
                 statusBarKeyguardViewManager,
                 viewMediatorCallback,
                 initController,
-                darkIconDispatcher,
                 timeTickHandler,
                 pluginDependencyProvider,
                 keyguardDismissUtil,
@@ -275,7 +276,9 @@
                 phoneStatusBarPolicy,
                 keyguardIndicationController,
                 dismissCallbackRegistry,
+                demoModeController,
                 notificationShadeDepthController,
-                statusBarTouchableRegionManager);
+                statusBarTouchableRegionManager,
+                notificationIconAreaController);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityController.java
index ebfdb3f..ad49c79 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityController.java
@@ -19,16 +19,17 @@
 import android.content.Context;
 import android.view.accessibility.AccessibilityManager;
 
+import com.android.systemui.dagger.SysUISingleton;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  */
-@Singleton
+@SysUISingleton
 public class AccessibilityController implements
         AccessibilityManager.AccessibilityStateChangeListener,
         AccessibilityManager.TouchExplorationStateChangeListener {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java
index 1395e13..d38284a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java
@@ -19,13 +19,16 @@
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener;
 
+import androidx.annotation.NonNull;
+
+import com.android.systemui.dagger.SysUISingleton;
+
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * For mocking because AccessibilityManager is final for some reason...
  */
-@Singleton
+@SysUISingleton
 public class AccessibilityManagerWrapper implements
         CallbackController<AccessibilityServicesStateChangeListener> {
 
@@ -37,12 +40,12 @@
     }
 
     @Override
-    public void addCallback(AccessibilityServicesStateChangeListener listener) {
+    public void addCallback(@NonNull AccessibilityServicesStateChangeListener listener) {
         mAccessibilityManager.addAccessibilityServicesStateChangeListener(listener, null);
     }
 
     @Override
-    public void removeCallback(AccessibilityServicesStateChangeListener listener) {
+    public void removeCallback(@NonNull AccessibilityServicesStateChangeListener listener) {
         mAccessibilityManager.removeAccessibilityServicesStateChangeListener(listener);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index e5a4679..06e4731 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -18,8 +18,8 @@
 
 import android.annotation.Nullable;
 
-import com.android.systemui.DemoMode;
 import com.android.systemui.Dumpable;
+import com.android.systemui.demomode.DemoMode;
 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
 
 import java.io.FileDescriptor;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index 88a6263..57ac85e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -27,6 +27,7 @@
 import android.os.PowerSaveState;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -34,22 +35,25 @@
 import com.android.settingslib.fuelgauge.Estimate;
 import com.android.settingslib.utils.PowerUtil;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.demomode.DemoMode;
+import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.power.EnhancedEstimates;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.List;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Default implementation of a {@link BatteryController}. This controller monitors for battery
  * level change events that are broadcasted by the system.
  */
-@Singleton
+@SysUISingleton
 public class BatteryControllerImpl extends BroadcastReceiver implements BatteryController {
     private static final String TAG = "BatteryController";
 
@@ -63,6 +67,7 @@
             mChangeCallbacks = new ArrayList<>();
     private final ArrayList<EstimateFetchCompletion> mFetchCallbacks = new ArrayList<>();
     private final PowerManager mPowerManager;
+    private final DemoModeController mDemoModeController;
     private final Handler mMainHandler;
     private final Handler mBgHandler;
     protected final Context mContext;
@@ -82,15 +87,21 @@
 
     @VisibleForTesting
     @Inject
-    public BatteryControllerImpl(Context context, EnhancedEstimates enhancedEstimates,
-            PowerManager powerManager, BroadcastDispatcher broadcastDispatcher,
-            @Main Handler mainHandler, @Background Handler bgHandler) {
+    public BatteryControllerImpl(
+            Context context,
+            EnhancedEstimates enhancedEstimates,
+            PowerManager powerManager,
+            BroadcastDispatcher broadcastDispatcher,
+            DemoModeController demoModeController,
+            @Main Handler mainHandler,
+            @Background Handler bgHandler) {
         mContext = context;
         mMainHandler = mainHandler;
         mBgHandler = bgHandler;
         mPowerManager = powerManager;
         mEstimates = enhancedEstimates;
         mBroadcastDispatcher = broadcastDispatcher;
+        mDemoModeController = demoModeController;
     }
 
     private void registerReceiver() {
@@ -114,6 +125,7 @@
                 onReceive(mContext, intent);
             }
         }
+        mDemoModeController.addCallback(this);
         updatePowerSave();
         updateEstimate();
     }
@@ -134,7 +146,7 @@
     }
 
     @Override
-    public void addCallback(BatteryController.BatteryStateChangeCallback cb) {
+    public void addCallback(@NonNull BatteryController.BatteryStateChangeCallback cb) {
         synchronized (mChangeCallbacks) {
             mChangeCallbacks.add(cb);
         }
@@ -144,7 +156,7 @@
     }
 
     @Override
-    public void removeCallback(BatteryController.BatteryStateChangeCallback cb) {
+    public void removeCallback(@NonNull BatteryController.BatteryStateChangeCallback cb) {
         synchronized (mChangeCallbacks) {
             mChangeCallbacks.remove(cb);
         }
@@ -325,32 +337,43 @@
         }
     }
 
-    private boolean mDemoMode;
-
     @Override
     public void dispatchDemoCommand(String command, Bundle args) {
-        if (!mDemoMode && command.equals(COMMAND_ENTER)) {
-            mDemoMode = true;
-            mBroadcastDispatcher.unregisterReceiver(this);
-        } else if (mDemoMode && command.equals(COMMAND_EXIT)) {
-            mDemoMode = false;
-            registerReceiver();
-            updatePowerSave();
-        } else if (mDemoMode && command.equals(COMMAND_BATTERY)) {
-            String level = args.getString("level");
-            String plugged = args.getString("plugged");
-            String powerSave = args.getString("powersave");
-            if (level != null) {
-                mLevel = Math.min(Math.max(Integer.parseInt(level), 0), 100);
-            }
-            if (plugged != null) {
-                mPluggedIn = Boolean.parseBoolean(plugged);
-            }
-            if (powerSave != null) {
-                mPowerSave = powerSave.equals("true");
-                firePowerSaveChanged();
-            }
-            fireBatteryLevelChanged();
+        if (!mDemoModeController.isInDemoMode()) {
+            return;
         }
+
+        String level = args.getString("level");
+        String plugged = args.getString("plugged");
+        String powerSave = args.getString("powersave");
+        if (level != null) {
+            mLevel = Math.min(Math.max(Integer.parseInt(level), 0), 100);
+        }
+        if (plugged != null) {
+            mPluggedIn = Boolean.parseBoolean(plugged);
+        }
+        if (powerSave != null) {
+            mPowerSave = powerSave.equals("true");
+            firePowerSaveChanged();
+        }
+        fireBatteryLevelChanged();
+    }
+
+    @Override
+    public List<String> demoCommands() {
+        List<String> s = new ArrayList<>();
+        s.add(DemoMode.COMMAND_BATTERY);
+        return s;
+    }
+
+    @Override
+    public void onDemoModeStarted() {
+        mBroadcastDispatcher.unregisterReceiver(this);
+    }
+
+    @Override
+    public void onDemoModeFinished() {
+        registerReceiver();
+        updatePowerSave();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index 0fc3d84..33b1a4a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -29,11 +29,14 @@
 import android.os.UserManager;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
 import com.android.settingslib.bluetooth.BluetoothCallback;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.settingslib.bluetooth.LocalBluetoothProfile;
 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 
@@ -46,11 +49,10 @@
 import java.util.WeakHashMap;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  */
-@Singleton
+@SysUISingleton
 public class BluetoothControllerImpl implements BluetoothController, BluetoothCallback,
         CachedBluetoothDevice.Callback, LocalBluetoothProfileManager.ServiceListener {
     private static final String TAG = "BluetoothController";
@@ -150,13 +152,13 @@
     }
 
     @Override
-    public void addCallback(Callback cb) {
+    public void addCallback(@NonNull Callback cb) {
         mHandler.obtainMessage(H.MSG_ADD_CALLBACK, cb).sendToTarget();
         mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
     }
 
     @Override
-    public void removeCallback(Callback cb) {
+    public void removeCallback(@NonNull Callback cb) {
         mHandler.obtainMessage(H.MSG_REMOVE_CALLBACK, cb).sendToTarget();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
index 78111fb..a0b03e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
@@ -123,13 +123,13 @@
     }
 
     @Override
-    public void addCallback(BrightnessMirrorListener listener) {
+    public void addCallback(@NonNull BrightnessMirrorListener listener) {
         Objects.requireNonNull(listener);
         mBrightnessMirrorListeners.add(listener);
     }
 
     @Override
-    public void removeCallback(BrightnessMirrorListener listener) {
+    public void removeCallback(@NonNull BrightnessMirrorListener listener) {
         mBrightnessMirrorListeners.remove(listener);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackController.java
index 626eef5..047ff75 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackController.java
@@ -15,14 +15,19 @@
 
 package com.android.systemui.statusbar.policy;
 
+import androidx.annotation.NonNull;
 import androidx.lifecycle.Lifecycle;
 import androidx.lifecycle.Lifecycle.Event;
 import androidx.lifecycle.LifecycleEventObserver;
 import androidx.lifecycle.LifecycleOwner;
 
 public interface CallbackController<T> {
-    void addCallback(T listener);
-    void removeCallback(T listener);
+
+    /** Add a callback */
+    void addCallback(@NonNull T listener);
+
+    /** Remove a callback */
+    void removeCallback(@NonNull T listener);
 
     /**
      * Wrapper to {@link #addCallback(Object)} when a lifecycle is in the resumed state
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
index 6106f38..7bde315 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
@@ -31,10 +31,12 @@
 import android.util.ArrayMap;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.util.Utils;
 
@@ -46,11 +48,10 @@
 import java.util.UUID;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 
 /** Platform implementation of the cast controller. **/
-@Singleton
+@SysUISingleton
 public class CastControllerImpl implements CastController {
     private static final String TAG = "CastController";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -95,7 +96,7 @@
     }
 
     @Override
-    public void addCallback(Callback callback) {
+    public void addCallback(@NonNull Callback callback) {
         synchronized (mCallbacks) {
             mCallbacks.add(callback);
         }
@@ -106,7 +107,7 @@
     }
 
     @Override
-    public void removeCallback(Callback callback) {
+    public void removeCallback(@NonNull Callback callback) {
         synchronized (mCallbacks) {
             mCallbacks.remove(callback);
         }
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 120a0e3..ef35a3c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -40,11 +40,11 @@
 import android.widget.TextView;
 
 import com.android.settingslib.Utils;
-import com.android.systemui.DemoMode;
 import com.android.systemui.Dependency;
 import com.android.systemui.FontSizeUtils;
 import com.android.systemui.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.demomode.DemoModeCommandReceiver;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
 import com.android.systemui.settings.CurrentUserTracker;
@@ -62,7 +62,10 @@
 /**
  * Digital clock for the status bar.
  */
-public class Clock extends TextView implements DemoMode, Tunable, CommandQueue.Callbacks,
+public class Clock extends TextView implements
+        DemoModeCommandReceiver,
+        Tunable,
+        CommandQueue.Callbacks,
         DarkReceiver, ConfigurationListener {
 
     public static final String CLOCK_SECONDS = "clock_seconds";
@@ -467,30 +470,35 @@
 
     @Override
     public void dispatchDemoCommand(String command, Bundle args) {
-        if (!mDemoMode && command.equals(COMMAND_ENTER)) {
-            mDemoMode = true;
-        } else if (mDemoMode && command.equals(COMMAND_EXIT)) {
-            mDemoMode = false;
-            updateClock();
-        } else if (mDemoMode && command.equals(COMMAND_CLOCK)) {
-            String millis = args.getString("millis");
-            String hhmm = args.getString("hhmm");
-            if (millis != null) {
-                mCalendar.setTimeInMillis(Long.parseLong(millis));
-            } else if (hhmm != null && hhmm.length() == 4) {
-                int hh = Integer.parseInt(hhmm.substring(0, 2));
-                int mm = Integer.parseInt(hhmm.substring(2));
-                boolean is24 = DateFormat.is24HourFormat(getContext(), mCurrentUserId);
-                if (is24) {
-                    mCalendar.set(Calendar.HOUR_OF_DAY, hh);
-                } else {
-                    mCalendar.set(Calendar.HOUR, hh);
-                }
-                mCalendar.set(Calendar.MINUTE, mm);
+        // Only registered for COMMAND_CLOCK
+        String millis = args.getString("millis");
+        String hhmm = args.getString("hhmm");
+        if (millis != null) {
+            mCalendar.setTimeInMillis(Long.parseLong(millis));
+        } else if (hhmm != null && hhmm.length() == 4) {
+            int hh = Integer.parseInt(hhmm.substring(0, 2));
+            int mm = Integer.parseInt(hhmm.substring(2));
+            boolean is24 = DateFormat.is24HourFormat(getContext(), mCurrentUserId);
+            if (is24) {
+                mCalendar.set(Calendar.HOUR_OF_DAY, hh);
+            } else {
+                mCalendar.set(Calendar.HOUR, hh);
             }
-            setText(getSmallTime());
-            setContentDescription(mContentDescriptionFormat.format(mCalendar.getTime()));
+            mCalendar.set(Calendar.MINUTE, mm);
         }
+        setText(getSmallTime());
+        setContentDescription(mContentDescriptionFormat.format(mCalendar.getTime()));
+    }
+
+    @Override
+    public void onDemoModeStarted() {
+        mDemoMode = true;
+    }
+
+    @Override
+    public void onDemoModeFinished() {
+        mDemoMode = false;
+        updateClock();
     }
 
     private final BroadcastReceiver mScreenReceiver = new BroadcastReceiver() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverControllerImpl.java
index 911715f..8207012 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverControllerImpl.java
@@ -21,6 +21,8 @@
 import android.os.Looper;
 import android.os.RemoteException;
 
+import androidx.annotation.NonNull;
+
 import java.util.ArrayList;
 
 public class DataSaverControllerImpl implements DataSaverController {
@@ -41,7 +43,8 @@
         }
     }
 
-    public void addCallback(Listener listener) {
+    @Override
+    public void addCallback(@NonNull Listener listener) {
         synchronized (mListeners) {
             mListeners.add(listener);
             if (mListeners.size() == 1) {
@@ -51,7 +54,8 @@
         listener.onDataSaverChanged(isDataSaverEnabled());
     }
 
-    public void removeCallback(Listener listener) {
+    @Override
+    public void removeCallback(@NonNull Listener listener) {
         synchronized (mListeners) {
             mListeners.remove(listener);
             if (mListeners.size() == 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java
index 7280a88..9b4e165 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java
@@ -24,18 +24,20 @@
 import android.provider.Settings.Secure;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.settings.CurrentUserTracker;
 
 import java.util.ArrayList;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  */
-@Singleton
+@SysUISingleton
 public class DeviceProvisionedControllerImpl extends CurrentUserTracker implements
         DeviceProvisionedController {
 
@@ -87,7 +89,7 @@
     }
 
     @Override
-    public void addCallback(DeviceProvisionedListener listener) {
+    public void addCallback(@NonNull DeviceProvisionedListener listener) {
         mListeners.add(listener);
         if (mListeners.size() == 1) {
             startListening(getCurrentUser());
@@ -97,7 +99,7 @@
     }
 
     @Override
-    public void removeCallback(DeviceProvisionedListener listener) {
+    public void removeCallback(@NonNull DeviceProvisionedListener listener) {
         mListeners.remove(listener);
         if (mListeners.size() == 0) {
             stopListening();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java
index eeef726..5011d96 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java
@@ -19,6 +19,7 @@
 import android.os.Handler;
 import android.util.ArrayMap;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.Plugin;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.shared.plugins.PluginManager;
@@ -34,11 +35,10 @@
 import java.util.function.Supplier;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  */
-@Singleton
+@SysUISingleton
 public class ExtensionControllerImpl implements ExtensionController {
 
     public static final int SORT_ORDER_PLUGIN  = 0;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
index 41ff9d10..d7c2b96 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
@@ -30,18 +30,21 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
+import com.android.systemui.dagger.SysUISingleton;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Manages the flashlight.
  */
-@Singleton
+@SysUISingleton
 public class FlashlightControllerImpl implements FlashlightController {
 
     private static final String TAG = "FlashlightController";
@@ -123,7 +126,8 @@
         return mTorchAvailable;
     }
 
-    public void addCallback(FlashlightListener l) {
+    @Override
+    public void addCallback(@NonNull FlashlightListener l) {
         synchronized (mListeners) {
             if (mCameraId == null) {
                 tryInitCamera();
@@ -135,7 +139,8 @@
         }
     }
 
-    public void removeCallback(FlashlightListener l) {
+    @Override
+    public void removeCallback(@NonNull FlashlightListener l) {
         synchronized (mListeners) {
             cleanUpListenersLocked(l);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
index 60ee75b..99feb18 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
@@ -30,7 +30,10 @@
 import android.os.UserManager;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.util.ConcurrentUtils;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 
@@ -40,12 +43,11 @@
 import java.util.List;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Controller used to retrieve information related to a hotspot.
  */
-@Singleton
+@SysUISingleton
 public class HotspotControllerImpl implements HotspotController, WifiManager.SoftApCallback {
 
     private static final String TAG = "HotspotController";
@@ -143,7 +145,7 @@
      * changes. It will immediately trigger the callback added to notify current state.
      */
     @Override
-    public void addCallback(Callback callback) {
+    public void addCallback(@NonNull Callback callback) {
         synchronized (mCallbacks) {
             if (callback == null || mCallbacks.contains(callback)) return;
             if (DEBUG) Log.d(TAG, "addCallback " + callback);
@@ -163,7 +165,7 @@
     }
 
     @Override
-    public void removeCallback(Callback callback) {
+    public void removeCallback(@NonNull Callback callback) {
         if (callback == null) return;
         if (DEBUG) Log.d(TAG, "removeCallback " + callback);
         synchronized (mCallbacks) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index a7f60d6..7f4eec7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -31,6 +31,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.SysUISingleton;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -38,11 +39,10 @@
 import java.util.Objects;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  */
-@Singleton
+@SysUISingleton
 public class KeyguardStateControllerImpl implements KeyguardStateController, Dumpable {
 
     private static final boolean DEBUG_AUTH_WITH_ADB = false;
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 adfc14e..0fdc80b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
@@ -33,12 +33,14 @@
 import android.os.UserManager;
 import android.provider.Settings;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
 import com.android.systemui.BootCompleteCache;
 import com.android.systemui.appops.AppOpItem;
 import com.android.systemui.appops.AppOpsController;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.util.Utils;
@@ -47,12 +49,11 @@
 import java.util.List;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * A controller to manage changes of location related states and update the views accordingly.
  */
-@Singleton
+@SysUISingleton
 public class LocationControllerImpl extends BroadcastReceiver implements LocationController,
         AppOpsController.Callback {
 
@@ -87,12 +88,14 @@
     /**
      * Add a callback to listen for changes in location settings.
      */
-    public void addCallback(LocationChangeCallback cb) {
+    @Override
+    public void addCallback(@NonNull LocationChangeCallback cb) {
         mHandler.obtainMessage(H.MSG_ADD_CALLBACK, cb).sendToTarget();
         mHandler.sendEmptyMessage(H.MSG_LOCATION_SETTINGS_CHANGED);
     }
 
-    public void removeCallback(LocationChangeCallback cb) {
+    @Override
+    public void removeCallback(@NonNull LocationChangeCallback cb) {
         mHandler.obtainMessage(H.MSG_REMOVE_CALLBACK, cb).sendToTarget();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
index 95a9772..b790c92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
@@ -22,7 +22,7 @@
 
 import com.android.settingslib.net.DataUsageController;
 import com.android.settingslib.wifi.AccessPoint;
-import com.android.systemui.DemoMode;
+import com.android.systemui.demomode.DemoMode;
 import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
 
 import java.util.List;
@@ -30,8 +30,6 @@
 public interface NetworkController extends CallbackController<SignalCallback>, DemoMode {
 
     boolean hasMobileDataFeature();
-    void addCallback(SignalCallback cb);
-    void removeCallback(SignalCallback cb);
     void setWifiEnabled(boolean enabled);
     AccessPointController getAccessPointController();
     DataUsageController getMobileDataController();
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 32c4aec..2253ce7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -55,14 +55,18 @@
 import android.util.MathUtils;
 import android.util.SparseArray;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.settingslib.net.DataUsageController;
-import com.android.systemui.DemoMode;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.demomode.DemoMode;
+import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.settings.CurrentUserTracker;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
 
@@ -76,10 +80,9 @@
 import java.util.Locale;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /** Platform implementation of the network controller. **/
-@Singleton
+@SysUISingleton
 public class NetworkControllerImpl extends BroadcastReceiver
         implements NetworkController, DemoMode, DataUsageController.NetworkNameProvider, Dumpable {
     // debug
@@ -104,6 +107,7 @@
     private final DataSaverController mDataSaverController;
     private final CurrentUserTracker mUserTracker;
     private final BroadcastDispatcher mBroadcastDispatcher;
+    private final DemoModeController mDemoModeController;
     private final Object mLock = new Object();
     private Config mConfig;
 
@@ -173,11 +177,16 @@
      * Construct this controller object and register for updates.
      */
     @Inject
-    public NetworkControllerImpl(Context context, @Background Looper bgLooper,
+    public NetworkControllerImpl(
+            Context context,
+            @Background Looper bgLooper,
             DeviceProvisionedController deviceProvisionedController,
-            BroadcastDispatcher broadcastDispatcher, ConnectivityManager connectivityManager,
-            TelephonyManager telephonyManager, @Nullable WifiManager wifiManager,
-            NetworkScoreManager networkScoreManager) {
+            BroadcastDispatcher broadcastDispatcher,
+            ConnectivityManager connectivityManager,
+            TelephonyManager telephonyManager,
+            @Nullable WifiManager wifiManager,
+            NetworkScoreManager networkScoreManager,
+            DemoModeController demoModeController) {
         this(context, connectivityManager,
                 telephonyManager,
                 wifiManager,
@@ -188,7 +197,8 @@
                 new DataUsageController(context),
                 new SubscriptionDefaults(),
                 deviceProvisionedController,
-                broadcastDispatcher);
+                broadcastDispatcher,
+                demoModeController);
         mReceiverHandler.post(mRegisterListeners);
     }
 
@@ -202,7 +212,8 @@
             DataUsageController dataUsageController,
             SubscriptionDefaults defaultsHandler,
             DeviceProvisionedController deviceProvisionedController,
-            BroadcastDispatcher broadcastDispatcher) {
+            BroadcastDispatcher broadcastDispatcher,
+            DemoModeController demoModeController) {
         mContext = context;
         mConfig = config;
         mReceiverHandler = new Handler(bgLooper);
@@ -215,6 +226,7 @@
         mConnectivityManager = connectivityManager;
         mHasMobileDataFeature =
                 mConnectivityManager.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
+        mDemoModeController = demoModeController;
 
         // telephony
         mPhone = telephonyManager;
@@ -306,6 +318,8 @@
                 doUpdateMobileControllers();
             }
         };
+
+        mDemoModeController.addCallback(this);
     }
 
     private final Runnable mClearForceValidated = () -> {
@@ -514,7 +528,8 @@
         mCallbackHandler.setEmergencyCallsOnly(mIsEmergency);
     }
 
-    public void addCallback(SignalCallback cb) {
+    @Override
+    public void addCallback(@NonNull SignalCallback cb) {
         cb.setSubs(mCurrentSubscriptions);
         cb.setIsAirplaneMode(new IconState(mAirplaneMode,
                 TelephonyIcons.FLIGHT_MODE_ICON, R.string.accessibility_airplane_mode, mContext));
@@ -529,7 +544,7 @@
     }
 
     @Override
-    public void removeCallback(SignalCallback cb) {
+    public void removeCallback(@NonNull SignalCallback cb) {
         mCallbackHandler.setListening(cb, false);
     }
 
@@ -932,203 +947,215 @@
         return "UNKNOWN_SOURCE";
     }
 
-    private boolean mDemoMode;
     private boolean mDemoInetCondition;
     private WifiSignalController.WifiState mDemoWifiState;
 
     @Override
+    public void onDemoModeStarted() {
+        if (DEBUG) Log.d(TAG, "Entering demo mode");
+        unregisterListeners();
+        mDemoInetCondition = mInetCondition;
+        mDemoWifiState = mWifiSignalController.getState();
+        mDemoWifiState.ssid = "DemoMode";
+    }
+
+    @Override
+    public void onDemoModeFinished() {
+        if (DEBUG) Log.d(TAG, "Exiting demo mode");
+        // Update what MobileSignalControllers, because they may change
+        // to set the number of sim slots.
+        updateMobileControllers();
+        for (int i = 0; i < mMobileSignalControllers.size(); i++) {
+            MobileSignalController controller = mMobileSignalControllers.valueAt(i);
+            controller.resetLastState();
+        }
+        mWifiSignalController.resetLastState();
+        mReceiverHandler.post(mRegisterListeners);
+        notifyAllListeners();
+    }
+
+    @Override
     public void dispatchDemoCommand(String command, Bundle args) {
-        if (!mDemoMode && command.equals(COMMAND_ENTER)) {
-            if (DEBUG) Log.d(TAG, "Entering demo mode");
-            unregisterListeners();
-            mDemoMode = true;
-            mDemoInetCondition = mInetCondition;
-            mDemoWifiState = mWifiSignalController.getState();
-            mDemoWifiState.ssid = "DemoMode";
-        } else if (mDemoMode && command.equals(COMMAND_EXIT)) {
-            if (DEBUG) Log.d(TAG, "Exiting demo mode");
-            mDemoMode = false;
-            // Update what MobileSignalControllers, because they may change
-            // to set the number of sim slots.
-            updateMobileControllers();
+        if (!mDemoModeController.isInDemoMode()) {
+            return;
+        }
+
+        String airplane = args.getString("airplane");
+        if (airplane != null) {
+            boolean show = airplane.equals("show");
+            mCallbackHandler.setIsAirplaneMode(new IconState(show,
+                    TelephonyIcons.FLIGHT_MODE_ICON, R.string.accessibility_airplane_mode,
+                    mContext));
+        }
+        String fully = args.getString("fully");
+        if (fully != null) {
+            mDemoInetCondition = Boolean.parseBoolean(fully);
+            BitSet connected = new BitSet();
+
+            if (mDemoInetCondition) {
+                connected.set(mWifiSignalController.mTransportType);
+            }
+            mWifiSignalController.updateConnectivity(connected, connected);
             for (int i = 0; i < mMobileSignalControllers.size(); i++) {
                 MobileSignalController controller = mMobileSignalControllers.valueAt(i);
-                controller.resetLastState();
-            }
-            mWifiSignalController.resetLastState();
-            mReceiverHandler.post(mRegisterListeners);
-            notifyAllListeners();
-        } else if (mDemoMode && command.equals(COMMAND_NETWORK)) {
-            String airplane = args.getString("airplane");
-            if (airplane != null) {
-                boolean show = airplane.equals("show");
-                mCallbackHandler.setIsAirplaneMode(new IconState(show,
-                        TelephonyIcons.FLIGHT_MODE_ICON, R.string.accessibility_airplane_mode,
-                        mContext));
-            }
-            String fully = args.getString("fully");
-            if (fully != null) {
-                mDemoInetCondition = Boolean.parseBoolean(fully);
-                BitSet connected = new BitSet();
-
                 if (mDemoInetCondition) {
-                    connected.set(mWifiSignalController.mTransportType);
+                    connected.set(controller.mTransportType);
                 }
-                mWifiSignalController.updateConnectivity(connected, connected);
+                controller.updateConnectivity(connected, connected);
+            }
+        }
+        String wifi = args.getString("wifi");
+        if (wifi != null) {
+            boolean show = wifi.equals("show");
+            String level = args.getString("level");
+            if (level != null) {
+                mDemoWifiState.level = level.equals("null") ? -1
+                        : Math.min(Integer.parseInt(level), WifiIcons.WIFI_LEVEL_COUNT - 1);
+                mDemoWifiState.connected = mDemoWifiState.level >= 0;
+            }
+            String activity = args.getString("activity");
+            if (activity != null) {
+                switch (activity) {
+                    case "inout":
+                        mWifiSignalController.setActivity(DATA_ACTIVITY_INOUT);
+                        break;
+                    case "in":
+                        mWifiSignalController.setActivity(DATA_ACTIVITY_IN);
+                        break;
+                    case "out":
+                        mWifiSignalController.setActivity(DATA_ACTIVITY_OUT);
+                        break;
+                    default:
+                        mWifiSignalController.setActivity(DATA_ACTIVITY_NONE);
+                        break;
+                }
+            } else {
+                mWifiSignalController.setActivity(DATA_ACTIVITY_NONE);
+            }
+            String ssid = args.getString("ssid");
+            if (ssid != null) {
+                mDemoWifiState.ssid = ssid;
+            }
+            mDemoWifiState.enabled = show;
+            mWifiSignalController.notifyListeners();
+        }
+        String sims = args.getString("sims");
+        if (sims != null) {
+            int num = MathUtils.constrain(Integer.parseInt(sims), 1, 8);
+            List<SubscriptionInfo> subs = new ArrayList<>();
+            if (num != mMobileSignalControllers.size()) {
+                mMobileSignalControllers.clear();
+                int start = mSubscriptionManager.getActiveSubscriptionInfoCountMax();
+                for (int i = start /* get out of normal index range */; i < start + num; i++) {
+                    subs.add(addSignalController(i, i));
+                }
+                mCallbackHandler.setSubs(subs);
                 for (int i = 0; i < mMobileSignalControllers.size(); i++) {
-                    MobileSignalController controller = mMobileSignalControllers.valueAt(i);
-                    if (mDemoInetCondition) {
-                        connected.set(controller.mTransportType);
-                    }
-                    controller.updateConnectivity(connected, connected);
-                }
-            }
-            String wifi = args.getString("wifi");
-            if (wifi != null) {
-                boolean show = wifi.equals("show");
-                String level = args.getString("level");
-                if (level != null) {
-                    mDemoWifiState.level = level.equals("null") ? -1
-                            : Math.min(Integer.parseInt(level), WifiIcons.WIFI_LEVEL_COUNT - 1);
-                    mDemoWifiState.connected = mDemoWifiState.level >= 0;
-                }
-                String activity = args.getString("activity");
-                if (activity != null) {
-                    switch (activity) {
-                        case "inout":
-                            mWifiSignalController.setActivity(DATA_ACTIVITY_INOUT);
-                            break;
-                        case "in":
-                            mWifiSignalController.setActivity(DATA_ACTIVITY_IN);
-                            break;
-                        case "out":
-                            mWifiSignalController.setActivity(DATA_ACTIVITY_OUT);
-                            break;
-                        default:
-                            mWifiSignalController.setActivity(DATA_ACTIVITY_NONE);
-                            break;
-                    }
-                } else {
-                    mWifiSignalController.setActivity(DATA_ACTIVITY_NONE);
-                }
-                String ssid = args.getString("ssid");
-                if (ssid != null) {
-                    mDemoWifiState.ssid = ssid;
-                }
-                mDemoWifiState.enabled = show;
-                mWifiSignalController.notifyListeners();
-            }
-            String sims = args.getString("sims");
-            if (sims != null) {
-                int num = MathUtils.constrain(Integer.parseInt(sims), 1, 8);
-                List<SubscriptionInfo> subs = new ArrayList<>();
-                if (num != mMobileSignalControllers.size()) {
-                    mMobileSignalControllers.clear();
-                    int start = mSubscriptionManager.getActiveSubscriptionInfoCountMax();
-                    for (int i = start /* get out of normal index range */; i < start + num; i++) {
-                        subs.add(addSignalController(i, i));
-                    }
-                    mCallbackHandler.setSubs(subs);
-                    for (int i = 0; i < mMobileSignalControllers.size(); i++) {
-                        int key = mMobileSignalControllers.keyAt(i);
-                        MobileSignalController controller = mMobileSignalControllers.get(key);
-                        controller.notifyListeners();
-                    }
-                }
-            }
-            String nosim = args.getString("nosim");
-            if (nosim != null) {
-                mHasNoSubs = nosim.equals("show");
-                mCallbackHandler.setNoSims(mHasNoSubs, mSimDetected);
-            }
-            String mobile = args.getString("mobile");
-            if (mobile != null) {
-                boolean show = mobile.equals("show");
-                String datatype = args.getString("datatype");
-                String slotString = args.getString("slot");
-                int slot = TextUtils.isEmpty(slotString) ? 0 : Integer.parseInt(slotString);
-                slot = MathUtils.constrain(slot, 0, 8);
-                // Ensure we have enough sim slots
-                List<SubscriptionInfo> subs = new ArrayList<>();
-                while (mMobileSignalControllers.size() <= slot) {
-                    int nextSlot = mMobileSignalControllers.size();
-                    subs.add(addSignalController(nextSlot, nextSlot));
-                }
-                if (!subs.isEmpty()) {
-                    mCallbackHandler.setSubs(subs);
-                }
-                // Hack to index linearly for easy use.
-                MobileSignalController controller = mMobileSignalControllers.valueAt(slot);
-                controller.getState().dataSim = datatype != null;
-                controller.getState().isDefault = datatype != null;
-                controller.getState().dataConnected = datatype != null;
-                if (datatype != null) {
-                    controller.getState().iconGroup =
-                            datatype.equals("1x") ? TelephonyIcons.ONE_X :
-                            datatype.equals("3g") ? TelephonyIcons.THREE_G :
-                            datatype.equals("4g") ? TelephonyIcons.FOUR_G :
-                            datatype.equals("4g+") ? TelephonyIcons.FOUR_G_PLUS :
-                            datatype.equals("5g") ? TelephonyIcons.NR_5G :
-                            datatype.equals("5ge") ? TelephonyIcons.LTE_CA_5G_E :
-                            datatype.equals("5g+") ? TelephonyIcons.NR_5G_PLUS :
-                            datatype.equals("e") ? TelephonyIcons.E :
-                            datatype.equals("g") ? TelephonyIcons.G :
-                            datatype.equals("h") ? TelephonyIcons.H :
-                            datatype.equals("h+") ? TelephonyIcons.H_PLUS :
-                            datatype.equals("lte") ? TelephonyIcons.LTE :
-                            datatype.equals("lte+") ? TelephonyIcons.LTE_PLUS :
-                            datatype.equals("dis") ? TelephonyIcons.DATA_DISABLED :
-                            datatype.equals("not") ? TelephonyIcons.NOT_DEFAULT_DATA :
-                            TelephonyIcons.UNKNOWN;
-                }
-                if (args.containsKey("roam")) {
-                    controller.getState().roaming = "show".equals(args.getString("roam"));
-                }
-                String level = args.getString("level");
-                if (level != null) {
-                    controller.getState().level = level.equals("null") ? -1
-                            : Math.min(Integer.parseInt(level),
-                                    CellSignalStrength.getNumSignalStrengthLevels());
-                    controller.getState().connected = controller.getState().level >= 0;
-                }
-                if (args.containsKey("inflate")) {
-                    for (int i = 0; i < mMobileSignalControllers.size(); i++) {
-                        mMobileSignalControllers.valueAt(i).mInflateSignalStrengths =
-                                "true".equals(args.getString("inflate"));
-                    }
-                }
-                String activity = args.getString("activity");
-                if (activity != null) {
-                    controller.getState().dataConnected = true;
-                    switch (activity) {
-                        case "inout":
-                            controller.setActivity(TelephonyManager.DATA_ACTIVITY_INOUT);
-                            break;
-                        case "in":
-                            controller.setActivity(TelephonyManager.DATA_ACTIVITY_IN);
-                            break;
-                        case "out":
-                            controller.setActivity(TelephonyManager.DATA_ACTIVITY_OUT);
-                            break;
-                        default:
-                            controller.setActivity(TelephonyManager.DATA_ACTIVITY_NONE);
-                            break;
-                    }
-                } else {
-                    controller.setActivity(TelephonyManager.DATA_ACTIVITY_NONE);
-                }
-                controller.getState().enabled = show;
-                controller.notifyListeners();
-            }
-            String carrierNetworkChange = args.getString("carriernetworkchange");
-            if (carrierNetworkChange != null) {
-                boolean show = carrierNetworkChange.equals("show");
-                for (int i = 0; i < mMobileSignalControllers.size(); i++) {
-                    MobileSignalController controller = mMobileSignalControllers.valueAt(i);
-                    controller.setCarrierNetworkChangeMode(show);
+                    int key = mMobileSignalControllers.keyAt(i);
+                    MobileSignalController controller = mMobileSignalControllers.get(key);
+                    controller.notifyListeners();
                 }
             }
         }
+        String nosim = args.getString("nosim");
+        if (nosim != null) {
+            mHasNoSubs = nosim.equals("show");
+            mCallbackHandler.setNoSims(mHasNoSubs, mSimDetected);
+        }
+        String mobile = args.getString("mobile");
+        if (mobile != null) {
+            boolean show = mobile.equals("show");
+            String datatype = args.getString("datatype");
+            String slotString = args.getString("slot");
+            int slot = TextUtils.isEmpty(slotString) ? 0 : Integer.parseInt(slotString);
+            slot = MathUtils.constrain(slot, 0, 8);
+            // Ensure we have enough sim slots
+            List<SubscriptionInfo> subs = new ArrayList<>();
+            while (mMobileSignalControllers.size() <= slot) {
+                int nextSlot = mMobileSignalControllers.size();
+                subs.add(addSignalController(nextSlot, nextSlot));
+            }
+            if (!subs.isEmpty()) {
+                mCallbackHandler.setSubs(subs);
+            }
+            // Hack to index linearly for easy use.
+            MobileSignalController controller = mMobileSignalControllers.valueAt(slot);
+            controller.getState().dataSim = datatype != null;
+            controller.getState().isDefault = datatype != null;
+            controller.getState().dataConnected = datatype != null;
+            if (datatype != null) {
+                controller.getState().iconGroup =
+                        datatype.equals("1x") ? TelephonyIcons.ONE_X :
+                        datatype.equals("3g") ? TelephonyIcons.THREE_G :
+                        datatype.equals("4g") ? TelephonyIcons.FOUR_G :
+                        datatype.equals("4g+") ? TelephonyIcons.FOUR_G_PLUS :
+                        datatype.equals("5g") ? TelephonyIcons.NR_5G :
+                        datatype.equals("5ge") ? TelephonyIcons.LTE_CA_5G_E :
+                        datatype.equals("5g+") ? TelephonyIcons.NR_5G_PLUS :
+                        datatype.equals("e") ? TelephonyIcons.E :
+                        datatype.equals("g") ? TelephonyIcons.G :
+                        datatype.equals("h") ? TelephonyIcons.H :
+                        datatype.equals("h+") ? TelephonyIcons.H_PLUS :
+                        datatype.equals("lte") ? TelephonyIcons.LTE :
+                        datatype.equals("lte+") ? TelephonyIcons.LTE_PLUS :
+                        datatype.equals("dis") ? TelephonyIcons.DATA_DISABLED :
+                        datatype.equals("not") ? TelephonyIcons.NOT_DEFAULT_DATA :
+                        TelephonyIcons.UNKNOWN;
+            }
+            if (args.containsKey("roam")) {
+                controller.getState().roaming = "show".equals(args.getString("roam"));
+            }
+            String level = args.getString("level");
+            if (level != null) {
+                controller.getState().level = level.equals("null") ? -1
+                        : Math.min(Integer.parseInt(level),
+                                CellSignalStrength.getNumSignalStrengthLevels());
+                controller.getState().connected = controller.getState().level >= 0;
+            }
+            if (args.containsKey("inflate")) {
+                for (int i = 0; i < mMobileSignalControllers.size(); i++) {
+                    mMobileSignalControllers.valueAt(i).mInflateSignalStrengths =
+                            "true".equals(args.getString("inflate"));
+                }
+            }
+            String activity = args.getString("activity");
+            if (activity != null) {
+                controller.getState().dataConnected = true;
+                switch (activity) {
+                    case "inout":
+                        controller.setActivity(TelephonyManager.DATA_ACTIVITY_INOUT);
+                        break;
+                    case "in":
+                        controller.setActivity(TelephonyManager.DATA_ACTIVITY_IN);
+                        break;
+                    case "out":
+                        controller.setActivity(TelephonyManager.DATA_ACTIVITY_OUT);
+                        break;
+                    default:
+                        controller.setActivity(TelephonyManager.DATA_ACTIVITY_NONE);
+                        break;
+                }
+            } else {
+                controller.setActivity(TelephonyManager.DATA_ACTIVITY_NONE);
+            }
+            controller.getState().enabled = show;
+            controller.notifyListeners();
+        }
+        String carrierNetworkChange = args.getString("carriernetworkchange");
+        if (carrierNetworkChange != null) {
+            boolean show = carrierNetworkChange.equals("show");
+            for (int i = 0; i < mMobileSignalControllers.size(); i++) {
+                MobileSignalController controller = mMobileSignalControllers.valueAt(i);
+                controller.setCarrierNetworkChangeMode(show);
+            }
+        }
+    }
+
+    @Override
+    public List<String> demoCommands() {
+        List<String> s = new ArrayList<>();
+        s.add(DemoMode.COMMAND_NETWORK);
+        return s;
     }
 
     private SubscriptionInfo addSignalController(int id, int simSlotIndex) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
index 288b3af..272c494 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
@@ -23,17 +23,20 @@
 import android.content.IntentFilter;
 import android.os.UserHandle;
 
+import androidx.annotation.NonNull;
+
+import com.android.systemui.dagger.SysUISingleton;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Implementation of {@link NextAlarmController}
  */
-@Singleton
+@SysUISingleton
 public class NextAlarmControllerImpl extends BroadcastReceiver
         implements NextAlarmController {
 
@@ -59,12 +62,14 @@
         pw.print("  mNextAlarm="); pw.println(mNextAlarm);
     }
 
-    public void addCallback(NextAlarmChangeCallback cb) {
+    @Override
+    public void addCallback(@NonNull NextAlarmChangeCallback cb) {
         mChangeCallbacks.add(cb);
         cb.onNextAlarmChanged(mNextAlarm);
     }
 
-    public void removeCallback(NextAlarmChangeCallback cb) {
+    @Override
+    public void removeCallback(@NonNull NextAlarmChangeCallback cb) {
         mChangeCallbacks.remove(cb);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.java
index 7ef9945..ac8b47d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.java
@@ -21,17 +21,17 @@
 import android.content.res.Configuration;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.qs.QSFragment;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.StatusBar;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Let {@link RemoteInputView} to control the visibility of QuickSetting.
  */
-@Singleton
+@SysUISingleton
 public class RemoteInputQuickSettingsDisabler
         implements ConfigurationController.ConfigurationListener {
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputUriController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputUriController.java
index b503183..03b6122 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputUriController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputUriController.java
@@ -23,17 +23,17 @@
 
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Handles granting and revoking inline URI grants associated with RemoteInputs.
  */
-@Singleton
+@SysUISingleton
 public class RemoteInputUriController {
 
     private final IStatusBarService mStatusBarManagerService;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
index 1f368e1..53d68d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
@@ -19,15 +19,17 @@
 import android.content.Context;
 import android.os.UserHandle;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.view.RotationPolicy;
+import com.android.systemui.dagger.SysUISingleton;
 
 import java.util.concurrent.CopyOnWriteArrayList;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /** Platform implementation of the rotation lock controller. **/
-@Singleton
+@SysUISingleton
 public final class RotationLockControllerImpl implements RotationLockController {
     private final Context mContext;
     private final CopyOnWriteArrayList<RotationLockControllerCallback> mCallbacks =
@@ -47,12 +49,14 @@
         setListening(true);
     }
 
-    public void addCallback(RotationLockControllerCallback callback) {
+    @Override
+    public void addCallback(@NonNull RotationLockControllerCallback callback) {
         mCallbacks.add(callback);
         notifyChanged(callback);
     }
 
-    public void removeCallback(RotationLockControllerCallback callback) {
+    @Override
+    public void removeCallback(@NonNull RotationLockControllerCallback callback) {
         mCallbacks.remove(callback);
     }
 
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 309d4b0..7e54e8d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -42,11 +42,14 @@
 import android.util.Pair;
 import android.util.SparseArray;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.net.LegacyVpnInfo;
 import com.android.internal.net.VpnConfig;
 import com.android.systemui.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.settings.CurrentUserTracker;
 
@@ -56,11 +59,10 @@
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  */
-@Singleton
+@SysUISingleton
 public class SecurityControllerImpl extends CurrentUserTracker implements SecurityController {
 
     private static final String TAG = "SecurityController";
@@ -274,7 +276,7 @@
     }
 
     @Override
-    public void removeCallback(SecurityControllerCallback callback) {
+    public void removeCallback(@NonNull SecurityControllerCallback callback) {
         synchronized (mCallbacks) {
             if (callback == null) return;
             if (DEBUG) Log.d(TAG, "removeCallback " + callback);
@@ -283,7 +285,7 @@
     }
 
     @Override
-    public void addCallback(SecurityControllerCallback callback) {
+    public void addCallback(@NonNull SecurityControllerCallback callback) {
         synchronized (mCallbacks) {
             if (callback == null || mCallbacks.contains(callback)) return;
             if (DEBUG) Log.d(TAG, "addCallback " + callback);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensorPrivacyControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensorPrivacyControllerImpl.java
index 5db6693..20cc46f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensorPrivacyControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensorPrivacyControllerImpl.java
@@ -19,16 +19,19 @@
 import android.content.Context;
 import android.hardware.SensorPrivacyManager;
 
+import androidx.annotation.NonNull;
+
+import com.android.systemui.dagger.SysUISingleton;
+
 import java.util.ArrayList;
 import java.util.List;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Controls sensor privacy state and notification.
  */
-@Singleton
+@SysUISingleton
 public class SensorPrivacyControllerImpl implements SensorPrivacyController,
         SensorPrivacyManager.OnSensorPrivacyChangedListener {
     private SensorPrivacyManager mSensorPrivacyManager;
@@ -60,7 +63,7 @@
     /**
      * Adds the provided listener for callbacks when sensor privacy state changes.
      */
-    public void addCallback(OnSensorPrivacyChangedListener listener) {
+    public void addCallback(@NonNull OnSensorPrivacyChangedListener listener) {
         synchronized (mLock) {
             mListeners.add(listener);
             notifyListenerLocked(listener);
@@ -70,7 +73,7 @@
     /**
      * Removes the provided listener from callbacks when sensor privacy state changes.
      */
-    public void removeCallback(OnSensorPrivacyChangedListener listener) {
+    public void removeCallback(@NonNull OnSensorPrivacyChangedListener listener) {
         synchronized (mLock) {
             mListeners.remove(listener);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
index 311e8738..52a6bca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
@@ -27,13 +27,13 @@
 
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.util.DeviceConfigProxy;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
-@Singleton
+@SysUISingleton
 public final class SmartReplyConstants {
 
     private static final String TAG = "SmartReplyConstants";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
index 0ca6ff6..9eaee22 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
@@ -34,18 +34,20 @@
 import android.provider.ContactsContract;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.util.UserIcons;
 import com.android.settingslib.drawable.UserIconDrawable;
 import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
 
 import java.util.ArrayList;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  */
-@Singleton
+@SysUISingleton
 public class UserInfoControllerImpl implements UserInfoController {
 
     private static final String TAG = "UserInfoController";
@@ -75,12 +77,14 @@
                 null, null);
     }
 
-    public void addCallback(OnUserInfoChangedListener callback) {
+    @Override
+    public void addCallback(@NonNull OnUserInfoChangedListener callback) {
         mCallbacks.add(callback);
         callback.onUserInfoChanged(mUserName, mUserDrawable, mUserAccount);
     }
 
-    public void removeCallback(OnUserInfoChangedListener callback) {
+    @Override
+    public void removeCallback(@NonNull OnUserInfoChangedListener callback) {
         mCallbacks.remove(callback);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index ce5bb05..f9ac760 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -62,6 +62,7 @@
 import com.android.systemui.R;
 import com.android.systemui.SystemUISecondaryUserService;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.qs.DetailAdapter;
@@ -76,12 +77,11 @@
 import java.util.List;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Keeps a list of all users on the device for user switching.
  */
-@Singleton
+@SysUISingleton
 public class UserSwitcherController implements Dumpable {
 
     public static final float USER_SWITCH_ENABLED_ALPHA = 1.0f;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
index 5257ce4..4ae9665 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
@@ -84,7 +84,7 @@
                 R.bool.config_showWifiIndicatorWhenEnabled);
         boolean wifiVisible = mCurrentState.enabled && (
                 (mCurrentState.connected && mCurrentState.inetCondition == 1)
-                        || !mHasMobileDataFeature || mWifiTracker.isDefaultNetwork
+                        || !mHasMobileDataFeature || mCurrentState.isDefault
                         || visibleWhenEnabled);
         String wifiDesc = mCurrentState.connected ? mCurrentState.ssid : null;
         boolean ssidPresent = wifiVisible && mCurrentState.ssid != null;
@@ -107,6 +107,7 @@
     public void fetchInitialState() {
         mWifiTracker.fetchInitialState();
         mCurrentState.enabled = mWifiTracker.enabled;
+        mCurrentState.isDefault = mWifiTracker.isDefaultNetwork;
         mCurrentState.connected = mWifiTracker.connected;
         mCurrentState.ssid = mWifiTracker.ssid;
         mCurrentState.rssi = mWifiTracker.rssi;
@@ -121,6 +122,7 @@
     public void handleBroadcast(Intent intent) {
         mWifiTracker.handleBroadcast(intent);
         mCurrentState.enabled = mWifiTracker.enabled;
+        mCurrentState.isDefault = mWifiTracker.isDefaultNetwork;
         mCurrentState.connected = mWifiTracker.connected;
         mCurrentState.ssid = mWifiTracker.ssid;
         mCurrentState.rssi = mWifiTracker.rssi;
@@ -131,6 +133,7 @@
 
     private void handleStatusUpdated() {
         mCurrentState.statusLabel = mWifiTracker.statusLabel;
+        mCurrentState.isDefault = mWifiTracker.isDefaultNetwork;
         notifyListenersIfNecessary();
     }
 
@@ -156,6 +159,7 @@
     static class WifiState extends SignalController.State {
         String ssid;
         boolean isTransient;
+        boolean isDefault;
         String statusLabel;
 
         @Override
@@ -164,6 +168,7 @@
             WifiState state = (WifiState) s;
             ssid = state.ssid;
             isTransient = state.isTransient;
+            isDefault = state.isDefault;
             statusLabel = state.statusLabel;
         }
 
@@ -172,6 +177,7 @@
             super.toString(builder);
             builder.append(",ssid=").append(ssid)
                 .append(",isTransient=").append(isTransient)
+                .append(",isDefault=").append(isDefault)
                 .append(",statusLabel=").append(statusLabel);
         }
 
@@ -183,6 +189,7 @@
             WifiState other = (WifiState) o;
             return Objects.equals(other.ssid, ssid)
                     && other.isTransient == isTransient
+                    && other.isDefault == isDefault
                     && TextUtils.equals(other.statusLabel, statusLabel);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index 4376a01..897a3b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -37,9 +37,12 @@
 import android.text.format.DateFormat;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Dumpable;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.qs.GlobalSetting;
 import com.android.systemui.settings.CurrentUserTracker;
@@ -51,10 +54,9 @@
 import java.util.Objects;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /** Platform implementation of the zen mode controller. **/
-@Singleton
+@SysUISingleton
 public class ZenModeControllerImpl extends CurrentUserTracker
         implements ZenModeController, Dumpable {
     private static final String TAG = "ZenModeController";
@@ -124,14 +126,14 @@
     }
 
     @Override
-    public void addCallback(Callback callback) {
+    public void addCallback(@NonNull Callback callback) {
         synchronized (mCallbacksLock) {
             mCallbacks.add(callback);
         }
     }
 
     @Override
-    public void removeCallback(Callback callback) {
+    public void removeCallback(@NonNull Callback callback) {
         synchronized (mCallbacksLock) {
             mCallbacks.remove(callback);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
index c0602762..bcfff60 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
@@ -29,11 +29,11 @@
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
 import com.android.systemui.assist.AssistManager;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.tv.micdisclosure.AudioRecordingDisclosureBar;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 
@@ -46,7 +46,7 @@
  * recording, discloses the responsible applications </li>
  * </ul>
  */
-@Singleton
+@SysUISingleton
 public class TvStatusBar extends SystemUI implements CommandQueue.Callbacks {
 
     private static final String ACTION_OPEN_TV_NOTIFICATIONS_PANEL =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioRecordingDisclosureBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioRecordingDisclosureBar.java
index e3eed35..a29db4d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioRecordingDisclosureBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioRecordingDisclosureBar.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.tv.micdisclosure;
 
+import static android.provider.DeviceConfig.NAMESPACE_PRIVACY;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
 import android.animation.Animator;
@@ -25,11 +26,8 @@
 import android.annotation.IntDef;
 import android.annotation.UiThread;
 import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.database.ContentObserver;
 import android.graphics.PixelFormat;
-import android.provider.Settings;
+import android.provider.DeviceConfig;
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Log;
@@ -46,8 +44,8 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
-import java.util.LinkedList;
-import java.util.Queue;
+import java.util.Collections;
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -65,9 +63,9 @@
     // CtsSystemUiHostTestCases:TvMicrophoneCaptureIndicatorTest
     private static final String LAYOUT_PARAMS_TITLE = "MicrophoneCaptureIndicator";
 
-    private static final String ENABLE_FLAG = "sysui_mic_disclosure_enable";
-    private static final String EXEMPT_PACKAGES_LIST = "sysui_mic_disclosure_exempt";
-    private static final String FORCED_PACKAGES_LIST = "sysui_mic_disclosure_forced";
+    private static final String ENABLED_FLAG = "mic_disclosure_enabled";
+    private static final String EXEMPT_PACKAGES_LIST = "mic_disclosure_exempt_packages";
+    private static final String FORCED_PACKAGES_LIST = "mic_disclosure_forced_packages";
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"STATE_"}, value = {
@@ -75,9 +73,6 @@
             STATE_NOT_SHOWN,
             STATE_APPEARING,
             STATE_SHOWN,
-            STATE_MINIMIZING,
-            STATE_MINIMIZED,
-            STATE_MAXIMIZING,
             STATE_DISAPPEARING
     })
     public @interface State {}
@@ -86,16 +81,12 @@
     private static final int STATE_NOT_SHOWN = 0;
     private static final int STATE_APPEARING = 1;
     private static final int STATE_SHOWN = 2;
-    private static final int STATE_MINIMIZING = 3;
-    private static final int STATE_MINIMIZED = 4;
-    private static final int STATE_MAXIMIZING = 5;
-    private static final int STATE_DISAPPEARING = 6;
+    private static final int STATE_DISAPPEARING = 3;
 
     private static final int ANIMATION_DURATION = 600;
-    private static final int MAXIMIZED_DURATION = 3000;
 
     private final Context mContext;
-    private boolean mIsEnabledInSettings;
+    private boolean mIsEnabled;
 
     private View mIndicatorView;
     private View mIconTextsContainer;
@@ -114,53 +105,38 @@
      */
     private AudioActivityObserver[] mAudioActivityObservers;
     /**
-     * Whether the indicator should expand and show the recording application's label.
-     * If disabled ({@code false}) the "minimized" ({@link #STATE_MINIMIZED}) indicator would appear
-     * on the screen whenever an application is recording, but will not reveal to the user what
-     * application this is.
-     */
-    private final boolean mRevealRecordingPackages;
-    /**
-     * Set of applications that we've notified the user about since the indicator came up. Meaning
-     * that if an application is in this list then at some point since the indicator came up, it
-     * was expanded showing this application's title.
-     * Used not to notify the user about the same application again while the indicator is shown.
-     * We empty this set every time the indicator goes off the screen (we always call {@code
-     * mSessionNotifiedPackages.clear()} before calling {@link #hide()}).
-     */
-    private final Set<String> mSessionNotifiedPackages = new ArraySet<>();
-    /**
-     * If an application starts recording while the TV indicator is neither in {@link
-     * #STATE_NOT_SHOWN} nor in {@link #STATE_MINIMIZED}, then we add the application's package
-     * name to the queue, from which we take packages names one by one to disclose the
-     * corresponding applications' titles to the user, whenever the indicator eventually comes to
-     * one of the two aforementioned states.
-     */
-    private final Queue<String> mPendingNotificationPackages = new LinkedList<>();
-    /**
      * Set of applications for which we make an exception and do not show the indicator. This gets
      * populated once - in {@link #AudioRecordingDisclosureBar(Context)}.
      */
-    private final Set<String> mExemptPackages;
+    private final Set<String> mExemptPackages = new ArraySet<>();
 
     public AudioRecordingDisclosureBar(Context context) {
         mContext = context;
 
-        // Loading configs
-        mRevealRecordingPackages = mContext.getResources().getBoolean(
-                R.bool.audio_recording_disclosure_reveal_packages);
-        mExemptPackages = new ArraySet<>(
-                Arrays.asList(mContext.getResources().getStringArray(
-                        R.array.audio_recording_disclosure_exempt_apps)));
-        mExemptPackages.addAll(Arrays.asList(getGlobalStringArray(EXEMPT_PACKAGES_LIST)));
-        mExemptPackages.removeAll(Arrays.asList(getGlobalStringArray(FORCED_PACKAGES_LIST)));
+        // Load configs
+        reloadExemptPackages();
 
-        // Check setting, and start if enabled
-        mIsEnabledInSettings = checkIfEnabledInSettings();
-        registerSettingsObserver();
-        if (mIsEnabledInSettings) {
+        mIsEnabled = DeviceConfig.getBoolean(NAMESPACE_PRIVACY, ENABLED_FLAG, true);
+        // Start if enabled
+        if (mIsEnabled) {
             start();
         }
+
+        // Set up a config change listener
+        DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_PRIVACY, mContext.getMainExecutor(),
+                mConfigChangeListener);
+    }
+
+    private void reloadExemptPackages() {
+        mExemptPackages.clear();
+        mExemptPackages.addAll(Arrays.asList(mContext.getResources().getStringArray(
+                R.array.audio_recording_disclosure_exempt_apps)));
+        mExemptPackages.addAll(
+                splitByComma(
+                        DeviceConfig.getString(NAMESPACE_PRIVACY, EXEMPT_PACKAGES_LIST, null)));
+        mExemptPackages.removeAll(
+                splitByComma(
+                        DeviceConfig.getString(NAMESPACE_PRIVACY, FORCED_PACKAGES_LIST, null)));
     }
 
     @UiThread
@@ -197,10 +173,6 @@
         if (mState != STATE_NOT_SHOWN) {
             removeIndicatorView();
         }
-
-        // Clean up the state.
-        mSessionNotifiedPackages.clear();
-        mPendingNotificationPackages.clear();
     }
 
     @UiThread
@@ -218,68 +190,29 @@
         }
 
         if (active) {
-            showIndicatorForPackageIfNeeded(packageName);
+            showIfNotShown();
         } else {
             hideIndicatorIfNeeded();
         }
     }
 
     @UiThread
-    private void showIndicatorForPackageIfNeeded(String packageName) {
-        if (DEBUG) Log.d(TAG, "showIndicatorForPackageIfNeeded, packageName=" + packageName);
-        if (!mSessionNotifiedPackages.add(packageName)) {
-            // We've already notified user about this app, no need to do it again.
-            if (DEBUG) Log.d(TAG, "   - already notified");
-            return;
-        }
-
-        switch (mState) {
-            case STATE_NOT_SHOWN:
-                show(packageName);
-                break;
-
-            case STATE_MINIMIZED:
-                if (mRevealRecordingPackages) {
-                    expand(packageName);
-                }
-                break;
-
-            case STATE_DISAPPEARING:
-            case STATE_APPEARING:
-            case STATE_MAXIMIZING:
-            case STATE_SHOWN:
-            case STATE_MINIMIZING:
-                // Currently animating or expanded. Thus add to the pending notifications, and it
-                // will be picked up once the indicator comes to the STATE_MINIMIZED.
-                mPendingNotificationPackages.add(packageName);
-                break;
-        }
-    }
-
-    @UiThread
     private void hideIndicatorIfNeeded() {
-        // If not MINIMIZED, will check whether the indicator should be hidden when the indicator
-        // comes to the STATE_MINIMIZED eventually.
-        if (mState != STATE_MINIMIZED) return;
+        // If not STATE_APPEARING, will check whether the indicator should be hidden when the
+        // indicator comes to the STATE_SHOWN.
+        // If STATE_DISAPPEARING or STATE_SHOWN - nothing else for us to do here.
+        if (mState != STATE_SHOWN) return;
 
-        // If is in the STATE_MINIMIZED, but there are other active recorders - simply ignore.
-        for (int index = mAudioActivityObservers.length - 1; index >= 0; index--) {
-            for (String activePackage : mAudioActivityObservers[index].getActivePackages()) {
-                if (mExemptPackages.contains(activePackage)) continue;
-                return;
-            }
+        // If is in the STATE_SHOWN and there are no active recorders - hide.
+        if (!hasActiveRecorders()) {
+            hide();
         }
-
-        // Clear the state and hide the indicator.
-        mSessionNotifiedPackages.clear();
-        hide();
     }
 
     @UiThread
-    private void show(String packageName) {
-        if (DEBUG) {
-            Log.d(TAG, "Showing indicator");
-        }
+    private void showIfNotShown() {
+        if (mState != STATE_NOT_SHOWN) return;
+        if (DEBUG) Log.d(TAG, "Showing indicator");
 
         mIsLtr = mContext.getResources().getConfiguration().getLayoutDirection()
                 == View.LAYOUT_DIRECTION_LTR;
@@ -295,30 +228,14 @@
         mTextView = mTextsContainers.findViewById(R.id.text);
         mBgEnd = mIndicatorView.findViewById(R.id.bg_end);
 
-        // Set up the notification text
-        if (mRevealRecordingPackages) {
-            // Swap background drawables depending on layout directions (both drawables have rounded
-            // corners only on one side)
-            if (mIsLtr) {
-                mBgEnd.setBackgroundResource(R.drawable.tv_rect_dark_right_rounded);
-                mIconContainerBg.setBackgroundResource(R.drawable.tv_rect_dark_left_rounded);
-            } else {
-                mBgEnd.setBackgroundResource(R.drawable.tv_rect_dark_left_rounded);
-                mIconContainerBg.setBackgroundResource(R.drawable.tv_rect_dark_right_rounded);
-            }
-
-            final String label = getApplicationLabel(packageName);
-            mTextView.setText(mContext.getString(R.string.app_accessed_mic, label));
-        } else {
-            mTextsContainers.setVisibility(View.GONE);
-            mIconContainerBg.setVisibility(View.GONE);
-            mTextView.setVisibility(View.GONE);
-            mBgEnd.setVisibility(View.GONE);
-            mTextsContainers = null;
-            mIconContainerBg = null;
-            mTextView = null;
-            mBgEnd = null;
-        }
+        mTextsContainers.setVisibility(View.GONE);
+        mIconContainerBg.setVisibility(View.GONE);
+        mTextView.setVisibility(View.GONE);
+        mBgEnd.setVisibility(View.GONE);
+        mTextsContainers = null;
+        mIconContainerBg = null;
+        mTextView = null;
+        mBgEnd = null;
 
         // Initially change the visibility to INVISIBLE, wait until and receives the size and
         // then animate it moving from "off" the screen correctly
@@ -353,9 +270,7 @@
                                             @Override
                                             public void onAnimationStart(Animator animation,
                                                     boolean isReverse) {
-                                                if (mState == STATE_STOPPED) {
-                                                    return;
-                                                }
+                                                if (mState == STATE_STOPPED) return;
 
                                                 // Indicator is INVISIBLE at the moment, change it.
                                                 mIndicatorView.setVisibility(View.VISIBLE);
@@ -363,11 +278,7 @@
 
                                             @Override
                                             public void onAnimationEnd(Animator animation) {
-                                                if (mRevealRecordingPackages) {
-                                                    onExpanded();
-                                                } else {
-                                                    onMinimized();
-                                                }
+                                                onAppeared();
                                             }
                                         });
                                 set.start();
@@ -391,60 +302,9 @@
     }
 
     @UiThread
-    private void expand(String packageName) {
-        assertRevealingRecordingPackages();
-
-        final String label = getApplicationLabel(packageName);
-        mTextView.setText(mContext.getString(R.string.app_accessed_mic, label));
-
-        final AnimatorSet set = new AnimatorSet();
-        set.playTogether(
-                ObjectAnimator.ofFloat(mIconTextsContainer, View.TRANSLATION_X, 0),
-                ObjectAnimator.ofFloat(mIconContainerBg, View.ALPHA, 1f),
-                ObjectAnimator.ofFloat(mTextsContainers, View.ALPHA, 1f),
-                ObjectAnimator.ofFloat(mBgEnd, View.ALPHA, 1f));
-        set.setDuration(ANIMATION_DURATION);
-        set.addListener(
-                new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        onExpanded();
-                    }
-                });
-        set.start();
-
-        mState = STATE_MAXIMIZING;
-    }
-
-    @UiThread
-    private void minimize() {
-        assertRevealingRecordingPackages();
-
-        final int targetOffset = (mIsLtr ? 1 : -1) * mTextsContainers.getWidth();
-        final AnimatorSet set = new AnimatorSet();
-        set.playTogether(
-                ObjectAnimator.ofFloat(mIconTextsContainer, View.TRANSLATION_X, targetOffset),
-                ObjectAnimator.ofFloat(mIconContainerBg, View.ALPHA, 0f),
-                ObjectAnimator.ofFloat(mTextsContainers, View.ALPHA, 0f),
-                ObjectAnimator.ofFloat(mBgEnd, View.ALPHA, 0f));
-        set.setDuration(ANIMATION_DURATION);
-        set.addListener(
-                new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        onMinimized();
-                    }
-                });
-        set.start();
-
-        mState = STATE_MINIMIZING;
-    }
-
-    @UiThread
     private void hide() {
-        if (DEBUG) {
-            Log.d(TAG, "Hide indicator");
-        }
+        if (DEBUG) Log.d(TAG, "Hide indicator");
+
         final int targetOffset = (mIsLtr ? 1 : -1) * (mIndicatorView.getWidth()
                 - (int) mIconTextsContainer.getTranslationX());
         final AnimatorSet set = new AnimatorSet();
@@ -464,53 +324,39 @@
         mState = STATE_DISAPPEARING;
     }
 
-    @UiThread
-    private void onExpanded() {
-        if (mState == STATE_STOPPED) {
-            return;
-        }
 
-        assertRevealingRecordingPackages();
+    @UiThread
+    private void onAppeared() {
+        if (mState == STATE_STOPPED) return;
 
         mState = STATE_SHOWN;
 
-        mIndicatorView.postDelayed(this::minimize, MAXIMIZED_DURATION);
-    }
-
-    @UiThread
-    private void onMinimized() {
-        if (mState == STATE_STOPPED) {
-            return;
-        }
-
-        mState = STATE_MINIMIZED;
-
-        if (mRevealRecordingPackages) {
-            if (!mPendingNotificationPackages.isEmpty()) {
-                // There is a new application that started recording, tell the user about it.
-                expand(mPendingNotificationPackages.poll());
-            } else {
-                hideIndicatorIfNeeded();
-            }
-        }
+        hideIndicatorIfNeeded();
     }
 
     @UiThread
     private void onHidden() {
-        if (mState == STATE_STOPPED) {
-            return;
-        }
+        if (mState == STATE_STOPPED) return;
 
         removeIndicatorView();
         mState = STATE_NOT_SHOWN;
 
-        // Check if anybody started recording while we were in STATE_DISAPPEARING
-        if (!mPendingNotificationPackages.isEmpty()) {
-            // There is a new application that started recording, tell the user about it.
-            show(mPendingNotificationPackages.poll());
+        if (hasActiveRecorders()) {
+            // Got new recorders, show again.
+            showIfNotShown();
         }
     }
 
+    private boolean hasActiveRecorders() {
+        for (int index = mAudioActivityObservers.length - 1; index >= 0; index--) {
+            for (String activePackage : mAudioActivityObservers[index].getActivePackages()) {
+                if (mExemptPackages.contains(activePackage)) continue;
+                return true;
+            }
+        }
+        return false;
+    }
+
     private void removeIndicatorView() {
         final WindowManager windowManager = (WindowManager) mContext.getSystemService(
                 Context.WINDOW_SERVICE);
@@ -525,57 +371,26 @@
         mBgEnd = null;
     }
 
-    private String[] getGlobalStringArray(String setting) {
-        String result = Settings.Global.getString(mContext.getContentResolver(), setting);
-        return TextUtils.isEmpty(result) ? new String[0] : result.split(",");
+    private static List<String> splitByComma(String string) {
+        return TextUtils.isEmpty(string) ? Collections.emptyList() : Arrays.asList(
+                string.split(","));
     }
 
-    private String getApplicationLabel(String packageName) {
-        assertRevealingRecordingPackages();
+    private final DeviceConfig.OnPropertiesChangedListener mConfigChangeListener =
+            new DeviceConfig.OnPropertiesChangedListener() {
+                @Override
+                public void onPropertiesChanged(DeviceConfig.Properties properties) {
+                    reloadExemptPackages();
 
-        final PackageManager pm = mContext.getPackageManager();
-        final ApplicationInfo appInfo;
-        try {
-            appInfo = pm.getApplicationInfo(packageName, 0);
-        } catch (PackageManager.NameNotFoundException e) {
-            return packageName;
-        }
-        return pm.getApplicationLabel(appInfo).toString();
-    }
-
-    private void assertRevealingRecordingPackages() {
-        if (!mRevealRecordingPackages) {
-            Log.e(TAG, "Not revealing recording packages",
-                    DEBUG ? new RuntimeException("Should not be called") : null);
-        }
-    }
-
-    private boolean checkIfEnabledInSettings() {
-        // 0 = disabled, everything else = enabled. Enabled by default.
-        return Settings.Global.getInt(mContext.getContentResolver(),
-                ENABLE_FLAG, 1) != 0;
-    }
-
-    private void registerSettingsObserver() {
-        final ContentObserver contentObserver = new ContentObserver(
-                mContext.getMainThreadHandler()) {
-            @Override
-            public void onChange(boolean selfChange) {
-                if (mIsEnabledInSettings == checkIfEnabledInSettings()) {
-                    // Nothing changed as we know it - ignore.
-                    return;
+                    // Check if was enabled/disabled
+                    if (mIsEnabled != properties.getBoolean(ENABLED_FLAG, true)) {
+                        mIsEnabled = !mIsEnabled;
+                        if (mIsEnabled) {
+                            start();
+                        } else {
+                            stop();
+                        }
+                    }
                 }
-
-                // Things changed: flip the flag.
-                mIsEnabledInSettings = !mIsEnabledInSettings;
-                if (mIsEnabledInSettings) {
-                    start();
-                } else {
-                    stop();
-                }
-            }
-        };
-        mContext.getContentResolver().registerContentObserver(
-                Settings.Global.getUriFor(ENABLE_FLAG), false, contentObserver);
-    }
+            };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index f31f8eb..132e092 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -36,6 +36,7 @@
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 
 import com.google.android.collect.Sets;
@@ -48,7 +49,6 @@
 import java.util.Set;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Controls the application of theme overlays across the system for all users.
@@ -59,7 +59,7 @@
  * - Observing work profile changes and applying overlays from the primary user to their
  * associated work profiles
  */
-@Singleton
+@SysUISingleton
 public class ThemeOverlayController extends SystemUI {
     private static final String TAG = "ThemeOverlayController";
     private static final boolean DEBUG = false;
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
index 9b465ae..a220373 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
@@ -33,17 +33,17 @@
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.SystemUI;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.CommandQueue;
 
 import java.util.Objects;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Controls display of text toasts.
  */
-@Singleton
+@SysUISingleton
 public class ToastUI extends SystemUI implements CommandQueue.Callbacks {
     private static final String TAG = "ToastUI";
 
diff --git a/packages/SystemUI/src/com/android/systemui/tracing/ProtoTracer.java b/packages/SystemUI/src/com/android/systemui/tracing/ProtoTracer.java
index 25ae098..8a8f92b 100644
--- a/packages/SystemUI/src/com/android/systemui/tracing/ProtoTracer.java
+++ b/packages/SystemUI/src/com/android/systemui/tracing/ProtoTracer.java
@@ -25,6 +25,7 @@
 import androidx.annotation.NonNull;
 
 import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.shared.tracing.FrameProtoTracer;
 import com.android.systemui.shared.tracing.FrameProtoTracer.ProtoTraceParams;
@@ -42,12 +43,11 @@
 import java.util.Queue;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Controller for coordinating winscope proto tracing.
  */
-@Singleton
+@SysUISingleton
 public class ProtoTracer implements Dumpable, ProtoTraceParams<MessageNano, SystemUiTraceFileProto,
         SystemUiTraceEntryProto, SystemUiTraceProto> {
 
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
index 49ada1a..1f44434 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
@@ -15,14 +15,10 @@
  */
 package com.android.systemui.tuner;
 
-import android.content.ContentResolver;
+import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.Intent;
-import android.database.ContentObserver;
 import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.provider.Settings;
 import android.view.MenuItem;
 
 import androidx.preference.Preference;
@@ -33,13 +29,13 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.DemoMode;
 import com.android.systemui.R;
+import com.android.systemui.demomode.DemoMode;
+import com.android.systemui.demomode.DemoModeAvailabilityTracker;
+import com.android.systemui.demomode.DemoModeController;
 
 public class DemoModeFragment extends PreferenceFragment implements OnPreferenceChangeListener {
 
-    private static final String DEMO_MODE_ON = "sysui_tuner_demo_on";
-
     private static final String[] STATUS_ICONS = {
         "volume",
         "bluetooth",
@@ -57,6 +53,17 @@
     private SwitchPreference mEnabledSwitch;
     private SwitchPreference mOnSwitch;
 
+    private DemoModeController mDemoModeController;
+    private Tracker mDemoModeTracker;
+
+    // We are the only ones who ever call this constructor, so don't worry about the warning
+    @SuppressLint("ValidFragment")
+    public DemoModeFragment(DemoModeController demoModeController) {
+        super();
+        mDemoModeController = demoModeController;
+    }
+
+
     @Override
     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
         Context context = getContext();
@@ -73,13 +80,11 @@
         screen.addPreference(mOnSwitch);
         setPreferenceScreen(screen);
 
+        mDemoModeTracker = new Tracker(context);
+        mDemoModeTracker.startTracking();
         updateDemoModeEnabled();
         updateDemoModeOn();
-        ContentResolver contentResolver = getContext().getContentResolver();
-        contentResolver.registerContentObserver(Settings.Global.getUriFor(
-                DemoMode.DEMO_MODE_ALLOWED), false, mDemoModeObserver);
-        contentResolver.registerContentObserver(Settings.Global.getUriFor(DEMO_MODE_ON), false,
-                mDemoModeObserver);
+
         setHasOptionsMenu(true);
     }
 
@@ -107,21 +112,17 @@
 
     @Override
     public void onDestroy() {
-        getContext().getContentResolver().unregisterContentObserver(mDemoModeObserver);
+        mDemoModeTracker.stopTracking();
         super.onDestroy();
     }
 
     private void updateDemoModeEnabled() {
-        boolean enabled = Settings.Global.getInt(getContext().getContentResolver(),
-                DemoMode.DEMO_MODE_ALLOWED, 0) != 0;
-        mEnabledSwitch.setChecked(enabled);
-        mOnSwitch.setEnabled(enabled);
+        mEnabledSwitch.setChecked(mDemoModeTracker.isDemoModeAvailable());
+        mOnSwitch.setEnabled(mDemoModeTracker.isDemoModeAvailable());
     }
 
     private void updateDemoModeOn() {
-        boolean enabled = Settings.Global.getInt(getContext().getContentResolver(),
-                DEMO_MODE_ON, 0) != 0;
-        mOnSwitch.setChecked(enabled);
+        mOnSwitch.setChecked(mDemoModeTracker.isInDemoMode());
     }
 
     @Override
@@ -134,7 +135,7 @@
                 stopDemoMode();
             }
             MetricsLogger.action(getContext(), MetricsEvent.TUNER_DEMO_MODE_ENABLED, enabled);
-            setGlobal(DemoMode.DEMO_MODE_ALLOWED, enabled ? 1 : 0);
+            mDemoModeController.requestSetDemoModeAllowed(enabled);
         } else if (preference == mOnSwitch) {
             MetricsLogger.action(getContext(), MetricsEvent.TUNER_DEMO_MODE_ON, enabled);
             if (enabled) {
@@ -151,11 +152,11 @@
     private void startDemoMode() {
         Intent intent = new Intent(DemoMode.ACTION_DEMO);
 
-        intent.putExtra(DemoMode.EXTRA_COMMAND, DemoMode.COMMAND_ENTER);
-        getContext().sendBroadcast(intent);
+        mDemoModeController.requestStartDemoMode();
 
         intent.putExtra(DemoMode.EXTRA_COMMAND, DemoMode.COMMAND_CLOCK);
 
+        //TODO: everything below should move to DemoModeController, or some `initialState` command
         String demoTime = "1010"; // 10:10, a classic choice of horologists
         try {
             String[] versionParts = android.os.Build.VERSION.RELEASE_OR_CODENAME.split("\\.");
@@ -194,25 +195,31 @@
         intent.putExtra("visible", "false");
         getContext().sendBroadcast(intent);
 
-        setGlobal(DEMO_MODE_ON, 1);
     }
 
     private void stopDemoMode() {
-        Intent intent = new Intent(DemoMode.ACTION_DEMO);
-        intent.putExtra(DemoMode.EXTRA_COMMAND, DemoMode.COMMAND_EXIT);
-        getContext().sendBroadcast(intent);
-        setGlobal(DEMO_MODE_ON, 0);
+        mDemoModeController.requestFinishDemoMode();
     }
 
-    private void setGlobal(String key, int value) {
-        Settings.Global.putInt(getContext().getContentResolver(), key, value);
-    }
+    private class Tracker extends DemoModeAvailabilityTracker {
+        Tracker(Context context) {
+            super(context);
+        }
 
-    private final ContentObserver mDemoModeObserver =
-            new ContentObserver(new Handler(Looper.getMainLooper())) {
-        public void onChange(boolean selfChange) {
+        @Override
+        public void onDemoModeAvailabilityChanged() {
             updateDemoModeEnabled();
             updateDemoModeOn();
-        };
+        }
+
+        @Override
+        public void onDemoModeStarted() {
+            updateDemoModeOn();
+        }
+
+        @Override
+        public void onDemoModeFinished() {
+            updateDemoModeOn();
+        }
     };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java b/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java
index fa531b5..87d2063 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java
@@ -14,18 +14,18 @@
 
 package com.android.systemui.tuner;
 
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.KEY;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.KEY_CODE_END;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.KEY_CODE_START;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.KEY_IMAGE_DELIM;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.MENU_IME_ROTATE;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.NAVSPACE;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.NAV_BAR_LEFT;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.NAV_BAR_RIGHT;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.NAV_BAR_VIEWS;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.extractButton;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.extractImage;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.extractKeycode;
+import static com.android.systemui.navigationbar.NavigationBarInflaterView.KEY;
+import static com.android.systemui.navigationbar.NavigationBarInflaterView.KEY_CODE_END;
+import static com.android.systemui.navigationbar.NavigationBarInflaterView.KEY_CODE_START;
+import static com.android.systemui.navigationbar.NavigationBarInflaterView.KEY_IMAGE_DELIM;
+import static com.android.systemui.navigationbar.NavigationBarInflaterView.MENU_IME_ROTATE;
+import static com.android.systemui.navigationbar.NavigationBarInflaterView.NAVSPACE;
+import static com.android.systemui.navigationbar.NavigationBarInflaterView.NAV_BAR_LEFT;
+import static com.android.systemui.navigationbar.NavigationBarInflaterView.NAV_BAR_RIGHT;
+import static com.android.systemui.navigationbar.NavigationBarInflaterView.NAV_BAR_VIEWS;
+import static com.android.systemui.navigationbar.NavigationBarInflaterView.extractButton;
+import static com.android.systemui.navigationbar.NavigationBarInflaterView.extractImage;
+import static com.android.systemui.navigationbar.NavigationBarInflaterView.extractKeycode;
 
 import android.annotation.Nullable;
 import android.app.AlertDialog;
@@ -51,6 +51,7 @@
 
 import java.util.ArrayList;
 
+@Deprecated
 public class NavBarTuner extends TunerPreferenceFragment {
 
     private static final String LAYOUT = "layout";
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunablePadding.java b/packages/SystemUI/src/com/android/systemui/tuner/TunablePadding.java
index 8f3a8f6..d54c07c 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunablePadding.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunablePadding.java
@@ -19,10 +19,10 @@
 import android.view.WindowManager;
 
 import com.android.systemui.Dependency;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.tuner.TunerService.Tunable;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Version of Space that can be resized by a tunable setting.
@@ -77,7 +77,7 @@
     /**
      * Exists for easy injecting in tests.
      */
-    @Singleton
+    @SysUISingleton
     public static class TunablePaddingService {
 
         private final TunerService mTunerService;
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
index 453c2f7..78341ed 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
@@ -31,6 +31,7 @@
 
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
+import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.fragments.FragmentService;
 
 import javax.inject.Inject;
@@ -41,9 +42,12 @@
 
     private static final String TAG_TUNER = "tuner";
 
+    private final DemoModeController mDemoModeController;
+
     @Inject
-    TunerActivity() {
+    TunerActivity(DemoModeController demoModeController) {
         super();
+        mDemoModeController = demoModeController;
     }
 
     protected void onCreate(Bundle savedInstanceState) {
@@ -61,7 +65,8 @@
             final String action = getIntent().getAction();
             boolean showDemoMode = action != null && action.equals(
                     "com.android.settings.action.DEMO_MODE");
-            final PreferenceFragment fragment = showDemoMode ? new DemoModeFragment()
+            final PreferenceFragment fragment = showDemoMode
+                    ? new DemoModeFragment(mDemoModeController)
                     : new TunerFragment();
             getFragmentManager().beginTransaction().replace(R.id.content_frame,
                     fragment, TAG_TUNER).commit();
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
index 644f758..d9727a7 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
@@ -18,7 +18,6 @@
 import android.app.ActivityManager;
 import android.content.ContentResolver;
 import android.content.Context;
-import android.content.Intent;
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
 import android.net.Uri;
@@ -33,9 +32,10 @@
 
 import com.android.internal.util.ArrayUtils;
 import com.android.systemui.DejankUtils;
-import com.android.systemui.DemoMode;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.qs.QSTileHost;
 import com.android.systemui.settings.CurrentUserTracker;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -46,14 +46,14 @@
 import java.util.concurrent.ConcurrentHashMap;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 
 /**
  */
-@Singleton
+@SysUISingleton
 public class TunerServiceImpl extends TunerService {
 
+    private static final String TAG = "TunerService";
     private static final String TUNER_VERSION = "sysui_tuner_version";
 
     private static final int CURRENT_TUNER_VERSION = 4;
@@ -63,7 +63,8 @@
     private static final String[] RESET_EXCEPTION_LIST = new String[] {
             QSTileHost.TILES_SETTING,
             Settings.Secure.DOZE_ALWAYS_ON,
-            Settings.Secure.MEDIA_CONTROLS_RESUME
+            Settings.Secure.MEDIA_CONTROLS_RESUME,
+            Secure.MEDIA_CONTROLS_RESUME_BLOCKED
     };
 
     private final Observer mObserver = new Observer();
@@ -76,6 +77,7 @@
     private final HashSet<Tunable> mTunables = LeakDetector.ENABLED ? new HashSet<>() : null;
     private final Context mContext;
     private final LeakDetector mLeakDetector;
+    private final DemoModeController mDemoModeController;
 
     private ContentResolver mContentResolver;
     private int mCurrentUser;
@@ -84,11 +86,16 @@
     /**
      */
     @Inject
-    public TunerServiceImpl(Context context, @Main Handler mainHandler,
-            LeakDetector leakDetector, BroadcastDispatcher broadcastDispatcher) {
+    public TunerServiceImpl(
+            Context context,
+            @Main Handler mainHandler,
+            LeakDetector leakDetector,
+            DemoModeController demoModeController,
+            BroadcastDispatcher broadcastDispatcher) {
         mContext = context;
         mContentResolver = mContext.getContentResolver();
         mLeakDetector = leakDetector;
+        mDemoModeController = demoModeController;
 
         for (UserInfo user : UserManager.get(mContext).getUsers()) {
             mCurrentUser = user.getUserHandle().getIdentifier();
@@ -244,12 +251,11 @@
     }
 
     public void clearAllFromUser(int user) {
-        // A couple special cases.
-        Settings.Global.putString(mContentResolver, DemoMode.DEMO_MODE_ALLOWED, null);
-        Intent intent = new Intent(DemoMode.ACTION_DEMO);
-        intent.putExtra(DemoMode.EXTRA_COMMAND, DemoMode.COMMAND_EXIT);
-        mContext.sendBroadcast(intent);
+        // Turn off demo mode
+        mDemoModeController.requestFinishDemoMode();
+        mDemoModeController.requestSetDemoModeAllowed(false);
 
+        // A couple special cases.
         for (String key : mTunableLookup.keySet()) {
             if (ArrayUtils.contains(RESET_EXCEPTION_LIST, key)) {
                 continue;
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvGlobalRootComponent.java b/packages/SystemUI/src/com/android/systemui/tv/TvGlobalRootComponent.java
new file mode 100644
index 0000000..37aac11
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvGlobalRootComponent.java
@@ -0,0 +1,41 @@
+/*
+ * 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.tv;
+
+import com.android.systemui.dagger.GlobalRootComponent;
+
+import javax.inject.Singleton;
+
+import dagger.Component;
+
+/**
+ * Root component for Dagger injection.
+ */
+@Singleton
+@Component(modules = {TvSysUIComponentModule.class})
+public interface TvGlobalRootComponent extends GlobalRootComponent {
+    /**
+     * Component Builder interface. This allows to bind Context instance in the component
+     */
+    @Component.Builder
+    interface Builder extends GlobalRootComponent.Builder {
+        TvGlobalRootComponent build();
+    }
+
+    @Override
+    TvSysUIComponent.Builder getSysUIComponent();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIRootComponent.java b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
similarity index 70%
rename from packages/SystemUI/src/com/android/systemui/tv/TvSystemUIRootComponent.java
rename to packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
index dce38c1..b7bc8c8 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIRootComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
@@ -19,22 +19,20 @@
 import com.android.systemui.dagger.DefaultComponentBinder;
 import com.android.systemui.dagger.DependencyBinder;
 import com.android.systemui.dagger.DependencyProvider;
+import com.android.systemui.dagger.SysUIComponent;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.SystemServicesModule;
 import com.android.systemui.dagger.SystemUIBinder;
-import com.android.systemui.dagger.SystemUIDefaultModule;
 import com.android.systemui.dagger.SystemUIModule;
-import com.android.systemui.dagger.SystemUIRootComponent;
 import com.android.systemui.onehanded.dagger.OneHandedModule;
 
-import javax.inject.Singleton;
-
-import dagger.Component;
+import dagger.Subcomponent;
 
 /**
- * Root component for Dagger injection.
+ * Dagger Subcomponent for Core SysUI.
  */
-@Singleton
-@Component(modules = {
+@SysUISingleton
+@Subcomponent(modules = {
         DefaultComponentBinder.class,
         DependencyProvider.class,
         DependencyBinder.class,
@@ -42,14 +40,15 @@
         SystemServicesModule.class,
         SystemUIBinder.class,
         SystemUIModule.class,
-        SystemUIDefaultModule.class,
+        TvSystemUIModule.class,
         TvSystemUIBinder.class})
-public interface TvSystemUIRootComponent extends SystemUIRootComponent {
+public interface TvSysUIComponent extends SysUIComponent {
+
     /**
-     * Component Builder interface. This allows to bind Context instance in the component
+     * Builder for a SysUIComponent.
      */
-    @Component.Builder
-    interface Builder extends SystemUIRootComponent.Builder {
-        TvSystemUIRootComponent build();
+    @Subcomponent.Builder
+    interface Builder extends SysUIComponent.Builder {
+        TvSysUIComponent build();
     }
 }
diff --git a/tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponentModule.java
similarity index 73%
copy from tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java
copy to packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponentModule.java
index 09ef472..334bb01 100644
--- a/tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponentModule.java
@@ -13,3 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
+package com.android.systemui.tv;
+
+import dagger.Module;
+
+/**
+ * Dagger module for including the WMComponent.
+ */
+@Module(subcomponents = {TvSysUIComponent.class})
+public abstract class TvSysUIComponentModule {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java
index be30a4a..9a44bf1 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java
@@ -16,7 +16,7 @@
 
 package com.android.systemui.tv;
 
-import com.android.systemui.dagger.SystemUIRootComponent;
+import com.android.systemui.dagger.GlobalRootComponent;
 import com.android.systemui.pip.tv.dagger.PipModule;
 
 import dagger.Binds;
@@ -25,5 +25,5 @@
 @Module(includes = {PipModule.class})
 interface TvSystemUIBinder {
     @Binds
-    SystemUIRootComponent bindSystemUIRootComponent(TvSystemUIRootComponent systemUIRootComponent);
+    GlobalRootComponent bindGlobalRootComponent(TvGlobalRootComponent globalRootComponent);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIFactory.java
index 7d3ec67..c99ad23 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIFactory.java
@@ -19,16 +19,16 @@
 import android.content.Context;
 
 import com.android.systemui.SystemUIFactory;
-import com.android.systemui.dagger.SystemUIRootComponent;
+import com.android.systemui.dagger.GlobalRootComponent;
 
 /**
- * TV variant {@link SystemUIFactory}, that substitutes default {@link SystemUIRootComponent} for
- * {@link TvSystemUIRootComponent}
+ * TV variant {@link SystemUIFactory}, that substitutes default {@link GlobalRootComponent} for
+ * {@link TvGlobalRootComponent}
  */
 public class TvSystemUIFactory extends SystemUIFactory {
     @Override
-    protected SystemUIRootComponent buildSystemUIRootComponent(Context context) {
-        return DaggerTvSystemUIRootComponent.builder()
+    protected GlobalRootComponent buildGlobalRootComponent(Context context) {
+        return DaggerTvGlobalRootComponent.builder()
                 .context(context)
                 .build();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
new file mode 100644
index 0000000..ca9cb08
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
@@ -0,0 +1,171 @@
+/*
+ * 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.tv;
+
+import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
+import static com.android.systemui.Dependency.LEAK_REPORT_EMAIL_NAME;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.PowerManager;
+
+import androidx.annotation.Nullable;
+
+import com.android.keyguard.KeyguardViewController;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.demomode.DemoModeController;
+import com.android.systemui.dock.DockManager;
+import com.android.systemui.dock.DockManagerImpl;
+import com.android.systemui.doze.DozeHost;
+import com.android.systemui.plugins.qs.QSFactory;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.power.EnhancedEstimates;
+import com.android.systemui.power.EnhancedEstimatesImpl;
+import com.android.systemui.qs.dagger.QSModule;
+import com.android.systemui.qs.tileimpl.QSFactoryImpl;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.RecentsImplementation;
+import com.android.systemui.stackdivider.DividerModule;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.phone.DozeServiceHost;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowControllerImpl;
+import com.android.systemui.statusbar.phone.ShadeController;
+import com.android.systemui.statusbar.phone.ShadeControllerImpl;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.BatteryControllerImpl;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedControllerImpl;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.wmshell.WMShellModule;
+
+import javax.inject.Named;
+
+import dagger.Binds;
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * A dagger module for injecting default implementations of components of System UI that may be
+ * overridden by the System UI implementation.
+ */
+@Module(includes = {
+            DividerModule.class,
+            QSModule.class,
+            WMShellModule.class
+        },
+        subcomponents = {
+        })
+public abstract class TvSystemUIModule {
+
+    @SysUISingleton
+    @Provides
+    @Named(LEAK_REPORT_EMAIL_NAME)
+    @Nullable
+    static String provideLeakReportEmail() {
+        return null;
+    }
+
+    @Binds
+    abstract EnhancedEstimates bindEnhancedEstimates(EnhancedEstimatesImpl enhancedEstimates);
+
+    @Binds
+    abstract NotificationLockscreenUserManager bindNotificationLockscreenUserManager(
+            NotificationLockscreenUserManagerImpl notificationLockscreenUserManager);
+
+    @Provides
+    @SysUISingleton
+    static BatteryController provideBatteryController(Context context,
+            EnhancedEstimates enhancedEstimates, PowerManager powerManager,
+            BroadcastDispatcher broadcastDispatcher, DemoModeController demoModeController,
+            @Main Handler mainHandler, @Background Handler bgHandler) {
+        BatteryController bC = new BatteryControllerImpl(context, enhancedEstimates, powerManager,
+                broadcastDispatcher, demoModeController, mainHandler, bgHandler);
+        bC.init();
+        return bC;
+    }
+
+    @Binds
+    @SysUISingleton
+    abstract QSFactory bindQSFactory(QSFactoryImpl qsFactoryImpl);
+
+    @Binds
+    abstract DockManager bindDockManager(DockManagerImpl dockManager);
+
+    @Binds
+    abstract NotificationEntryManager.KeyguardEnvironment bindKeyguardEnvironment(
+            KeyguardEnvironmentImpl keyguardEnvironment);
+
+    @Binds
+    abstract ShadeController provideShadeController(ShadeControllerImpl shadeController);
+
+    @SysUISingleton
+    @Provides
+    @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME)
+    static boolean provideAllowNotificationLongPress() {
+        return true;
+    }
+
+    @SysUISingleton
+    @Provides
+    static HeadsUpManagerPhone provideHeadsUpManagerPhone(
+            Context context,
+            StatusBarStateController statusBarStateController,
+            KeyguardBypassController bypassController,
+            NotificationGroupManager groupManager,
+            ConfigurationController configurationController) {
+        return new HeadsUpManagerPhone(context, statusBarStateController, bypassController,
+                groupManager, configurationController);
+    }
+
+    @Binds
+    abstract HeadsUpManager bindHeadsUpManagerPhone(HeadsUpManagerPhone headsUpManagerPhone);
+
+    @Provides
+    @SysUISingleton
+    static Recents provideRecents(Context context, RecentsImplementation recentsImplementation,
+            CommandQueue commandQueue) {
+        return new Recents(context, recentsImplementation, commandQueue);
+    }
+
+    @Binds
+    abstract DeviceProvisionedController bindDeviceProvisionedController(
+            DeviceProvisionedControllerImpl deviceProvisionedController);
+
+    @Binds
+    abstract KeyguardViewController bindKeyguardViewController(
+            StatusBarKeyguardViewManager statusBarKeyguardViewManager);
+
+    @Binds
+    abstract NotificationShadeWindowController bindNotificationShadeController(
+            NotificationShadeWindowControllerImpl notificationShadeWindowController);
+
+    @Binds
+    abstract DozeHost provideDozeHost(DozeServiceHost dozeServiceHost);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt
index 242f7cd..f22f59b 100644
--- a/packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt
@@ -2,10 +2,10 @@
 
 import android.graphics.Rect
 import android.util.Log
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.util.FloatingContentCoordinator.FloatingContent
-import java.util.HashMap
+import java.util.*
 import javax.inject.Inject
-import javax.inject.Singleton
 
 /** Tag for debug logging. */
 private const val TAG = "FloatingCoordinator"
@@ -20,7 +20,7 @@
  * other content out of the way. [onContentRemoved] should be called when the content is removed or
  * no longer visible.
  */
-@Singleton
+@SysUISingleton
 class FloatingContentCoordinator @Inject constructor() {
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
index d16bedc..d278905 100644
--- a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
+++ b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
@@ -23,16 +23,14 @@
 import android.view.LayoutInflater;
 import android.view.View;
 
-import com.android.keyguard.KeyguardClockSwitch;
 import com.android.keyguard.KeyguardMessageArea;
 import com.android.keyguard.KeyguardSliceView;
-import com.android.systemui.dagger.SystemUIRootComponent;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.qs.QSFooterImpl;
 import com.android.systemui.qs.QSPanel;
 import com.android.systemui.qs.QuickQSPanel;
 import com.android.systemui.qs.QuickStatusBarHeader;
 import com.android.systemui.qs.customize.QSCustomizer;
-import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 
 import java.lang.reflect.InvocationTargetException;
@@ -41,38 +39,28 @@
 
 import javax.inject.Inject;
 import javax.inject.Named;
-import javax.inject.Singleton;
 
-import dagger.Module;
-import dagger.Provides;
+import dagger.BindsInstance;
 import dagger.Subcomponent;
 
 /**
  * Manages inflation that requires dagger injection.
  * See docs/dagger.md for details.
  */
-@Singleton
+@SysUISingleton
 public class InjectionInflationController {
 
     public static final String VIEW_CONTEXT = "view_context";
-    private final ViewCreator mViewCreator;
     private final ArrayMap<String, Method> mInjectionMap = new ArrayMap<>();
     private final LayoutInflater.Factory2 mFactory = new InjectionFactory();
+    private final ViewInstanceCreator.Factory mViewInstanceCreatorFactory;
 
     @Inject
-    public InjectionInflationController(SystemUIRootComponent rootComponent) {
-        mViewCreator = rootComponent.createViewCreator();
+    public InjectionInflationController(ViewInstanceCreator.Factory viewInstanceCreatorFactory) {
+        mViewInstanceCreatorFactory = viewInstanceCreatorFactory;
         initInjectionMap();
     }
 
-    ArrayMap<String, Method> getInjectionMap() {
-        return mInjectionMap;
-    }
-
-    ViewCreator getFragmentCreator() {
-        return mViewCreator;
-    }
-
     /**
      * Wraps a {@link LayoutInflater} to support creating dagger injected views.
      * See docs/dagger.md for details.
@@ -93,25 +81,19 @@
     }
 
     /**
-     * The subcomponent of dagger that holds all views that need injection.
+     * Subcomponent that actually creates injected views.
      */
     @Subcomponent
-    public interface ViewCreator {
-        /**
-         * Creates another subcomponent to actually generate the view.
-         */
-        ViewInstanceCreator createInstanceCreator(ViewAttributeProvider attributeProvider);
-    }
-
-    /**
-     * Secondary sub-component that actually creates the views.
-     *
-     * Having two subcomponents lets us hide the complexity of providing the named context
-     * and AttributeSet from the SystemUIRootComponent, instead we have one subcomponent that
-     * creates a new ViewInstanceCreator any time we need to inflate a view.
-     */
-    @Subcomponent(modules = ViewAttributeProvider.class)
     public interface ViewInstanceCreator {
+
+        /** Factory for creating a ViewInstanceCreator. */
+        @Subcomponent.Factory
+        interface Factory {
+            ViewInstanceCreator build(
+                    @BindsInstance @Named(VIEW_CONTEXT) Context context,
+                    @BindsInstance AttributeSet attributeSet);
+        }
+
         /**
          * Creates the QuickStatusBarHeader.
          */
@@ -152,36 +134,6 @@
         QSCustomizer createQSCustomizer();
     }
 
-    /**
-     * Module for providing view-specific constructor objects.
-     */
-    @Module
-    public class ViewAttributeProvider {
-        private final Context mContext;
-        private final AttributeSet mAttrs;
-
-        private ViewAttributeProvider(Context context, AttributeSet attrs) {
-            mContext = context;
-            mAttrs = attrs;
-        }
-
-        /**
-         * Provides the view-themed context (as opposed to the global sysui application context).
-         */
-        @Provides
-        @Named(VIEW_CONTEXT)
-        public Context provideContext() {
-            return mContext;
-        }
-
-        /**
-         * Provides the AttributeSet for the current view being inflated.
-         */
-        @Provides
-        public AttributeSet provideAttributeSet() {
-            return mAttrs;
-        }
-    }
 
     private class InjectionFactory implements LayoutInflater.Factory2 {
 
@@ -189,10 +141,9 @@
         public View onCreateView(String name, Context context, AttributeSet attrs) {
             Method creationMethod = mInjectionMap.get(name);
             if (creationMethod != null) {
-                ViewAttributeProvider provider = new ViewAttributeProvider(context, attrs);
                 try {
                     return (View) creationMethod.invoke(
-                            mViewCreator.createInstanceCreator(provider));
+                            mViewInstanceCreatorFactory.build(context, attrs));
                 } catch (IllegalAccessException e) {
                     throw new InflateException("Could not inflate " + name, e);
                 } catch (InvocationTargetException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/RingerModeTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/util/RingerModeTrackerImpl.kt
index 58684c3..7513241 100644
--- a/packages/SystemUI/src/com/android/systemui/util/RingerModeTrackerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/RingerModeTrackerImpl.kt
@@ -25,12 +25,12 @@
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
 import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import java.util.concurrent.Executor
 import javax.inject.Inject
-import javax.inject.Singleton
 
-@Singleton
+@SysUISingleton
 class RingerModeTrackerImpl @Inject constructor(
     audioManager: AudioManager,
     broadcastDispatcher: BroadcastDispatcher,
diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java
index e5f30cf..72f1f22 100644
--- a/packages/SystemUI/src/com/android/systemui/util/Utils.java
+++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java
@@ -21,12 +21,15 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.provider.Settings;
+import android.text.TextUtils;
 import android.view.View;
 
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.CommandQueue;
 
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.function.Consumer;
 
 public class Utils {
@@ -143,4 +146,21 @@
                 Settings.Secure.MEDIA_CONTROLS_RESUME, 1);
         return useQsMediaPlayer(context) && flag > 0;
     }
+
+    /**
+     * Get the set of apps for which the user has manually disabled resumption.
+     */
+    public static Set<String> getBlockedMediaApps(Context context) {
+        String list = Settings.Secure.getString(context.getContentResolver(),
+                Settings.Secure.MEDIA_CONTROLS_RESUME_BLOCKED);
+        if (TextUtils.isEmpty(list)) {
+            return new HashSet<>();
+        }
+        String[] names = list.split(":");
+        Set<String> apps = new HashSet<>(names.length);
+        for (String s : names) {
+            apps.add(s);
+        }
+        return apps;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
index bf22a98..628c808 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
@@ -22,6 +22,7 @@
 import android.os.Looper;
 import android.os.Process;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.LongRunning;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -30,8 +31,6 @@
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 
-import javax.inject.Singleton;
-
 import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
@@ -43,7 +42,7 @@
 public abstract class ConcurrencyModule {
     /** Background Looper */
     @Provides
-    @Singleton
+    @SysUISingleton
     @Background
     public static Looper provideBgLooper() {
         HandlerThread thread = new HandlerThread("SysUiBg",
@@ -54,7 +53,7 @@
 
     /** Long running tasks Looper */
     @Provides
-    @Singleton
+    @SysUISingleton
     @LongRunning
     public static Looper provideLongRunningLooper() {
         HandlerThread thread = new HandlerThread("SysUiLng",
@@ -96,7 +95,7 @@
      * Provide a Background-Thread Executor by default.
      */
     @Provides
-    @Singleton
+    @SysUISingleton
     public static Executor provideExecutor(@Background Looper looper) {
         return new ExecutorImpl(looper);
     }
@@ -105,7 +104,7 @@
      * Provide a Long running Executor by default.
      */
     @Provides
-    @Singleton
+    @SysUISingleton
     @LongRunning
     public static Executor provideLongRunningExecutor(@LongRunning Looper looper) {
         return new ExecutorImpl(looper);
@@ -115,7 +114,7 @@
      * Provide a Background-Thread Executor.
      */
     @Provides
-    @Singleton
+    @SysUISingleton
     @Background
     public static Executor provideBackgroundExecutor(@Background Looper looper) {
         return new ExecutorImpl(looper);
@@ -134,7 +133,7 @@
      * Provide a Background-Thread Executor by default.
      */
     @Provides
-    @Singleton
+    @SysUISingleton
     public static DelayableExecutor provideDelayableExecutor(@Background Looper looper) {
         return new ExecutorImpl(looper);
     }
@@ -143,7 +142,7 @@
      * Provide a Background-Thread Executor.
      */
     @Provides
-    @Singleton
+    @SysUISingleton
     @Background
     public static DelayableExecutor provideBackgroundDelayableExecutor(@Background Looper looper) {
         return new ExecutorImpl(looper);
@@ -153,7 +152,7 @@
      * Provide a Main-Thread Executor.
      */
     @Provides
-    @Singleton
+    @SysUISingleton
     @Main
     public static DelayableExecutor provideMainDelayableExecutor(@Main Looper looper) {
         return new ExecutorImpl(looper);
@@ -163,7 +162,7 @@
      * Provide a Background-Thread Executor by default.
      */
     @Provides
-    @Singleton
+    @SysUISingleton
     public static RepeatableExecutor provideRepeatableExecutor(@Background DelayableExecutor exec) {
         return new RepeatableExecutorImpl(exec);
     }
@@ -172,7 +171,7 @@
      * Provide a Background-Thread Executor.
      */
     @Provides
-    @Singleton
+    @SysUISingleton
     @Background
     public static RepeatableExecutor provideBackgroundRepeatableExecutor(
             @Background DelayableExecutor exec) {
@@ -183,7 +182,7 @@
      * Provide a Main-Thread Executor.
      */
     @Provides
-    @Singleton
+    @SysUISingleton
     @Main
     public static RepeatableExecutor provideMainRepeatableExecutor(@Main DelayableExecutor exec) {
         return new RepeatableExecutorImpl(exec);
@@ -195,7 +194,7 @@
      * Keep submitted runnables short and to the point, just as with any other UI code.
      */
     @Provides
-    @Singleton
+    @SysUISingleton
     @UiBackground
     public static Executor provideUiBackgroundExecutor() {
         return Executors.newSingleThreadExecutor();
diff --git a/packages/SystemUI/src/com/android/systemui/util/io/Files.java b/packages/SystemUI/src/com/android/systemui/util/io/Files.java
index 7d633a7..a2d309c 100644
--- a/packages/SystemUI/src/com/android/systemui/util/io/Files.java
+++ b/packages/SystemUI/src/com/android/systemui/util/io/Files.java
@@ -18,6 +18,8 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.systemui.dagger.SysUISingleton;
+
 import java.io.BufferedWriter;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
@@ -28,12 +30,11 @@
 import java.util.stream.Stream;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Wrapper around {@link java.nio.file.Files} that can be mocked in tests.
  */
-@Singleton
+@SysUISingleton
 public class Files {
     @Inject
     public Files() { }
diff --git a/tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java b/packages/SystemUI/src/com/android/systemui/util/kotlin/nullability.kt
similarity index 73%
copy from tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java
copy to packages/SystemUI/src/com/android/systemui/util/kotlin/nullability.kt
index 09ef472..92c73a4 100644
--- a/tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/nullability.kt
@@ -13,3 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
+package com.android.systemui.util.kotlin
+
+/**
+ * If [value] is not null, then returns block(value). Otherwise returns null.
+ */
+inline fun <T : Any, R> transform(value: T?, block: (T) -> R): R? = value?.let(block)
diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
index d1805af..ba58ed2 100644
--- a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
@@ -48,6 +48,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
@@ -63,14 +64,13 @@
 import java.util.List;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Suite of tools to periodically inspect the System UI heap and possibly prompt the user to
  * capture heap dumps and report them. Includes the implementation of the "Dump SysUI Heap"
  * quick settings tile.
  */
-@Singleton
+@SysUISingleton
 public class GarbageMonitor implements Dumpable {
     // Feature switches
     // ================
@@ -552,7 +552,7 @@
     }
 
     /** */
-    @Singleton
+    @SysUISingleton
     public static class Service extends SystemUI implements Dumpable {
         private final GarbageMonitor mGarbageMonitor;
 
diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/LeakReporter.java b/packages/SystemUI/src/com/android/systemui/util/leak/LeakReporter.java
index 5e72808..f0a4195 100644
--- a/packages/SystemUI/src/com/android/systemui/util/leak/LeakReporter.java
+++ b/packages/SystemUI/src/com/android/systemui/util/leak/LeakReporter.java
@@ -34,6 +34,8 @@
 
 import androidx.core.content.FileProvider;
 
+import com.android.systemui.dagger.SysUISingleton;
+
 import com.google.android.collect.Lists;
 
 import java.io.File;
@@ -44,12 +46,11 @@
 
 import javax.inject.Inject;
 import javax.inject.Named;
-import javax.inject.Singleton;
 
 /**
  * Dumps data to debug leaks and posts a notification to share the data.
  */
-@Singleton
+@SysUISingleton
 public class LeakReporter {
 
     static final String TAG = "LeakReporter";
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
index ed4df17..4875982 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
@@ -29,6 +29,7 @@
 import android.util.Log;
 
 import com.android.internal.util.Preconditions;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.plugins.SensorManagerPlugin;
 import com.android.systemui.shared.plugins.PluginManager;
@@ -39,7 +40,6 @@
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Wrapper around sensor manager that hides potential sources of latency.
@@ -48,7 +48,7 @@
  * without blocking. Note that this means registering listeners now always appears successful even
  * if it is not.
  */
-@Singleton
+@SysUISingleton
 public class AsyncSensorManager extends SensorManager
         implements PluginListener<SensorManagerPlugin> {
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java
index 4239337..e8c5db7 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java
@@ -18,7 +18,7 @@
 
 import android.content.res.Configuration;
 
-import com.android.systemui.DemoMode;
+import com.android.systemui.demomode.DemoMode;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
index 1c2a2fa..56f1c09 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
@@ -27,6 +27,9 @@
 
 import com.android.settingslib.applications.InterestingConfigChanges;
 import com.android.systemui.Dependency;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.demomode.DemoMode;
+import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.PluginDependencyProvider;
@@ -38,14 +41,15 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Implementation of VolumeComponent backed by the new volume dialog.
  */
-@Singleton
+@SysUISingleton
 public class VolumeDialogComponent implements VolumeComponent, TunerService.Tunable,
         VolumeDialogControllerImpl.UserActivityListener{
 
@@ -72,8 +76,11 @@
     );
 
     @Inject
-    public VolumeDialogComponent(Context context, KeyguardViewMediator keyguardViewMediator,
-            VolumeDialogControllerImpl volumeDialogController) {
+    public VolumeDialogComponent(
+            Context context,
+            KeyguardViewMediator keyguardViewMediator,
+            VolumeDialogControllerImpl volumeDialogController,
+            DemoModeController demoModeController) {
         mContext = context;
         mKeyguardViewMediator = keyguardViewMediator;
         mController = volumeDialogController;
@@ -94,6 +101,7 @@
         applyConfiguration();
         Dependency.get(TunerService.class).addTunable(this, VOLUME_DOWN_SILENT, VOLUME_UP_SILENT,
                 VOLUME_SILENT_DO_NOT_DISTURB);
+        demoModeController.addCallback(this);
     }
 
     protected VolumeDialog createDefault() {
@@ -164,6 +172,13 @@
     }
 
     @Override
+    public List<String> demoCommands() {
+        List<String> s = new ArrayList<>();
+        s.add(DemoMode.COMMAND_VOLUME);
+        return s;
+    }
+
+    @Override
     public void register() {
         mController.register();
         DndTile.setCombinedIcon(mContext, true);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index f19c49c..d8eecef 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -62,6 +62,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.plugins.VolumeDialogController;
 import com.android.systemui.qs.tiles.DndTile;
@@ -77,7 +78,6 @@
 import java.util.Optional;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 
@@ -88,7 +88,7 @@
  *
  *  Methods ending in "W" must be called on the worker thread.
  */
-@Singleton
+@SysUISingleton
 public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpable {
     private static final String TAG = Util.logTag(VolumeDialogControllerImpl.class);
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 4b119dd..51ad30e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -940,6 +940,7 @@
     protected void onStateChangedH(State state) {
         if (D.BUG) Log.d(TAG, "onStateChangedH() state: " + state.toString());
         if (mState != null && state != null
+                && mState.ringerModeInternal != -1
                 && mState.ringerModeInternal != state.ringerModeInternal
                 && state.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE) {
             mController.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK));
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
index c0b8041..c378e3b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
@@ -23,15 +23,15 @@
 
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.qs.tiles.DndTile;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
-@Singleton
+@SysUISingleton
 public class VolumeUI extends SystemUI {
     private static final String TAG = "VolumeUI";
     private static boolean LOGD = Log.isLoggable(TAG, Log.DEBUG);
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WindowManagerShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
similarity index 60%
copy from packages/SystemUI/src/com/android/systemui/wmshell/WindowManagerShellModule.java
copy to packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index d2c61cc..ac47660 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WindowManagerShellModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -20,59 +20,39 @@
 import android.os.Handler;
 import android.view.IWindowManager;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.pip.phone.PipMenuActivity;
-import com.android.systemui.pip.phone.dagger.PipMenuActivityClass;
 import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.SystemWindows;
 import com.android.wm.shell.common.TransactionPool;
 
-import javax.inject.Singleton;
-
 import dagger.Module;
 import dagger.Provides;
 
 /**
- * Provides dependencies from {@link com.android.wm.shell}.
+ * Provides basic dependencies from {@link com.android.wm.shell}, the dependencies declared here
+ * should be shared among different branches of SystemUI.
  */
+// TODO(b/162923491): Move most of these dependencies into WMSingleton scope.
 @Module
-// TODO(b/161116823) Clean up dependencies after wm shell migration finished.
-public class WindowManagerShellModule {
-    @Singleton
+public class WMShellBaseModule {
+    @SysUISingleton
     @Provides
     static TransactionPool provideTransactionPool() {
         return new TransactionPool();
     }
 
-    @Singleton
+    @SysUISingleton
     @Provides
     static DisplayController provideDisplayController(Context context, @Main Handler handler,
             IWindowManager wmService) {
         return new DisplayController(context, handler, wmService);
     }
 
-    @Singleton
+    @SysUISingleton
     @Provides
     static SystemWindows provideSystemWindows(DisplayController displayController,
             IWindowManager wmService) {
         return new SystemWindows(displayController, wmService);
     }
-
-    @Singleton
-    @Provides
-    static DisplayImeController provideDisplayImeController(IWindowManager wmService,
-            DisplayController displayController, @Main Handler mainHandler,
-            TransactionPool transactionPool) {
-        return new DisplayImeController.Builder(wmService, displayController, mainHandler,
-                transactionPool).build();
-    }
-
-    /** TODO(b/150319024): PipMenuActivity will move to a Window */
-    @Singleton
-    @PipMenuActivityClass
-    @Provides
-    static Class<?> providePipMenuActivityClass() {
-        return PipMenuActivity.class;
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WindowManagerShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
similarity index 64%
rename from packages/SystemUI/src/com/android/systemui/wmshell/WindowManagerShellModule.java
rename to packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
index d2c61cc..44bb6ac 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WindowManagerShellModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
@@ -16,50 +16,28 @@
 
 package com.android.systemui.wmshell;
 
-import android.content.Context;
 import android.os.Handler;
 import android.view.IWindowManager;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.pip.phone.PipMenuActivity;
 import com.android.systemui.pip.phone.dagger.PipMenuActivityClass;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
-import com.android.wm.shell.common.SystemWindows;
 import com.android.wm.shell.common.TransactionPool;
 
-import javax.inject.Singleton;
-
 import dagger.Module;
 import dagger.Provides;
 
 /**
- * Provides dependencies from {@link com.android.wm.shell}.
+ * Provides dependencies from {@link com.android.wm.shell} which could be customized among different
+ * branches of SystemUI.
  */
-@Module
-// TODO(b/161116823) Clean up dependencies after wm shell migration finished.
-public class WindowManagerShellModule {
-    @Singleton
-    @Provides
-    static TransactionPool provideTransactionPool() {
-        return new TransactionPool();
-    }
-
-    @Singleton
-    @Provides
-    static DisplayController provideDisplayController(Context context, @Main Handler handler,
-            IWindowManager wmService) {
-        return new DisplayController(context, handler, wmService);
-    }
-
-    @Singleton
-    @Provides
-    static SystemWindows provideSystemWindows(DisplayController displayController,
-            IWindowManager wmService) {
-        return new SystemWindows(displayController, wmService);
-    }
-
-    @Singleton
+// TODO(b/162923491): Move most of these dependencies into WMSingleton scope.
+@Module(includes = WMShellBaseModule.class)
+public class WMShellModule {
+    @SysUISingleton
     @Provides
     static DisplayImeController provideDisplayImeController(IWindowManager wmService,
             DisplayController displayController, @Main Handler mainHandler,
@@ -69,7 +47,7 @@
     }
 
     /** TODO(b/150319024): PipMenuActivity will move to a Window */
-    @Singleton
+    @SysUISingleton
     @PipMenuActivityClass
     @Provides
     static Class<?> providePipMenuActivityClass() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index 4c0762e..5999e2c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
@@ -79,7 +79,9 @@
                 .thenReturn(mMockKeyguardSliceView);
 
         InjectionInflationController inflationController = new InjectionInflationController(
-                SystemUIFactory.getInstance().getRootComponent());
+                SystemUIFactory.getInstance()
+                        .getSysUIComponent()
+                        .createViewInstanceCreatorFactory());
         LayoutInflater layoutInflater = inflationController
                 .injectable(LayoutInflater.from(getContext()));
         layoutInflater.setPrivateFactory(new LayoutInflater.Factory2() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java
index e6c2ddc..446b122 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java
@@ -65,7 +65,9 @@
         allowTestableLooperAsMainThread();
 
         InjectionInflationController inflationController = new InjectionInflationController(
-                SystemUIFactory.getInstance().getRootComponent());
+                SystemUIFactory.getInstance()
+                        .getSysUIComponent()
+                        .createViewInstanceCreatorFactory());
         mLayoutInflater = inflationController.injectable(LayoutInflater.from(mContext));
         mLayoutInflater.setPrivateFactory(new LayoutInflater.Factory2() {
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java
index bc3c3d9..0bf1376 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java
@@ -51,7 +51,9 @@
         allowTestableLooperAsMainThread();
         mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
         InjectionInflationController inflationController = new InjectionInflationController(
-                SystemUIFactory.getInstance().getRootComponent());
+                SystemUIFactory.getInstance()
+                        .getSysUIComponent()
+                        .createViewInstanceCreatorFactory());
         LayoutInflater layoutInflater = inflationController
                 .injectable(LayoutInflater.from(getContext()));
         mKeyguardStatusView =
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index a0e5f73..1192023 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -182,7 +182,7 @@
         // IBiometricsFace@1.0 does not support detection, only authentication.
         when(mFaceSensorProperties.isEmpty()).thenReturn(false);
         when(mFaceSensorProperties.get(anyInt())).thenReturn(new FaceSensorProperties(0 /* id */,
-                false /* supportsFaceDetection */));
+                false /* supportsFaceDetection */, true /* supportsSelfIllumination */));
 
         when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
         when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
index 35be496..5d8e435 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
@@ -47,7 +47,7 @@
     public void testInitDependency() {
         Dependency.clearDependencies();
         Dependency dependency =
-                SystemUIFactory.getInstance().getRootComponent().createDependency();
+                SystemUIFactory.getInstance().getSysUIComponent().createDependency();
         dependency.start();
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
index 60f0cd9..e967a5d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
@@ -82,8 +82,7 @@
         allowTestableLooperAsMainThread();
 
         MockitoAnnotations.initMocks(this);
-        mFsc = new ForegroundServiceController(
-                mEntryManager, mAppOpsController, mMainHandler);
+        mFsc = new ForegroundServiceController(mAppOpsController, mMainHandler);
         mListener = new ForegroundServiceNotificationListener(
                 mContext, mFsc, mEntryManager, mNotifPipeline,
                 mock(ForegroundServiceLifetimeExtender.class), mClock);
@@ -115,85 +114,6 @@
     }
 
     @Test
-    public void testAppOps_appOpChangedBeforeNotificationExists() {
-        // GIVEN app op exists, but notification doesn't exist in NEM yet
-        NotificationEntry entry = createFgEntry();
-        mFsc.onAppOpChanged(
-                AppOpsManager.OP_CAMERA,
-                entry.getSbn().getUid(),
-                entry.getSbn().getPackageName(),
-                true);
-        assertFalse(entry.mActiveAppOps.contains(AppOpsManager.OP_CAMERA));
-
-        // WHEN the notification is added
-        mEntryListener.onPendingEntryAdded(entry);
-
-        // THEN the app op is added to the entry
-        Assert.assertTrue(entry.mActiveAppOps.contains(AppOpsManager.OP_CAMERA));
-    }
-
-    @Test
-    public void testAppOps_appOpAddedToForegroundNotif() {
-        // GIVEN a notification associated with a foreground service
-        NotificationEntry entry = addFgEntry();
-        when(mEntryManager.getPendingOrActiveNotif(entry.getKey())).thenReturn(entry);
-
-        // WHEN we are notified of a new app op for this notification
-        mFsc.onAppOpChanged(
-                AppOpsManager.OP_CAMERA,
-                entry.getSbn().getUid(),
-                entry.getSbn().getPackageName(),
-                true);
-
-        // THEN the app op is added to the entry
-        Assert.assertTrue(entry.mActiveAppOps.contains(AppOpsManager.OP_CAMERA));
-
-        // THEN notification views are updated since the notification is visible
-        verify(mEntryManager, times(1)).updateNotifications(anyString());
-    }
-
-    @Test
-    public void testAppOpsAlreadyAdded() {
-        // GIVEN a foreground service associated notification that already has the correct app op
-        NotificationEntry entry = addFgEntry();
-        entry.mActiveAppOps.add(AppOpsManager.OP_CAMERA);
-        when(mEntryManager.getPendingOrActiveNotif(entry.getKey())).thenReturn(entry);
-
-        // WHEN we are notified of the same app op for this notification
-        mFsc.onAppOpChanged(
-                AppOpsManager.OP_CAMERA,
-                entry.getSbn().getUid(),
-                entry.getSbn().getPackageName(),
-                true);
-
-        // THEN the app op still exists in the notification entry
-        Assert.assertTrue(entry.mActiveAppOps.contains(AppOpsManager.OP_CAMERA));
-
-        // THEN notification views aren't updated since nothing changed
-        verify(mEntryManager, never()).updateNotifications(anyString());
-    }
-
-    @Test
-    public void testAppOps_appOpNotAddedToUnrelatedNotif() {
-        // GIVEN no notification entries correspond to the newly updated appOp
-        NotificationEntry entry = addFgEntry();
-        when(mEntryManager.getPendingOrActiveNotif(entry.getKey())).thenReturn(null);
-
-        // WHEN a new app op is detected
-        mFsc.onAppOpChanged(
-                AppOpsManager.OP_CAMERA,
-                entry.getSbn().getUid(),
-                entry.getSbn().getPackageName(),
-                true);
-
-        // THEN we won't see appOps on the entry
-        Assert.assertFalse(entry.mActiveAppOps.contains(AppOpsManager.OP_CAMERA));
-
-        // THEN notification views aren't updated since nothing changed
-        verify(mEntryManager, never()).updateNotifications(anyString());
-    }
-
-    @Test
     public void testAppOpsCRUD() {
         // no crash on remove that doesn't exist
         mFsc.onAppOpChanged(9, 1000, "pkg1", false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiBaseFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiBaseFragmentTest.java
index 3687b4c..53bae86 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysuiBaseFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiBaseFragmentTest.java
@@ -55,7 +55,7 @@
     public void SysuiSetup() {
         SystemUIFactory.createFromConfig(mContext);
         mDependency = new TestableDependency(
-                SystemUIFactory.getInstance().getRootComponent().createDependency());
+                SystemUIFactory.getInstance().getSysUIComponent().createDependency());
         Dependency.setInstance(mDependency);
 
         // TODO: Figure out another way to give reference to a SysuiTestableContext.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
index 08e2784..b7175ea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
@@ -74,7 +74,7 @@
     public void SysuiSetup() throws Exception {
         SystemUIFactory.createFromConfig(mContext);
         mDependency = new TestableDependency(
-                SystemUIFactory.getInstance().getRootComponent().createDependency());
+                SystemUIFactory.getInstance().getSysUIComponent().createDependency());
         Dependency.setInstance(mDependency);
         mFakeBroadcastDispatcher = new FakeBroadcastDispatcher(mContext, mock(Looper.class),
                 mock(Executor.class), mock(DumpManager.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
index b7c198e..a6dfbbd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
@@ -38,6 +38,7 @@
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.systemui.SysuiTestCase;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -94,6 +95,11 @@
                 mContext, mController, newValueAnimator());
     }
 
+    @After
+    public void tearDown() throws Exception {
+        mInstrumentation.runOnMainSync(() -> mController.deleteWindowMagnification());
+    }
+
     @Test
     public void enableWindowMagnification_disabled_expectedStartAndEndValues() {
         enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleBehaviorControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleBehaviorControllerTest.java
index afcd441..7567f0c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleBehaviorControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleBehaviorControllerTest.java
@@ -41,7 +41,7 @@
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.statusbar.phone.NavigationModeController;
+import com.android.systemui.navigationbar.NavigationModeController;
 
 import org.junit.After;
 import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index d4a94c5..c8566c5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -54,6 +54,7 @@
 
 import com.android.internal.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.CommandQueue;
 
 import org.junit.Before;
@@ -103,8 +104,8 @@
         when(mDialog1.isAllowDeviceCredentials()).thenReturn(false);
         when(mDialog2.isAllowDeviceCredentials()).thenReturn(false);
 
-        mAuthController = new TestableAuthController(
-                context, mock(CommandQueue.class), new MockInjector());
+        mAuthController = new TestableAuthController(context, mock(CommandQueue.class),
+                mock(StatusBarStateController.class), new MockInjector());
 
         mAuthController.start();
     }
@@ -502,8 +503,9 @@
         private int mBuildCount = 0;
         private PromptInfo mLastBiometricPromptInfo;
 
-        TestableAuthController(Context context, CommandQueue commandQueue, Injector injector) {
-            super(context, commandQueue, injector);
+        TestableAuthController(Context context, CommandQueue commandQueue,
+                StatusBarStateController statusBarStateController, Injector injector) {
+            super(context, commandQueue, statusBarStateController, injector);
         }
 
         @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index 1538a32..a7808ad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -60,7 +60,6 @@
 
 import com.android.internal.colorextraction.ColorExtractor;
 import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.SystemUIFactory;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dump.DumpManager;
@@ -71,9 +70,7 @@
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationRemoveInterceptor;
-import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.RankingBuilder;
-import com.android.systemui.statusbar.SuperStatusBarViewFactory;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -87,7 +84,7 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.LockscreenLockIconController;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowControllerImpl;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -95,7 +92,6 @@
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.util.FloatingContentCoordinator;
-import com.android.systemui.util.InjectionInflationController;
 
 import com.google.common.collect.ImmutableList;
 
@@ -159,7 +155,7 @@
     private ArgumentCaptor<NotificationRemoveInterceptor> mRemoveInterceptorCaptor;
 
     private TestableBubbleController mBubbleController;
-    private NotificationShadeWindowController mNotificationShadeWindowController;
+    private NotificationShadeWindowControllerImpl mNotificationShadeWindowController;
     private NotificationEntryListener mEntryListener;
     private NotificationRemoveInterceptor mRemoveInterceptor;
 
@@ -199,8 +195,6 @@
 
     private TestableLooper mTestableLooper;
 
-    private SuperStatusBarViewFactory mSuperStatusBarViewFactory;
-
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
@@ -210,24 +204,8 @@
         mContext.addMockSystemService(FaceManager.class, mFaceManager);
         when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
 
-        mSuperStatusBarViewFactory = new SuperStatusBarViewFactory(mContext,
-                new InjectionInflationController(SystemUIFactory.getInstance().getRootComponent()),
-                new NotificationShelfComponent.Builder() {
-                    @Override
-                    public NotificationShelfComponent.Builder notificationShelf(
-                            NotificationShelf view) {
-                        return this;
-                    }
-
-                    @Override
-                    public NotificationShelfComponent build() {
-                        return mNotificationShelfComponent;
-                    }
-                },
-                mLockIconController);
-
         // Bubbles get added to status bar window view
-        mNotificationShadeWindowController = new NotificationShadeWindowController(mContext,
+        mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(mContext,
                 mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController,
                 mConfigurationController, mKeyguardViewMediator, mKeyguardBypassController,
                 mColorExtractor, mDumpManager);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
index 0e7cb79..4936360 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
@@ -83,7 +83,7 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.LockscreenLockIconController;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowControllerImpl;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -153,7 +153,7 @@
     @Captor
     private ArgumentCaptor<NotifCollectionListener> mNotifListenerCaptor;
     private TestableBubbleController mBubbleController;
-    private NotificationShadeWindowController mNotificationShadeWindowController;
+    private NotificationShadeWindowControllerImpl mNotificationShadeWindowController;
     private NotifCollectionListener mEntryListener;
     private NotificationTestHelper mNotificationTestHelper;
     private ExpandableNotificationRow mRow;
@@ -201,7 +201,8 @@
         when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
 
         mSuperStatusBarViewFactory = new SuperStatusBarViewFactory(mContext,
-                new InjectionInflationController(SystemUIFactory.getInstance().getRootComponent()),
+                new InjectionInflationController(SystemUIFactory.getInstance().getSysUIComponent()
+                        .createViewInstanceCreatorFactory()),
                 new NotificationShelfComponent.Builder() {
                     @Override
                     public NotificationShelfComponent.Builder notificationShelf(
@@ -217,7 +218,7 @@
                 mLockIconController);
 
         // Bubbles get added to status bar window view
-        mNotificationShadeWindowController = new NotificationShadeWindowController(mContext,
+        mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(mContext,
                 mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController,
                 mConfigurationController, mKeyguardViewMediator, mKeyguardBypassController,
                 mColorExtractor, mDumpManager);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java
index 0a6d071..51ca2a4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java
@@ -27,11 +27,11 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ZenModeController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
index 4c54954..c8e0f49 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
@@ -71,7 +71,7 @@
 import com.android.systemui.plugins.GlobalActionsPanelPlugin;
 import com.android.systemui.settings.CurrentUserContextTracker;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.RingerModeLiveData;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
index f70fb4f..d1f505b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
@@ -254,16 +254,12 @@
         int mCleanDateFormatInvokations;
         private int mCounter;
 
-        Uri getUri() {
-            return mSliceUri;
-        }
+        TestableKeyguardSliceProvider() {
+            super();
 
-        @Override
-        protected void inject() {
             mAlarmManager = KeyguardSliceProviderTest.this.mAlarmManager;
             mContentResolver = KeyguardSliceProviderTest.this.mContentResolver;
             mZenModeController = KeyguardSliceProviderTest.this.mZenModeController;
-            mMediaWakeLock = KeyguardSliceProviderTest.this.mMediaWakeLock;
             mDozeParameters = KeyguardSliceProviderTest.this.mDozeParameters;
             mNextAlarmController = KeyguardSliceProviderTest.this.mNextAlarmController;
             mStatusBarStateController = KeyguardSliceProviderTest.this.mStatusBarStateController;
@@ -272,6 +268,17 @@
         }
 
         @Override
+        public boolean onCreateSliceProvider() {
+            boolean result = super.onCreateSliceProvider();
+            mMediaWakeLock = KeyguardSliceProviderTest.this.mMediaWakeLock;
+            return result;
+        }
+
+        Uri getUri() {
+            return mSliceUri;
+        }
+
+        @Override
         protected boolean isDndOn() {
             return mIsZenMode;
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 90e9ef4..c874b1f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -42,10 +42,11 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.statusbar.phone.NavigationModeController;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.util.DeviceConfigProxy;
 import com.android.systemui.util.DeviceConfigProxyFake;
+import com.android.systemui.util.InjectionInflationController;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 
@@ -71,6 +72,7 @@
     private @Mock PowerManager mPowerManager;
     private @Mock TrustManager mTrustManager;
     private @Mock NavigationModeController mNavigationModeController;
+    private @Mock InjectionInflationController mInjectionInflationController;
     private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake();
     private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
 
@@ -88,7 +90,8 @@
                 mContext, mFalsingManager, mLockPatternUtils, mBroadcastDispatcher,
                 () -> mStatusBarKeyguardViewManager,
                 mDismissCallbackRegistry, mUpdateMonitor, mDumpManager, mUiBgExecutor,
-                mPowerManager, mTrustManager, mDeviceConfig, mNavigationModeController);
+                mPowerManager, mTrustManager, mDeviceConfig, mNavigationModeController,
+                mInjectionInflationController);
         mViewMediator.start();
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
index 492b33e..89538ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
@@ -20,7 +20,6 @@
 
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
@@ -34,19 +33,23 @@
 import com.android.systemui.SysuiTestCase;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 
 import java.util.ArrayList;
-import java.util.Map;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class MediaDataCombineLatestTest extends SysuiTestCase {
 
+    @Rule public MockitoRule mockito = MockitoJUnit.rule();
+
     private static final String KEY = "TEST_KEY";
     private static final String OLD_KEY = "TEST_KEY_OLD";
     private static final String APP = "APP";
@@ -59,39 +62,26 @@
 
     private MediaDataCombineLatest mManager;
 
-    @Mock private MediaDataManager mDataSource;
-    @Mock private MediaDeviceManager mDeviceSource;
     @Mock private MediaDataManager.Listener mListener;
 
-    private MediaDataManager.Listener mDataListener;
-    private MediaDeviceManager.Listener mDeviceListener;
-
     private MediaData mMediaData;
     private MediaDeviceData mDeviceData;
 
     @Before
     public void setUp() {
-        mDataSource = mock(MediaDataManager.class);
-        mDeviceSource = mock(MediaDeviceManager.class);
-        mListener = mock(MediaDataManager.Listener.class);
-
-        mManager = new MediaDataCombineLatest(mDataSource, mDeviceSource);
-
-        mDataListener = captureDataListener();
-        mDeviceListener = captureDeviceListener();
-
+        mManager = new MediaDataCombineLatest();
         mManager.addListener(mListener);
 
         mMediaData = new MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null,
-                new ArrayList<>(), new ArrayList<>(), PACKAGE, null, null, null, true, null, false,
-                KEY, false);
+                new ArrayList<>(), new ArrayList<>(), PACKAGE, null, null, null, true, null, true,
+                false, KEY, false);
         mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME);
     }
 
     @Test
     public void eventNotEmittedWithoutDevice() {
         // WHEN data source emits an event without device data
-        mDataListener.onMediaDataLoaded(KEY, null, mMediaData);
+        mManager.onMediaDataLoaded(KEY, null, mMediaData);
         // THEN an event isn't emitted
         verify(mListener, never()).onMediaDataLoaded(eq(KEY), any(), any());
     }
@@ -99,7 +89,7 @@
     @Test
     public void eventNotEmittedWithoutMedia() {
         // WHEN device source emits an event without media data
-        mDeviceListener.onMediaDeviceChanged(KEY, null, mDeviceData);
+        mManager.onMediaDeviceChanged(KEY, null, mDeviceData);
         // THEN an event isn't emitted
         verify(mListener, never()).onMediaDataLoaded(eq(KEY), any(), any());
     }
@@ -107,9 +97,9 @@
     @Test
     public void emitEventAfterDeviceFirst() {
         // GIVEN that a device event has already been received
-        mDeviceListener.onMediaDeviceChanged(KEY, null, mDeviceData);
+        mManager.onMediaDeviceChanged(KEY, null, mDeviceData);
         // WHEN media event is received
-        mDataListener.onMediaDataLoaded(KEY, null, mMediaData);
+        mManager.onMediaDataLoaded(KEY, null, mMediaData);
         // THEN the listener receives a combined event
         ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
         verify(mListener).onMediaDataLoaded(eq(KEY), any(), captor.capture());
@@ -119,9 +109,9 @@
     @Test
     public void emitEventAfterMediaFirst() {
         // GIVEN that media event has already been received
-        mDataListener.onMediaDataLoaded(KEY, null, mMediaData);
+        mManager.onMediaDataLoaded(KEY, null, mMediaData);
         // WHEN device event is received
-        mDeviceListener.onMediaDeviceChanged(KEY, null, mDeviceData);
+        mManager.onMediaDeviceChanged(KEY, null, mDeviceData);
         // THEN the listener receives a combined event
         ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
         verify(mListener).onMediaDataLoaded(eq(KEY), any(), captor.capture());
@@ -131,11 +121,11 @@
     @Test
     public void migrateKeyMediaFirst() {
         // GIVEN that media and device info has already been received
-        mDataListener.onMediaDataLoaded(OLD_KEY, null, mMediaData);
-        mDeviceListener.onMediaDeviceChanged(OLD_KEY, null, mDeviceData);
+        mManager.onMediaDataLoaded(OLD_KEY, null, mMediaData);
+        mManager.onMediaDeviceChanged(OLD_KEY, null, mDeviceData);
         reset(mListener);
         // WHEN a key migration event is received
-        mDataListener.onMediaDataLoaded(KEY, OLD_KEY, mMediaData);
+        mManager.onMediaDataLoaded(KEY, OLD_KEY, mMediaData);
         // THEN the listener receives a combined event
         ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
         verify(mListener).onMediaDataLoaded(eq(KEY), eq(OLD_KEY), captor.capture());
@@ -145,11 +135,11 @@
     @Test
     public void migrateKeyDeviceFirst() {
         // GIVEN that media and device info has already been received
-        mDataListener.onMediaDataLoaded(OLD_KEY, null, mMediaData);
-        mDeviceListener.onMediaDeviceChanged(OLD_KEY, null, mDeviceData);
+        mManager.onMediaDataLoaded(OLD_KEY, null, mMediaData);
+        mManager.onMediaDeviceChanged(OLD_KEY, null, mDeviceData);
         reset(mListener);
         // WHEN a key migration event is received
-        mDeviceListener.onMediaDeviceChanged(KEY, OLD_KEY, mDeviceData);
+        mManager.onMediaDeviceChanged(KEY, OLD_KEY, mDeviceData);
         // THEN the listener receives a combined event
         ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
         verify(mListener).onMediaDataLoaded(eq(KEY), eq(OLD_KEY), captor.capture());
@@ -159,12 +149,12 @@
     @Test
     public void migrateKeyMediaAfter() {
         // GIVEN that media and device info has already been received
-        mDataListener.onMediaDataLoaded(OLD_KEY, null, mMediaData);
-        mDeviceListener.onMediaDeviceChanged(OLD_KEY, null, mDeviceData);
-        mDeviceListener.onMediaDeviceChanged(KEY, OLD_KEY, mDeviceData);
+        mManager.onMediaDataLoaded(OLD_KEY, null, mMediaData);
+        mManager.onMediaDeviceChanged(OLD_KEY, null, mDeviceData);
+        mManager.onMediaDeviceChanged(KEY, OLD_KEY, mDeviceData);
         reset(mListener);
         // WHEN a second key migration event is received for media
-        mDataListener.onMediaDataLoaded(KEY, OLD_KEY, mMediaData);
+        mManager.onMediaDataLoaded(KEY, OLD_KEY, mMediaData);
         // THEN the key has already been migrated
         ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
         verify(mListener).onMediaDataLoaded(eq(KEY), eq(KEY), captor.capture());
@@ -174,12 +164,12 @@
     @Test
     public void migrateKeyDeviceAfter() {
         // GIVEN that media and device info has already been received
-        mDataListener.onMediaDataLoaded(OLD_KEY, null, mMediaData);
-        mDeviceListener.onMediaDeviceChanged(OLD_KEY, null, mDeviceData);
-        mDataListener.onMediaDataLoaded(KEY, OLD_KEY, mMediaData);
+        mManager.onMediaDataLoaded(OLD_KEY, null, mMediaData);
+        mManager.onMediaDeviceChanged(OLD_KEY, null, mDeviceData);
+        mManager.onMediaDataLoaded(KEY, OLD_KEY, mMediaData);
         reset(mListener);
         // WHEN a second key migration event is received for the device
-        mDeviceListener.onMediaDeviceChanged(KEY, OLD_KEY, mDeviceData);
+        mManager.onMediaDeviceChanged(KEY, OLD_KEY, mDeviceData);
         // THEN the key has already be migrated
         ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
         verify(mListener).onMediaDataLoaded(eq(KEY), eq(KEY), captor.capture());
@@ -189,60 +179,34 @@
     @Test
     public void mediaDataRemoved() {
         // WHEN media data is removed without first receiving device or data
-        mDataListener.onMediaDataRemoved(KEY);
+        mManager.onMediaDataRemoved(KEY);
         // THEN a removed event isn't emitted
         verify(mListener, never()).onMediaDataRemoved(eq(KEY));
     }
 
     @Test
     public void mediaDataRemovedAfterMediaEvent() {
-        mDataListener.onMediaDataLoaded(KEY, null, mMediaData);
-        mDataListener.onMediaDataRemoved(KEY);
+        mManager.onMediaDataLoaded(KEY, null, mMediaData);
+        mManager.onMediaDataRemoved(KEY);
         verify(mListener).onMediaDataRemoved(eq(KEY));
     }
 
     @Test
     public void mediaDataRemovedAfterDeviceEvent() {
-        mDeviceListener.onMediaDeviceChanged(KEY, null, mDeviceData);
-        mDataListener.onMediaDataRemoved(KEY);
+        mManager.onMediaDeviceChanged(KEY, null, mDeviceData);
+        mManager.onMediaDataRemoved(KEY);
         verify(mListener).onMediaDataRemoved(eq(KEY));
     }
 
     @Test
     public void mediaDataKeyUpdated() {
         // GIVEN that device and media events have already been received
-        mDataListener.onMediaDataLoaded(KEY, null, mMediaData);
-        mDeviceListener.onMediaDeviceChanged(KEY, null, mDeviceData);
+        mManager.onMediaDataLoaded(KEY, null, mMediaData);
+        mManager.onMediaDeviceChanged(KEY, null, mDeviceData);
         // WHEN the key is changed
-        mDataListener.onMediaDataLoaded("NEW_KEY", KEY, mMediaData);
+        mManager.onMediaDataLoaded("NEW_KEY", KEY, mMediaData);
         // THEN the listener gets a load event with the correct keys
         ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
         verify(mListener).onMediaDataLoaded(eq("NEW_KEY"), any(), captor.capture());
     }
-
-    @Test
-    public void getDataIncludesDevice() {
-        // GIVEN that device and media events have been received
-        mDeviceListener.onMediaDeviceChanged(KEY, null, mDeviceData);
-        mDataListener.onMediaDataLoaded(KEY, null, mMediaData);
-
-        // THEN the result of getData includes device info
-        Map<String, MediaData> results = mManager.getData();
-        assertThat(results.get(KEY)).isNotNull();
-        assertThat(results.get(KEY).getDevice()).isEqualTo(mDeviceData);
-    }
-
-    private MediaDataManager.Listener captureDataListener() {
-        ArgumentCaptor<MediaDataManager.Listener> captor = ArgumentCaptor.forClass(
-                MediaDataManager.Listener.class);
-        verify(mDataSource).addListener(captor.capture());
-        return captor.getValue();
-    }
-
-    private MediaDeviceManager.Listener captureDeviceListener() {
-        ArgumentCaptor<MediaDeviceManager.Listener> captor = ArgumentCaptor.forClass(
-                MediaDeviceManager.Listener.class);
-        verify(mDeviceSource).addListener(captor.capture());
-        return captor.getValue();
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
index afb64a7..36b6527 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
@@ -32,6 +32,7 @@
 import org.mockito.Mockito
 import org.mockito.Mockito.`when`
 import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 import java.util.concurrent.Executor
@@ -56,8 +57,6 @@
 class MediaDataFilterTest : SysuiTestCase() {
 
     @Mock
-    private lateinit var combineLatest: MediaDataCombineLatest
-    @Mock
     private lateinit var listener: MediaDataManager.Listener
     @Mock
     private lateinit var broadcastDispatcher: BroadcastDispatcher
@@ -78,8 +77,9 @@
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-        mediaDataFilter = MediaDataFilter(combineLatest, broadcastDispatcher, mediaResumeListener,
-            mediaDataManager, lockscreenUserManager, executor)
+        mediaDataFilter = MediaDataFilter(broadcastDispatcher, mediaResumeListener,
+                lockscreenUserManager, executor)
+        mediaDataFilter.mediaDataManager = mediaDataManager
         mediaDataFilter.addListener(listener)
 
         // Start all tests as main user
@@ -152,8 +152,9 @@
     @Test
     fun testOnUserSwitched_addsNewUserControls() {
         // GIVEN that we had some media for both users
-        val dataMap = mapOf(KEY to dataMain, KEY_ALT to dataGuest)
-        `when`(combineLatest.getData()).thenReturn(dataMap)
+        mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
+        mediaDataFilter.onMediaDataLoaded(KEY_ALT, null, dataGuest)
+        reset(listener)
 
         // and we switch to guest user
         setUser(USER_GUEST)
@@ -213,4 +214,4 @@
 
         verify(mediaDataManager).setTimedOut(eq(KEY), eq(true))
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
index a4ebe1f..84c1bf9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
@@ -16,6 +16,7 @@
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.statusbar.SbnBuilder
 import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
@@ -24,9 +25,13 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
 import org.mockito.junit.MockitoJUnit
 import org.mockito.Mockito.`when` as whenever
@@ -48,6 +53,7 @@
 @RunWith(AndroidTestingRunner::class)
 class MediaDataManagerTest : SysuiTestCase() {
 
+    @JvmField @Rule val mockito = MockitoJUnit.rule()
     @Mock lateinit var mediaControllerFactory: MediaControllerFactory
     @Mock lateinit var controller: MediaController
     lateinit var session: MediaSession
@@ -58,20 +64,38 @@
     @Mock lateinit var broadcastDispatcher: BroadcastDispatcher
     @Mock lateinit var mediaTimeoutListener: MediaTimeoutListener
     @Mock lateinit var mediaResumeListener: MediaResumeListener
+    @Mock lateinit var mediaSessionBasedFilter: MediaSessionBasedFilter
+    @Mock lateinit var mediaDeviceManager: MediaDeviceManager
+    @Mock lateinit var mediaDataCombineLatest: MediaDataCombineLatest
+    @Mock lateinit var mediaDataFilter: MediaDataFilter
+    @Mock lateinit var listener: MediaDataManager.Listener
     @Mock lateinit var pendingIntent: PendingIntent
     @Mock lateinit var activityStarter: ActivityStarter
-    @JvmField @Rule val mockito = MockitoJUnit.rule()
     lateinit var mediaDataManager: MediaDataManager
     lateinit var mediaNotification: StatusBarNotification
+    @Captor lateinit var mediaDataCaptor: ArgumentCaptor<MediaData>
 
     @Before
     fun setup() {
         foregroundExecutor = FakeExecutor(FakeSystemClock())
         backgroundExecutor = FakeExecutor(FakeSystemClock())
-        mediaDataManager = MediaDataManager(context, backgroundExecutor, foregroundExecutor,
-                mediaControllerFactory, broadcastDispatcher, dumpManager,
-                mediaTimeoutListener, mediaResumeListener, activityStarter,
-                useMediaResumption = true, useQsMediaPlayer = true)
+        mediaDataManager = MediaDataManager(
+            context = context,
+            backgroundExecutor = backgroundExecutor,
+            foregroundExecutor = foregroundExecutor,
+            mediaControllerFactory = mediaControllerFactory,
+            broadcastDispatcher = broadcastDispatcher,
+            dumpManager = dumpManager,
+            mediaTimeoutListener = mediaTimeoutListener,
+            mediaResumeListener = mediaResumeListener,
+            mediaSessionBasedFilter = mediaSessionBasedFilter,
+            mediaDeviceManager = mediaDeviceManager,
+            mediaDataCombineLatest = mediaDataCombineLatest,
+            mediaDataFilter = mediaDataFilter,
+            activityStarter = activityStarter,
+            useMediaResumption = true,
+            useQsMediaPlayer = true
+        )
         session = MediaSession(context, "MediaDataManagerTestSession")
         mediaNotification = SbnBuilder().run {
             setPkg(PACKAGE_NAME)
@@ -86,6 +110,12 @@
             putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
         }
         whenever(mediaControllerFactory.create(eq(session.sessionToken))).thenReturn(controller)
+
+        // This is an ugly hack for now. The mediaSessionBasedFilter is one of the internal
+        // listeners in the internal processing pipeline. It receives events, but ince it is a
+        // mock, it doesn't pass those events along the chain to the external listeners. So, just
+        // treat mediaSessionBasedFilter as a listener for testing.
+        listener = mediaSessionBasedFilter
     }
 
     @After
@@ -115,8 +145,6 @@
 
     @Test
     fun testOnMetaDataLoaded_callsListener() {
-        val listener = mock(MediaDataManager.Listener::class.java)
-        mediaDataManager.addListener(listener)
         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
         mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = mock(MediaData::class.java))
         verify(listener).onMediaDataLoaded(eq(KEY), eq(null), anyObject())
@@ -124,84 +152,123 @@
 
     @Test
     fun testOnMetaDataLoaded_conservesActiveFlag() {
-        val listener = TestListener()
         whenever(mediaControllerFactory.create(anyObject())).thenReturn(controller)
         whenever(controller.metadata).thenReturn(metadataBuilder.build())
         mediaDataManager.addListener(listener)
         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
         assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
         assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(listener.data!!.active).isTrue()
+        verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor))
+        assertThat(mediaDataCaptor.value!!.active).isTrue()
     }
 
     @Test
     fun testOnNotificationRemoved_callsListener() {
-        val listener = mock(MediaDataManager.Listener::class.java)
-        mediaDataManager.addListener(listener)
         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
         mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = mock(MediaData::class.java))
         mediaDataManager.onNotificationRemoved(KEY)
-
         verify(listener).onMediaDataRemoved(eq(KEY))
     }
 
     @Test
     fun testOnNotificationRemoved_withResumption() {
         // GIVEN that the manager has a notification with a resume action
-        val listener = TestListener()
-        mediaDataManager.addListener(listener)
         whenever(controller.metadata).thenReturn(metadataBuilder.build())
         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
         assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
         assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
-        val data = listener.data!!
+        verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor))
+        val data = mediaDataCaptor.value
         assertThat(data.resumption).isFalse()
         mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {}))
         // WHEN the notification is removed
         mediaDataManager.onNotificationRemoved(KEY)
         // THEN the media data indicates that it is for resumption
-        assertThat(listener.data!!.resumption).isTrue()
-        // AND the new key is the package name
-        assertThat(listener.key!!).isEqualTo(PACKAGE_NAME)
-        assertThat(listener.oldKey!!).isEqualTo(KEY)
-        assertThat(listener.removedKey).isNull()
+        verify(listener).onMediaDataLoaded(eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor))
+        assertThat(mediaDataCaptor.value.resumption).isTrue()
     }
 
     @Test
     fun testOnNotificationRemoved_twoWithResumption() {
         // GIVEN that the manager has two notifications with resume actions
-        val listener = TestListener()
-        mediaDataManager.addListener(listener)
         whenever(controller.metadata).thenReturn(metadataBuilder.build())
         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
         mediaDataManager.onNotificationAdded(KEY_2, mediaNotification)
         assertThat(backgroundExecutor.runAllReady()).isEqualTo(2)
         assertThat(foregroundExecutor.runAllReady()).isEqualTo(2)
-        val data = listener.data!!
+        verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor))
+        val data = mediaDataCaptor.value
         assertThat(data.resumption).isFalse()
         val resumableData = data.copy(resumeAction = Runnable {})
         mediaDataManager.onMediaDataLoaded(KEY, null, resumableData)
         mediaDataManager.onMediaDataLoaded(KEY_2, null, resumableData)
+        reset(listener)
         // WHEN the first is removed
         mediaDataManager.onNotificationRemoved(KEY)
         // THEN the data is for resumption and the key is migrated to the package name
-        assertThat(listener.data!!.resumption).isTrue()
-        assertThat(listener.key!!).isEqualTo(PACKAGE_NAME)
-        assertThat(listener.oldKey!!).isEqualTo(KEY)
-        assertThat(listener.removedKey).isNull()
+        verify(listener).onMediaDataLoaded(eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor))
+        assertThat(mediaDataCaptor.value.resumption).isTrue()
+        verify(listener, never()).onMediaDataRemoved(eq(KEY))
         // WHEN the second is removed
         mediaDataManager.onNotificationRemoved(KEY_2)
         // THEN the data is for resumption and the second key is removed
-        assertThat(listener.data!!.resumption).isTrue()
-        assertThat(listener.key!!).isEqualTo(PACKAGE_NAME)
-        assertThat(listener.oldKey!!).isEqualTo(PACKAGE_NAME)
-        assertThat(listener.removedKey!!).isEqualTo(KEY_2)
+        verify(listener).onMediaDataLoaded(eq(PACKAGE_NAME), eq(PACKAGE_NAME),
+                capture(mediaDataCaptor))
+        assertThat(mediaDataCaptor.value.resumption).isTrue()
+        verify(listener).onMediaDataRemoved(eq(KEY_2))
+    }
+
+    @Test
+    fun testAppBlockedFromResumption() {
+        // GIVEN that the manager has a notification with a resume action
+        whenever(controller.metadata).thenReturn(metadataBuilder.build())
+        mediaDataManager.onNotificationAdded(KEY, mediaNotification)
+        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor))
+        val data = mediaDataCaptor.value
+        assertThat(data.resumption).isFalse()
+        mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {}))
+
+        // and the manager should block the package from creating resume controls
+        val blocked = mutableSetOf(PACKAGE_NAME, "com.example.app")
+        mediaDataManager.appsBlockedFromResume = blocked
+
+        // WHEN the notification is removed
+        mediaDataManager.onNotificationRemoved(KEY)
+
+        // THEN the media data is removed
+        verify(listener).onMediaDataRemoved(eq(KEY))
+    }
+
+    @Test
+    fun testAppUnblockedFromResumption() {
+        // GIVEN that an app was blocked from resuming
+        val blocked = mutableSetOf(PACKAGE_NAME, "com.example.app")
+        mediaDataManager.appsBlockedFromResume = blocked
+
+        // and GIVEN that the manager has a notification from that app with a resume action
+        whenever(controller.metadata).thenReturn(metadataBuilder.build())
+        mediaDataManager.onNotificationAdded(KEY, mediaNotification)
+        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor))
+        val data = mediaDataCaptor.value
+        assertThat(data.resumption).isFalse()
+        mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {}))
+
+        // WHEN the app is unblocked
+        mediaDataManager.appsBlockedFromResume = mutableSetOf("com.example.app")
+
+        // and the notification is removed
+        mediaDataManager.onNotificationRemoved(KEY)
+
+        // THEN the entry will stay as a resume control
+        verify(listener).onMediaDataLoaded(eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor))
     }
 
     @Test
     fun testAddResumptionControls() {
-        val listener = TestListener()
-        mediaDataManager.addListener(listener)
         // WHEN resumption controls are added`
         val desc = MediaDescription.Builder().run {
             setTitle(SESSION_TITLE)
@@ -212,7 +279,8 @@
         assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
         assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
         // THEN the media data indicates that it is for resumption
-        val data = listener.data!!
+        verify(listener).onMediaDataLoaded(eq(PACKAGE_NAME), eq(null), capture(mediaDataCaptor))
+        val data = mediaDataCaptor.value
         assertThat(data.resumption).isTrue()
         assertThat(data.song).isEqualTo(SESSION_TITLE)
         assertThat(data.app).isEqualTo(APP_NAME)
@@ -221,8 +289,6 @@
 
     @Test
     fun testDismissMedia_listenerCalled() {
-        val listener = mock(MediaDataManager.Listener::class.java)
-        mediaDataManager.addListener(listener)
         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
         mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = mock(MediaData::class.java))
         mediaDataManager.dismissMediaData(KEY, 0L)
@@ -232,26 +298,4 @@
 
         verify(listener).onMediaDataRemoved(eq(KEY))
     }
-
-    /**
-     * Simple implementation of [MediaDataManager.Listener] for the test.
-     *
-     * Giving up on trying to get a mock Listener and ArgumentCaptor to work.
-     */
-    private class TestListener : MediaDataManager.Listener {
-        var data: MediaData? = null
-        var key: String? = null
-        var oldKey: String? = null
-        var removedKey: String? = null
-
-        override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
-            this.key = key
-            this.oldKey = oldKey
-            this.data = data
-        }
-
-        override fun onMediaDataRemoved(key: String) {
-            removedKey = key
-        }
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
index 7bc15dd..fdb432c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
@@ -68,7 +68,6 @@
 public class MediaDeviceManagerTest : SysuiTestCase() {
 
     private lateinit var manager: MediaDeviceManager
-    @Mock private lateinit var mediaDataManager: MediaDataManager
     @Mock private lateinit var lmmFactory: LocalMediaManagerFactory
     @Mock private lateinit var lmm: LocalMediaManager
     @Mock private lateinit var mr2: MediaRouter2Manager
@@ -91,7 +90,7 @@
         fakeFgExecutor = FakeExecutor(FakeSystemClock())
         fakeBgExecutor = FakeExecutor(FakeSystemClock())
         manager = MediaDeviceManager(context, lmmFactory, mr2, fakeFgExecutor, fakeBgExecutor,
-                mediaDataManager, dumpster)
+                dumpster)
         manager.addListener(listener)
 
         // Configure mocks.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt
new file mode 100644
index 0000000..ca8f79d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt
@@ -0,0 +1,122 @@
+/*
+ * 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.media
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+public class MediaPlayerDataTest : SysuiTestCase() {
+
+    companion object {
+        val LOCAL = true
+        val RESUMPTION = true
+    }
+
+    @Before
+    fun setup() {
+        MediaPlayerData.clear()
+    }
+
+    @Test
+    fun addPlayingThenRemote() {
+        val playerIsPlaying = mock(MediaControlPanel::class.java)
+        whenever(playerIsPlaying.isPlaying).thenReturn(true)
+        val dataIsPlaying = createMediaData("app1", LOCAL, !RESUMPTION)
+
+        val playerIsRemote = mock(MediaControlPanel::class.java)
+        whenever(playerIsRemote.isPlaying).thenReturn(false)
+        val dataIsRemote = createMediaData("app2", !LOCAL, !RESUMPTION)
+
+        MediaPlayerData.addMediaPlayer("1", dataIsPlaying, playerIsPlaying)
+        MediaPlayerData.addMediaPlayer("2", dataIsRemote, playerIsRemote)
+
+        val players = MediaPlayerData.players()
+        assertThat(players).hasSize(2)
+        assertThat(players).containsExactly(playerIsPlaying, playerIsRemote).inOrder()
+    }
+
+    @Test
+    fun switchPlayersPlaying() {
+        val playerIsPlaying1 = mock(MediaControlPanel::class.java)
+        whenever(playerIsPlaying1.isPlaying).thenReturn(true)
+        val dataIsPlaying1 = createMediaData("app1", LOCAL, !RESUMPTION)
+
+        val playerIsPlaying2 = mock(MediaControlPanel::class.java)
+        whenever(playerIsPlaying2.isPlaying).thenReturn(false)
+        val dataIsPlaying2 = createMediaData("app2", LOCAL, !RESUMPTION)
+
+        MediaPlayerData.addMediaPlayer("1", dataIsPlaying1, playerIsPlaying1)
+        MediaPlayerData.addMediaPlayer("2", dataIsPlaying2, playerIsPlaying2)
+
+        whenever(playerIsPlaying1.isPlaying).thenReturn(false)
+        whenever(playerIsPlaying2.isPlaying).thenReturn(true)
+
+        MediaPlayerData.addMediaPlayer("1", dataIsPlaying1, playerIsPlaying1)
+        MediaPlayerData.addMediaPlayer("2", dataIsPlaying2, playerIsPlaying2)
+
+        val players = MediaPlayerData.players()
+        assertThat(players).hasSize(2)
+        assertThat(players).containsExactly(playerIsPlaying2, playerIsPlaying1).inOrder()
+    }
+
+    @Test
+    fun fullOrderTest() {
+        val playerIsPlaying = mock(MediaControlPanel::class.java)
+        whenever(playerIsPlaying.isPlaying).thenReturn(true)
+        val dataIsPlaying = createMediaData("app1", LOCAL, !RESUMPTION)
+
+        val playerIsPlayingAndRemote = mock(MediaControlPanel::class.java)
+        whenever(playerIsPlayingAndRemote.isPlaying).thenReturn(true)
+        val dataIsPlayingAndRemote = createMediaData("app2", !LOCAL, !RESUMPTION)
+
+        val playerIsStoppedAndLocal = mock(MediaControlPanel::class.java)
+        whenever(playerIsStoppedAndLocal.isPlaying).thenReturn(false)
+        val dataIsStoppedAndLocal = createMediaData("app3", LOCAL, !RESUMPTION)
+
+        val playerIsStoppedAndRemote = mock(MediaControlPanel::class.java)
+        whenever(playerIsStoppedAndLocal.isPlaying).thenReturn(false)
+        val dataIsStoppedAndRemote = createMediaData("app4", !LOCAL, !RESUMPTION)
+
+        val playerCanResume = mock(MediaControlPanel::class.java)
+        whenever(playerCanResume.isPlaying).thenReturn(false)
+        val dataCanResume = createMediaData("app5", LOCAL, RESUMPTION)
+
+        MediaPlayerData.addMediaPlayer("3", dataIsStoppedAndLocal, playerIsStoppedAndLocal)
+        MediaPlayerData.addMediaPlayer("5", dataIsStoppedAndRemote, playerIsStoppedAndRemote)
+        MediaPlayerData.addMediaPlayer("4", dataCanResume, playerCanResume)
+        MediaPlayerData.addMediaPlayer("1", dataIsPlaying, playerIsPlaying)
+        MediaPlayerData.addMediaPlayer("2", dataIsPlayingAndRemote, playerIsPlayingAndRemote)
+
+        val players = MediaPlayerData.players()
+        assertThat(players).hasSize(5)
+        assertThat(players).containsExactly(playerIsPlaying, playerIsPlayingAndRemote,
+            playerIsStoppedAndLocal, playerCanResume, playerIsStoppedAndRemote).inOrder()
+    }
+
+    private fun createMediaData(app: String, isLocalSession: Boolean, resumption: Boolean) =
+        MediaData(0, false, 0, app, null, null, null, null, emptyList(), emptyList<Int>(), "",
+            null, null, null, true, null, isLocalSession, resumption, null, false)
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt
new file mode 100644
index 0000000..887cc77
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt
@@ -0,0 +1,383 @@
+/*
+ * 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.media
+
+import android.graphics.Color
+import android.media.session.MediaController
+import android.media.session.MediaController.PlaybackInfo
+import android.media.session.MediaSession
+import android.media.session.MediaSessionManager
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.time.FakeSystemClock
+
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.any
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import org.mockito.Mockito.`when` as whenever
+
+private const val PACKAGE = "PKG"
+private const val KEY = "TEST_KEY"
+private const val NOTIF_KEY = "TEST_KEY"
+private const val SESSION_ARTIST = "SESSION_ARTIST"
+private const val SESSION_TITLE = "SESSION_TITLE"
+private const val APP_NAME = "APP_NAME"
+private const val USER_ID = 0
+
+private val info = MediaData(
+    userId = USER_ID,
+    initialized = true,
+    backgroundColor = Color.DKGRAY,
+    app = APP_NAME,
+    appIcon = null,
+    artist = SESSION_ARTIST,
+    song = SESSION_TITLE,
+    artwork = null,
+    actions = emptyList(),
+    actionsToShowInCompact = emptyList(),
+    packageName = PACKAGE,
+    token = null,
+    clickIntent = null,
+    device = null,
+    active = true,
+    resumeAction = null,
+    resumption = false,
+    notificationKey = NOTIF_KEY,
+    hasCheckedForResume = false
+)
+
+private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+public class MediaSessionBasedFilterTest : SysuiTestCase() {
+
+    @JvmField @Rule val mockito = MockitoJUnit.rule()
+
+    // Unit to be tested
+    private lateinit var filter: MediaSessionBasedFilter
+
+    private lateinit var sessionListener: MediaSessionManager.OnActiveSessionsChangedListener
+    @Mock private lateinit var mediaListener: MediaDataManager.Listener
+
+    // MediaSessionBasedFilter dependencies
+    @Mock private lateinit var mediaSessionManager: MediaSessionManager
+    private lateinit var fgExecutor: FakeExecutor
+    private lateinit var bgExecutor: FakeExecutor
+
+    @Mock private lateinit var controller1: MediaController
+    @Mock private lateinit var controller2: MediaController
+    @Mock private lateinit var controller3: MediaController
+    @Mock private lateinit var controller4: MediaController
+
+    private lateinit var token1: MediaSession.Token
+    private lateinit var token2: MediaSession.Token
+    private lateinit var token3: MediaSession.Token
+    private lateinit var token4: MediaSession.Token
+
+    @Mock private lateinit var remotePlaybackInfo: PlaybackInfo
+    @Mock private lateinit var localPlaybackInfo: PlaybackInfo
+
+    private lateinit var session1: MediaSession
+    private lateinit var session2: MediaSession
+    private lateinit var session3: MediaSession
+    private lateinit var session4: MediaSession
+
+    private lateinit var mediaData1: MediaData
+    private lateinit var mediaData2: MediaData
+    private lateinit var mediaData3: MediaData
+    private lateinit var mediaData4: MediaData
+
+    @Before
+    fun setUp() {
+        fgExecutor = FakeExecutor(FakeSystemClock())
+        bgExecutor = FakeExecutor(FakeSystemClock())
+        filter = MediaSessionBasedFilter(context, mediaSessionManager, fgExecutor, bgExecutor)
+
+        // Configure mocks.
+        whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(emptyList())
+
+        session1 = MediaSession(context, "MediaSessionBasedFilter1")
+        session2 = MediaSession(context, "MediaSessionBasedFilter2")
+        session3 = MediaSession(context, "MediaSessionBasedFilter3")
+        session4 = MediaSession(context, "MediaSessionBasedFilter4")
+
+        token1 = session1.sessionToken
+        token2 = session2.sessionToken
+        token3 = session3.sessionToken
+        token4 = session4.sessionToken
+
+        whenever(controller1.getSessionToken()).thenReturn(token1)
+        whenever(controller2.getSessionToken()).thenReturn(token2)
+        whenever(controller3.getSessionToken()).thenReturn(token3)
+        whenever(controller4.getSessionToken()).thenReturn(token4)
+
+        whenever(controller1.getPackageName()).thenReturn(PACKAGE)
+        whenever(controller2.getPackageName()).thenReturn(PACKAGE)
+        whenever(controller3.getPackageName()).thenReturn(PACKAGE)
+        whenever(controller4.getPackageName()).thenReturn(PACKAGE)
+
+        mediaData1 = info.copy(token = token1)
+        mediaData2 = info.copy(token = token2)
+        mediaData3 = info.copy(token = token3)
+        mediaData4 = info.copy(token = token4)
+
+        whenever(remotePlaybackInfo.getPlaybackType()).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
+        whenever(localPlaybackInfo.getPlaybackType()).thenReturn(PlaybackInfo.PLAYBACK_TYPE_LOCAL)
+
+        whenever(controller1.getPlaybackInfo()).thenReturn(localPlaybackInfo)
+        whenever(controller2.getPlaybackInfo()).thenReturn(localPlaybackInfo)
+        whenever(controller3.getPlaybackInfo()).thenReturn(localPlaybackInfo)
+        whenever(controller4.getPlaybackInfo()).thenReturn(localPlaybackInfo)
+
+        // Capture listener
+        bgExecutor.runAllReady()
+        val listenerCaptor = ArgumentCaptor.forClass(
+                MediaSessionManager.OnActiveSessionsChangedListener::class.java)
+        verify(mediaSessionManager).addOnActiveSessionsChangedListener(
+                listenerCaptor.capture(), any())
+        sessionListener = listenerCaptor.value
+
+        filter.addListener(mediaListener)
+    }
+
+    @After
+    fun tearDown() {
+        session1.release()
+        session2.release()
+        session3.release()
+        session4.release()
+    }
+
+    @Test
+    fun noMediaSession_loadedEventNotFiltered() {
+        filter.onMediaDataLoaded(KEY, null, mediaData1)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1))
+    }
+
+    @Test
+    fun noMediaSession_removedEventNotFiltered() {
+        filter.onMediaDataRemoved(KEY)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        verify(mediaListener).onMediaDataRemoved(eq(KEY))
+    }
+
+    @Test
+    fun matchingMediaSession_loadedEventNotFiltered() {
+        // GIVEN an active session
+        val controllers = listOf(controller1)
+        whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers)
+        sessionListener.onActiveSessionsChanged(controllers)
+        // WHEN a loaded event is received that matches the session
+        filter.onMediaDataLoaded(KEY, null, mediaData1)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        // THEN the event is not filtered
+        verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1))
+    }
+
+    @Test
+    fun matchingMediaSession_removedEventNotFiltered() {
+        // GIVEN an active session
+        val controllers = listOf(controller1)
+        whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers)
+        sessionListener.onActiveSessionsChanged(controllers)
+        // WHEN a removed event is received
+        filter.onMediaDataRemoved(KEY)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        // THEN the event is not filtered
+        verify(mediaListener).onMediaDataRemoved(eq(KEY))
+    }
+
+    @Test
+    fun remoteSession_loadedEventNotFiltered() {
+        // GIVEN a remove session
+        whenever(controller1.getPlaybackInfo()).thenReturn(remotePlaybackInfo)
+        val controllers = listOf(controller1)
+        whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers)
+        sessionListener.onActiveSessionsChanged(controllers)
+        // WHEN a loaded event is received that matche the session
+        filter.onMediaDataLoaded(KEY, null, mediaData1)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        // THEN the event is not filtered
+        verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1))
+    }
+
+    @Test
+    fun remoteAndLocalSessions_localLoadedEventFiltered() {
+        // GIVEN remote and local sessions
+        whenever(controller1.getPlaybackInfo()).thenReturn(remotePlaybackInfo)
+        val controllers = listOf(controller1, controller2)
+        whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers)
+        sessionListener.onActiveSessionsChanged(controllers)
+        // WHEN a loaded event is received that matches the remote session
+        filter.onMediaDataLoaded(KEY, null, mediaData1)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        // THEN the event is not filtered
+        verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1))
+        // WHEN a loaded event is received that matches the local session
+        filter.onMediaDataLoaded(KEY, null, mediaData2)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        // THEN the event is filtered
+        verify(mediaListener, never()).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData2))
+    }
+
+    @Test
+    fun remoteAndLocalHaveDifferentKeys_localLoadedEventFiltered() {
+        // GIVEN remote and local sessions
+        val key1 = "KEY_1"
+        val key2 = "KEY_2"
+        whenever(controller1.getPlaybackInfo()).thenReturn(remotePlaybackInfo)
+        val controllers = listOf(controller1, controller2)
+        whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers)
+        sessionListener.onActiveSessionsChanged(controllers)
+        // WHEN a loaded event is received that matches the remote session
+        filter.onMediaDataLoaded(key1, null, mediaData1)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        // THEN the event is not filtered
+        verify(mediaListener).onMediaDataLoaded(eq(key1), eq(null), eq(mediaData1))
+        // WHEN a loaded event is received that matches the local session
+        filter.onMediaDataLoaded(key2, null, mediaData2)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        // THEN the event is filtered
+        verify(mediaListener, never()).onMediaDataLoaded(eq(key2), eq(null), eq(mediaData2))
+        // AND there should be a removed event for key2
+        verify(mediaListener).onMediaDataRemoved(eq(key2))
+    }
+
+    @Test
+    fun multipleRemoteSessions_loadedEventNotFiltered() {
+        // GIVEN two remote sessions
+        whenever(controller1.getPlaybackInfo()).thenReturn(remotePlaybackInfo)
+        whenever(controller2.getPlaybackInfo()).thenReturn(remotePlaybackInfo)
+        val controllers = listOf(controller1, controller2)
+        whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers)
+        sessionListener.onActiveSessionsChanged(controllers)
+        // WHEN a loaded event is received that matches the remote session
+        filter.onMediaDataLoaded(KEY, null, mediaData1)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        // THEN the event is not filtered
+        verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1))
+        // WHEN a loaded event is received that matches the local session
+        filter.onMediaDataLoaded(KEY, null, mediaData2)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        // THEN the event is not filtered
+        verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData2))
+    }
+
+    @Test
+    fun multipleOtherSessions_loadedEventNotFiltered() {
+        // GIVEN multiple active sessions from other packages
+        val controllers = listOf(controller1, controller2, controller3, controller4)
+        whenever(controller1.getPackageName()).thenReturn("PKG_1")
+        whenever(controller2.getPackageName()).thenReturn("PKG_2")
+        whenever(controller3.getPackageName()).thenReturn("PKG_3")
+        whenever(controller4.getPackageName()).thenReturn("PKG_4")
+        whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers)
+        sessionListener.onActiveSessionsChanged(controllers)
+        // WHEN a loaded event is received
+        filter.onMediaDataLoaded(KEY, null, mediaData1)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        // THEN the event is not filtered
+        verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1))
+    }
+
+    @Test
+    fun doNotFilterDuringKeyMigration() {
+        val key1 = "KEY_1"
+        val key2 = "KEY_2"
+        // GIVEN a loaded event
+        filter.onMediaDataLoaded(key1, null, mediaData2)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        reset(mediaListener)
+        // GIVEN remote and local sessions
+        whenever(controller1.getPlaybackInfo()).thenReturn(remotePlaybackInfo)
+        val controllers = listOf(controller1, controller2)
+        whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers)
+        sessionListener.onActiveSessionsChanged(controllers)
+        // WHEN a loaded event is received that matches the local session but it is a key migration
+        filter.onMediaDataLoaded(key2, key1, mediaData2)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        // THEN the key migration event is fired
+        verify(mediaListener).onMediaDataLoaded(eq(key2), eq(key1), eq(mediaData2))
+    }
+
+    @Test
+    fun filterAfterKeyMigration() {
+        val key1 = "KEY_1"
+        val key2 = "KEY_2"
+        // GIVEN a loaded event
+        filter.onMediaDataLoaded(key1, null, mediaData1)
+        filter.onMediaDataLoaded(key1, null, mediaData2)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        reset(mediaListener)
+        // GIVEN remote and local sessions
+        whenever(controller1.getPlaybackInfo()).thenReturn(remotePlaybackInfo)
+        val controllers = listOf(controller1, controller2)
+        whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers)
+        sessionListener.onActiveSessionsChanged(controllers)
+        // GIVEN that the keys have been migrated
+        filter.onMediaDataLoaded(key2, key1, mediaData1)
+        filter.onMediaDataLoaded(key2, key1, mediaData2)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        reset(mediaListener)
+        // WHEN a loaded event is received that matches the local session
+        filter.onMediaDataLoaded(key2, null, mediaData2)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        // THEN the key migration event is filtered
+        verify(mediaListener, never()).onMediaDataLoaded(eq(key2), eq(null), eq(mediaData2))
+        // WHEN a loaded event is received that matches the remote session
+        filter.onMediaDataLoaded(key2, null, mediaData1)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        // THEN the key migration event is fired
+        verify(mediaListener).onMediaDataLoaded(eq(key2), eq(null), eq(mediaData1))
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarButtonTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarButtonTest.java
similarity index 94%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarButtonTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarButtonTest.java
index aae0757..37b7cbe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarButtonTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarButtonTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar;
 
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
@@ -35,6 +35,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.SysuiTestableContext;
 import com.android.systemui.assist.AssistManager;
+import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
@@ -65,6 +66,7 @@
         mDependency.injectMockDependency(AssistManager.class);
         mDependency.injectMockDependency(OverviewProxyService.class);
         mDependency.injectMockDependency(KeyguardStateController.class);
+        mDependency.injectMockDependency(NavigationBarController.class);
         mNavBar = new NavigationBarView(context, null);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NavigationBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
similarity index 73%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/NavigationBarControllerTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
index 3c66ac6..2e4d8a7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NavigationBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar;
+package com.android.systemui.navigationbar;
 
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
@@ -36,12 +36,32 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
 import android.util.SparseArray;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.Dependency;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.phone.NavigationBarFragment;
+import com.android.systemui.accessibility.SystemActions;
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.model.SysUiState;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.stackdivider.Divider;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.phone.ShadeController;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+
+import java.util.Optional;
 
 import org.junit.After;
 import org.junit.Before;
@@ -55,27 +75,47 @@
 public class NavigationBarControllerTest extends SysuiTestCase {
 
     private NavigationBarController mNavigationBarController;
-    private NavigationBarFragment mDefaultNavBar;
-    private NavigationBarFragment mSecondaryNavBar;
+    private NavigationBar mDefaultNavBar;
+    private NavigationBar mSecondaryNavBar;
 
     private static final int SECONDARY_DISPLAY = 1;
 
     @Before
     public void setUp() {
         mNavigationBarController = spy(
-                new NavigationBarController(mContext, Dependency.get(Dependency.MAIN_HANDLER),
-                        mock(CommandQueue.class)));
+                new NavigationBarController(mContext,
+                        mock(WindowManager.class),
+                        () -> mock(AssistManager.class),
+                        mock(AccessibilityManager.class),
+                        mock(AccessibilityManagerWrapper.class),
+                        mock(DeviceProvisionedController.class),
+                        mock(MetricsLogger.class),
+                        mock(OverviewProxyService.class), 
+                        mock(NavigationModeController.class),
+                        mock(StatusBarStateController.class),
+                        mock(SysUiState.class),
+                        mock(BroadcastDispatcher.class),
+                        mock(CommandQueue.class),
+                        mock(Divider.class),
+                        Optional.of(mock(Recents.class)),
+                        () -> mock(StatusBar.class),
+                        mock(ShadeController.class),
+                        mock(NotificationRemoteInputManager.class),
+                        mock(SystemActions.class),
+                        Dependency.get(Dependency.MAIN_HANDLER),
+                        mock(UiEventLogger.class),
+                        mock(ConfigurationController.class)));
         initializeNavigationBars();
     }
 
     private void initializeNavigationBars() {
         mNavigationBarController.mNavigationBars = mock(SparseArray.class);
-        mDefaultNavBar = mock(NavigationBarFragment.class);
+        mDefaultNavBar = mock(NavigationBar.class);
         mDefaultNavBar.mDisplayId = DEFAULT_DISPLAY;
         doReturn(mDefaultNavBar)
                 .when(mNavigationBarController.mNavigationBars).get(DEFAULT_DISPLAY);
 
-        mSecondaryNavBar = mock(NavigationBarFragment.class);
+        mSecondaryNavBar = mock(NavigationBar.class);
         mSecondaryNavBar.mDisplayId = SECONDARY_DISPLAY;
         doReturn(mSecondaryNavBar)
                 .when(mNavigationBarController.mNavigationBars).get(SECONDARY_DISPLAY);
@@ -90,22 +130,22 @@
 
     @Test
     public void testCreateNavigationBarsIncludeDefaultTrue() {
-        doNothing().when(mNavigationBarController).createNavigationBar(any(), any());
+        doNothing().when(mNavigationBarController).createNavigationBar(any(), any(), any());
 
         mNavigationBarController.createNavigationBars(true, null);
 
         verify(mNavigationBarController).createNavigationBar(
-                argThat(display -> display.getDisplayId() == DEFAULT_DISPLAY), any());
+                argThat(display -> display.getDisplayId() == DEFAULT_DISPLAY), any(), any());
     }
 
     @Test
     public void testCreateNavigationBarsIncludeDefaultFalse() {
-        doNothing().when(mNavigationBarController).createNavigationBar(any(), any());
+        doNothing().when(mNavigationBarController).createNavigationBar(any(), any(), any());
 
         mNavigationBarController.createNavigationBars(false, null);
 
         verify(mNavigationBarController, never()).createNavigationBar(
-                argThat(display -> display.getDisplayId() == DEFAULT_DISPLAY), any());
+                argThat(display -> display.getDisplayId() == DEFAULT_DISPLAY), any(), any());
     }
 
     // Tests if NPE occurs when call checkNavBarModes() with invalid display.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarInflaterViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarInflaterViewTest.java
similarity index 90%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarInflaterViewTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarInflaterViewTest.java
index 80e33fb..7369c82 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarInflaterViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarInflaterViewTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar;
 
 import static org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.doNothing;
@@ -32,6 +32,9 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.assist.AssistManager;
+import com.android.systemui.navigationbar.buttons.ButtonDispatcher;
+import com.android.systemui.navigationbar.NavigationBarInflaterView;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.recents.OverviewProxyService;
 
 import org.junit.After;
@@ -54,6 +57,7 @@
         mDependency.injectMockDependency(AssistManager.class);
         mDependency.injectMockDependency(OverviewProxyService.class);
         mDependency.injectMockDependency(NavigationModeController.class);
+        mDependency.injectMockDependency(NavigationBarController.class);
 
         mNavBarInflaterView = spy(new NavigationBarInflaterView(mContext, null));
         doNothing().when(mNavBarInflaterView).createInflaters();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarRotationContextTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java
similarity index 94%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarRotationContextTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java
index f665241..51cf501 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarRotationContextTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar;
 
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -30,7 +30,9 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.SysuiTestableContext;
-import com.android.systemui.statusbar.policy.KeyButtonDrawable;
+import com.android.systemui.navigationbar.buttons.KeyButtonDrawable;
+import com.android.systemui.navigationbar.RotationButton;
+import com.android.systemui.navigationbar.RotationButtonController;
 import com.android.systemui.statusbar.policy.RotationLockController;
 
 import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
new file mode 100644
index 0000000..a643c2d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -0,0 +1,240 @@
+/*
+ * 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.navigationbar;
+
+import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT;
+import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
+import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT;
+import static android.inputmethodservice.InputMethodService.IME_VISIBLE;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
+
+import static com.android.systemui.navigationbar.NavigationBar.NavBarActionEvent.NAVBAR_ASSIST_LONGPRESS;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doNothing;
+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.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.IntentFilter;
+import android.hardware.display.DisplayManagerGlobal;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
+import android.view.accessibility.AccessibilityManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.Dependency;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.SysuiTestableContext;
+import com.android.systemui.accessibility.SystemActions;
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.model.SysUiState;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.stackdivider.Divider;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.phone.ShadeController;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.utils.leaks.LeakCheckedTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Optional;
+
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper(setAsMainLooper = true)
+@SmallTest
+public class NavigationBarTest extends SysuiTestCase {
+    private static final int EXTERNAL_DISPLAY_ID = 2;
+
+    private NavigationBar mNavigationBar;
+    private NavigationBar mExternalDisplayNavigationBar;
+
+    private SysuiTestableContext mSysuiTestableContextExternal;
+    private OverviewProxyService mOverviewProxyService;
+    private CommandQueue mCommandQueue;
+    private SysUiState mMockSysUiState;
+    private Handler mHandler;
+    @Mock
+    private BroadcastDispatcher mBroadcastDispatcher;
+    @Mock
+    private UiEventLogger mUiEventLogger;
+
+    @Rule
+    public final LeakCheckedTest.SysuiLeakCheck mLeakCheck = new LeakCheckedTest.SysuiLeakCheck();
+    private AccessibilityManagerWrapper mAccessibilityWrapper =
+            new AccessibilityManagerWrapper(mContext);
+
+    @Before
+    public void setup() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mCommandQueue = new CommandQueue(mContext);
+        setupSysuiDependency();
+        mDependency.injectMockDependency(AssistManager.class);
+        mDependency.injectMockDependency(KeyguardStateController.class);
+        mDependency.injectMockDependency(StatusBarStateController.class);
+        mDependency.injectMockDependency(NavigationBarController.class);
+        mDependency.injectMockDependency(Divider.class);
+        mOverviewProxyService = mDependency.injectMockDependency(OverviewProxyService.class);
+        TestableLooper.get(this).runWithLooper(() -> {
+            mHandler = new Handler();
+            mNavigationBar = createNavBar(mContext);
+            mExternalDisplayNavigationBar = createNavBar(mSysuiTestableContextExternal);
+        });
+    }
+
+    private void setupSysuiDependency() {
+        Display display = new Display(DisplayManagerGlobal.getInstance(), EXTERNAL_DISPLAY_ID,
+                new DisplayInfo(), DEFAULT_DISPLAY_ADJUSTMENTS);
+        mSysuiTestableContextExternal = (SysuiTestableContext) getContext().createDisplayContext(
+                display);
+
+        WindowManager windowManager = mock(WindowManager.class);
+        Display defaultDisplay = mContext.getSystemService(WindowManager.class).getDefaultDisplay();
+        when(windowManager.getDefaultDisplay()).thenReturn(
+                defaultDisplay);
+        WindowMetrics metrics = mContext.getSystemService(WindowManager.class)
+                .getMaximumWindowMetrics();
+        when(windowManager.getMaximumWindowMetrics()).thenReturn(metrics);
+        doNothing().when(windowManager).addView(any(), any());
+        mContext.addMockSystemService(Context.WINDOW_SERVICE, windowManager);
+        mSysuiTestableContextExternal.addMockSystemService(Context.WINDOW_SERVICE, windowManager);
+
+        mDependency.injectTestDependency(Dependency.BG_LOOPER, Looper.getMainLooper());
+        mDependency.injectTestDependency(AccessibilityManagerWrapper.class, mAccessibilityWrapper);
+
+        mMockSysUiState = mock(SysUiState.class);
+        when(mMockSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mMockSysUiState);
+    }
+
+    @Test
+    public void testHomeLongPress() {
+        mNavigationBar.onViewAttachedToWindow(mNavigationBar.createView(null));
+        mNavigationBar.onHomeLongClick(mNavigationBar.getView());
+
+        verify(mUiEventLogger, times(1)).log(NAVBAR_ASSIST_LONGPRESS);
+    }
+
+    @Test
+    public void testRegisteredWithDispatcher() {
+        mNavigationBar.onViewAttachedToWindow(mNavigationBar.createView(null));
+        verify(mBroadcastDispatcher).registerReceiverWithHandler(
+                any(BroadcastReceiver.class),
+                any(IntentFilter.class),
+                any(Handler.class),
+                any(UserHandle.class));
+    }
+
+    @Test
+    public void testSetImeWindowStatusWhenImeSwitchOnDisplay() {
+        // Create default & external NavBar fragment.
+        NavigationBar defaultNavBar = mNavigationBar;
+        NavigationBar externalNavBar = mExternalDisplayNavigationBar;
+        doNothing().when(defaultNavBar).checkNavBarModes();
+        doNothing().when(externalNavBar).checkNavBarModes();
+        defaultNavBar.createView(null);
+        externalNavBar.createView(null);
+
+        // Set IME window status for default NavBar.
+        mCommandQueue.setImeWindowStatus(DEFAULT_DISPLAY, null, IME_VISIBLE,
+                BACK_DISPOSITION_DEFAULT, true, false);
+        processAllMessages();
+
+        // Verify IME window state will be updated in default NavBar & external NavBar state reset.
+        assertEquals(NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_SHOWN,
+                defaultNavBar.getNavigationIconHints());
+        assertFalse((externalNavBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0);
+        assertFalse((externalNavBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0);
+
+        // Set IME window status for external NavBar.
+        mCommandQueue.setImeWindowStatus(EXTERNAL_DISPLAY_ID, null,
+                IME_VISIBLE, BACK_DISPOSITION_DEFAULT, true, false);
+        processAllMessages();
+
+        // Verify IME window state will be updated in external NavBar & default NavBar state reset.
+        assertEquals(NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_SHOWN,
+                externalNavBar.getNavigationIconHints());
+        assertFalse((defaultNavBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0);
+        assertFalse((defaultNavBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0);
+    }
+
+    private NavigationBar createNavBar(Context context) {
+        DeviceProvisionedController deviceProvisionedController =
+                mock(DeviceProvisionedController.class);
+        when(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
+        assertNotNull(mAccessibilityWrapper);
+        return spy(new NavigationBar(context,
+                mock(WindowManager.class),
+                () -> mock(AssistManager.class),
+                mock(AccessibilityManager.class),
+                context.getDisplayId() == DEFAULT_DISPLAY ? mAccessibilityWrapper
+                        : mock(AccessibilityManagerWrapper.class),
+                deviceProvisionedController,
+                new MetricsLogger(),
+                mOverviewProxyService,
+                mock(NavigationModeController.class),
+                mock(StatusBarStateController.class),
+                mMockSysUiState,
+                mBroadcastDispatcher,
+                mCommandQueue,
+                mock(Divider.class),
+                Optional.of(mock(Recents.class)),
+                () -> mock(StatusBar.class),
+                mock(ShadeController.class),
+                mock(NotificationRemoteInputManager.class),
+                mock(SystemActions.class),
+                mHandler,
+                mUiEventLogger));
+    }
+
+    private void processAllMessages() {
+        TestableLooper.get(this).processAllMessages();
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarTransitionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTransitionsTest.java
similarity index 76%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarTransitionsTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTransitionsTest.java
index 14c6e9f9..7e0920c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarTransitionsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTransitionsTest.java
@@ -1,18 +1,20 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * 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
+ * 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.
+ * 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.phone;
+package com.android.systemui.navigationbar;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -30,9 +32,13 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.assist.AssistManager;
+import com.android.systemui.navigationbar.NavigationBarTransitions;
+import com.android.systemui.navigationbar.NavigationBarView;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.phone.BarTransitions;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.Before;
@@ -53,6 +59,7 @@
         mDependency.injectMockDependency(OverviewProxyService.class);
         mDependency.injectMockDependency(StatusBarStateController.class);
         mDependency.injectMockDependency(KeyguardStateController.class);
+        mDependency.injectMockDependency(NavigationBarController.class);
         doReturn(mContext)
                 .when(mDependency.injectMockDependency(NavigationModeController.class))
                 .getCurrentUserContext();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyButtonViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/KeyButtonViewTest.java
similarity index 79%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyButtonViewTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/KeyButtonViewTest.java
index d52d686..3f10c8d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyButtonViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/KeyButtonViewTest.java
@@ -1,18 +1,20 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * 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
+ * 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.
+ * 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.policy;
+package com.android.systemui.navigationbar.buttons;
 
 import static android.view.KeyEvent.ACTION_DOWN;
 import static android.view.KeyEvent.ACTION_UP;
@@ -23,12 +25,12 @@
 import static android.view.KeyEvent.KEYCODE_BACK;
 import static android.view.KeyEvent.KEYCODE_HOME;
 
-import static com.android.systemui.statusbar.policy.KeyButtonView.NavBarButtonEvent.NAVBAR_BACK_BUTTON_LONGPRESS;
-import static com.android.systemui.statusbar.policy.KeyButtonView.NavBarButtonEvent.NAVBAR_BACK_BUTTON_TAP;
-import static com.android.systemui.statusbar.policy.KeyButtonView.NavBarButtonEvent.NAVBAR_HOME_BUTTON_LONGPRESS;
-import static com.android.systemui.statusbar.policy.KeyButtonView.NavBarButtonEvent.NAVBAR_HOME_BUTTON_TAP;
-import static com.android.systemui.statusbar.policy.KeyButtonView.NavBarButtonEvent.NAVBAR_OVERVIEW_BUTTON_LONGPRESS;
-import static com.android.systemui.statusbar.policy.KeyButtonView.NavBarButtonEvent.NAVBAR_OVERVIEW_BUTTON_TAP;
+import static com.android.systemui.navigationbar.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_BACK_BUTTON_LONGPRESS;
+import static com.android.systemui.navigationbar.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_BACK_BUTTON_TAP;
+import static com.android.systemui.navigationbar.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_HOME_BUTTON_LONGPRESS;
+import static com.android.systemui.navigationbar.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_HOME_BUTTON_TAP;
+import static com.android.systemui.navigationbar.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_OVERVIEW_BUTTON_LONGPRESS;
+import static com.android.systemui.navigationbar.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_OVERVIEW_BUTTON_TAP;
 
 import static junit.framework.Assert.assertEquals;
 
@@ -52,6 +54,7 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.navigationbar.buttons.KeyButtonView;
 import com.android.systemui.recents.OverviewProxyService;
 
 import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarContextTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/NavigationBarContextTest.java
similarity index 95%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarContextTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/NavigationBarContextTest.java
index 1fb28f0..d56aa77 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarContextTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/NavigationBarContextTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -11,10 +11,10 @@
  * 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
+ * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar.buttons;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -35,7 +35,9 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.assist.AssistManager;
-import com.android.systemui.statusbar.policy.KeyButtonDrawable;
+import com.android.systemui.navigationbar.buttons.ContextualButton;
+import com.android.systemui.navigationbar.buttons.ContextualButtonGroup;
+import com.android.systemui.navigationbar.buttons.KeyButtonDrawable;
 
 import org.junit.Before;
 import org.junit.Ignore;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NearestTouchFrameTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/NearestTouchFrameTest.java
similarity index 91%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NearestTouchFrameTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/NearestTouchFrameTest.java
index a04bcc0..0320103 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NearestTouchFrameTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/NearestTouchFrameTest.java
@@ -1,18 +1,20 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * 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
+ * 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.
+ * 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.phone;
+package com.android.systemui.navigationbar.buttons;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
@@ -31,6 +33,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.navigationbar.buttons.NearestTouchFrame;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedAnimationControllerTest.java
index 73164b5..7fabf82 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedAnimationControllerTest.java
@@ -54,8 +54,7 @@
         MockitoAnnotations.initMocks(this);
 
         mTutorialHandler = new OneHandedTutorialHandler(mContext);
-        mOneHandedAnimationController = new OneHandedAnimationController(
-                new OneHandedSurfaceTransactionHelper(mContext));
+        mOneHandedAnimationController = new OneHandedAnimationController(mContext);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedGestureHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedGestureHandlerTest.java
index f4c0700..95a230f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedGestureHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedGestureHandlerTest.java
@@ -31,7 +31,7 @@
 
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.phone.NavigationModeController;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.wm.shell.common.DisplayController;
 
 import org.junit.Before;
@@ -79,7 +79,7 @@
 
     @Test
     public void testOneHandedManager_registerForDisplayAreaOrganizer() {
-        verify(mMockDisplayAreaOrganizer, times(1)).registerTransitionCallback(mGestureHandler);
+        verify(mMockDisplayAreaOrganizer).registerTransitionCallback(mGestureHandler);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedManagerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedManagerImplTest.java
index 763f6e4..d7dba5f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedManagerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedManagerImplTest.java
@@ -87,10 +87,8 @@
 
     @Test
     public void testDefaultShouldNotInOneHanded() {
-        final OneHandedSurfaceTransactionHelper transactionHelper =
-                new OneHandedSurfaceTransactionHelper(mContext);
         final OneHandedAnimationController animationController = new OneHandedAnimationController(
-                transactionHelper);
+                mContext);
         OneHandedDisplayAreaOrganizer displayAreaOrganizer = new OneHandedDisplayAreaOrganizer(
                 mContext, mMockDisplayController, animationController, mMockTutorialHandler);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedSettingsUtilTest.java b/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedSettingsUtilTest.java
index f81d047..990eb63 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedSettingsUtilTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedSettingsUtilTest.java
@@ -40,14 +40,12 @@
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class OneHandedSettingsUtilTest extends OneHandedTestCase {
-    OneHandedSettingsUtil mOneHandedSettingsUtil;
     ContentResolver mContentResolver;
     ContentObserver mContentObserver;
     boolean mOnChanged;
 
     @Before
     public void setUp() {
-        mOneHandedSettingsUtil = new OneHandedSettingsUtil();
         mContentResolver = mContext.getContentResolver();
         mContentObserver = new ContentObserver(mContext.getMainThreadHandler()) {
             @Override
@@ -60,20 +58,20 @@
 
     @Test
     public void testRegisterSecureKeyObserver() {
-        final Uri result = mOneHandedSettingsUtil.registerSettingsKeyObserver(
+        final Uri result = OneHandedSettingsUtil.registerSettingsKeyObserver(
                 Settings.Secure.ONE_HANDED_MODE_ENABLED, mContentResolver, mContentObserver);
 
         assertThat(result).isNotNull();
 
-        mOneHandedSettingsUtil.registerSettingsKeyObserver(
+        OneHandedSettingsUtil.registerSettingsKeyObserver(
                 Settings.Secure.ONE_HANDED_MODE_ENABLED, mContentResolver, mContentObserver);
     }
 
     @Test
     public void testUnregisterSecureKeyObserver() {
-        mOneHandedSettingsUtil.registerSettingsKeyObserver(
+        OneHandedSettingsUtil.registerSettingsKeyObserver(
                 Settings.Secure.ONE_HANDED_MODE_ENABLED, mContentResolver, mContentObserver);
-        mOneHandedSettingsUtil.unregisterSettingsKeyObserver(mContentResolver, mContentObserver);
+        OneHandedSettingsUtil.unregisterSettingsKeyObserver(mContentResolver, mContentObserver);
 
         assertThat(mOnChanged).isFalse();
 
@@ -85,19 +83,19 @@
 
     @Test
     public void testGetSettingsIsOneHandedModeEnabled() {
-        assertThat(mOneHandedSettingsUtil.getSettingsOneHandedModeEnabled(
+        assertThat(OneHandedSettingsUtil.getSettingsOneHandedModeEnabled(
                 mContentResolver)).isAnyOf(true, false);
     }
 
     @Test
     public void testGetSettingsTapsAppToExit() {
-        assertThat(mOneHandedSettingsUtil.getSettingsTapsAppToExit(
+        assertThat(OneHandedSettingsUtil.getSettingsTapsAppToExit(
                 mContentResolver)).isAnyOf(true, false);
     }
 
     @Test
     public void testGetSettingsOneHandedModeTimeout() {
-        assertThat(mOneHandedSettingsUtil.getSettingsOneHandedModeTimeout(
+        assertThat(OneHandedSettingsUtil.getSettingsOneHandedModeTimeout(
                 mContentResolver)).isAnyOf(
                 ONE_HANDED_TIMEOUT_NEVER,
                 ONE_HANDED_TIMEOUT_SHORT_IN_SECONDS,
@@ -107,7 +105,7 @@
 
     @Test
     public void testGetSettingsSwipeToNotificationEnabled() {
-        assertThat(mOneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
+        assertThat(OneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
                 mContentResolver)).isAnyOf(true, false);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedTouchHandlerTest.java
index 15881a2..8ae632d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedTouchHandlerTest.java
@@ -29,7 +29,7 @@
 
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.phone.NavigationModeController;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.wm.shell.common.DisplayController;
 
 import org.junit.Before;
@@ -77,8 +77,7 @@
 
     @Test
     public void testOneHandedManager_registerForDisplayAreaOrganizer() {
-        verify(mMockDisplayAreaOrganizer, times(1))
-                .registerTransitionCallback(mTouchHandler);
+        verify(mMockDisplayAreaOrganizer).registerTransitionCallback(mTouchHandler);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedTutorialHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedTutorialHandlerTest.java
index f2b77a0..c75a8d2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedTutorialHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedTutorialHandlerTest.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.onehanded;
 
-import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import android.testing.AndroidTestingRunner;
@@ -26,7 +25,7 @@
 
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.phone.NavigationModeController;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.wm.shell.common.DisplayController;
 
 import org.junit.Before;
@@ -75,7 +74,6 @@
 
     @Test
     public void testOneHandedManager_registerForDisplayAreaOrganizer() {
-        verify(mMockDisplayAreaOrganizer, times(1))
-                .registerTransitionCallback(mTutorialHandler);
+        verify(mMockDisplayAreaOrganizer).registerTransitionCallback(mTutorialHandler);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedUITest.java b/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedUITest.java
index 6db2679..f0e713e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedUITest.java
@@ -16,8 +16,7 @@
 
 package com.android.systemui.onehanded;
 
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.times;
+import static org.junit.Assume.assumeTrue;
 import static org.mockito.Mockito.verify;
 
 import android.os.SystemProperties;
@@ -44,7 +43,6 @@
 public class OneHandedUITest extends OneHandedTestCase {
     private static final String SUPPORT_ONE_HANDED_MODE = "ro.support_one_handed_mode";
 
-    boolean mIsSupportOneHandedMode;
     CommandQueue mCommandQueue;
     KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     OneHandedUI mOneHandedUI;
@@ -52,110 +50,58 @@
     @Mock
     OneHandedManagerImpl mMockOneHandedManagerImpl;
     @Mock
-    OneHandedSettingsUtil mMockSettingsUtil;
-    @Mock
     OneHandedTimeoutHandler mMockTimeoutHandler;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mIsSupportOneHandedMode = SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false);
         mCommandQueue = new CommandQueue(mContext);
         mScreenLifecycle = new ScreenLifecycle();
         mOneHandedUI = new OneHandedUI(mContext,
                 mCommandQueue,
                 mMockOneHandedManagerImpl,
-                mMockSettingsUtil,
                 mScreenLifecycle);
         mOneHandedUI.start();
         mKeyguardUpdateMonitor = mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
     }
 
+    @Before
+    public void assumeOneHandedModeSupported() {
+        assumeTrue(SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false));
+    }
+
     @Test
     public void testStartOneHanded() {
-        // Bypass test if device not support one-handed mode
-        if (!mIsSupportOneHandedMode) {
-            return;
-        }
         mOneHandedUI.startOneHanded();
 
-        verify(mMockOneHandedManagerImpl, times(1)).startOneHanded();
+        verify(mMockOneHandedManagerImpl).startOneHanded();
     }
 
     @Test
     public void testStopOneHanded() {
-        // Bypass test if device not support one-handed mode
-        if (!mIsSupportOneHandedMode) {
-            return;
-        }
         mOneHandedUI.stopOneHanded();
 
-        verify(mMockOneHandedManagerImpl, times(1)).stopOneHanded();
-    }
-
-    @Test
-    public void testRegisterSettingsObserver_forEnabled() {
-        // Bypass test if device not support one-handed mode
-        if (!mIsSupportOneHandedMode) {
-            return;
-        }
-        final String key = Settings.Secure.ONE_HANDED_MODE_ENABLED;
-
-        verify(mMockSettingsUtil, times(1)).registerSettingsKeyObserver(key, any(), any());
-    }
-
-    @Test
-    public void testRegisterSettingsObserver_forTimeout() {
-        // Bypass test if device not support one-handed mode
-        if (!mIsSupportOneHandedMode) {
-            return;
-        }
-        final String key = Settings.Secure.ONE_HANDED_MODE_TIMEOUT;
-
-        verify(mMockSettingsUtil, times(1)).registerSettingsKeyObserver(key, any(), any());
-    }
-
-    @Test
-    public void testRegisterSettingsObserver_forTapAppExit() {
-        // Bypass test if device not support one-handed mode
-        if (!mIsSupportOneHandedMode) {
-            return;
-        }
-        final String key = Settings.Secure.TAPS_APP_TO_EXIT;
-
-        verify(mMockSettingsUtil, times(1)).registerSettingsKeyObserver(key, any(), any());
+        verify(mMockOneHandedManagerImpl).stopOneHanded();
     }
 
     @Test
     public void tesSettingsObserver_updateTapAppToExit() {
-        // Bypass test if device not support one-handed mode
-        if (!mIsSupportOneHandedMode) {
-            return;
-        }
         Settings.Secure.putInt(mContext.getContentResolver(),
                 Settings.Secure.TAPS_APP_TO_EXIT, 1);
 
-        verify(mMockOneHandedManagerImpl, times(1)).setTaskChangeToExit(true);
+        verify(mMockOneHandedManagerImpl).setTaskChangeToExit(true);
     }
 
     @Test
     public void tesSettingsObserver_updateEnabled() {
-        // Bypass test if device not support one-handed mode
-        if (!mIsSupportOneHandedMode) {
-            return;
-        }
         Settings.Secure.putInt(mContext.getContentResolver(),
                 Settings.Secure.ONE_HANDED_MODE_ENABLED, 1);
 
-        verify(mMockOneHandedManagerImpl, times(1)).setOneHandedEnabled(true);
+        verify(mMockOneHandedManagerImpl).setOneHandedEnabled(true);
     }
 
     @Test
     public void tesSettingsObserver_updateTimeout() {
-        // Bypass test if device not support one-handed mode
-        if (!mIsSupportOneHandedMode) {
-            return;
-        }
         Settings.Secure.putInt(mContext.getContentResolver(),
                 Settings.Secure.ONE_HANDED_MODE_TIMEOUT,
                 OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS);
@@ -166,10 +112,6 @@
 
     @Test
     public void tesSettingsObserver_updateSwipeToNotification() {
-        // Bypass test if device not support one-handed mode
-        if (!mIsSupportOneHandedMode) {
-            return;
-        }
         Settings.Secure.putInt(mContext.getContentResolver(),
                 Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, 1);
 
@@ -179,24 +121,16 @@
     @Ignore("Clarifying do not receive callback")
     @Test
     public void testKeyguardBouncerShowing_shouldStopOneHanded() {
-        // Bypass test if device not support one-handed mode
-        if (!mIsSupportOneHandedMode) {
-            return;
-        }
         mKeyguardUpdateMonitor.sendKeyguardBouncerChanged(true);
 
-        verify(mMockOneHandedManagerImpl, times(1)).stopOneHanded();
+        verify(mMockOneHandedManagerImpl).stopOneHanded();
     }
 
     @Test
     public void testScreenTurningOff_shouldStopOneHanded() {
-        // Bypass test if device not support one-handed mode
-        if (!mIsSupportOneHandedMode) {
-            return;
-        }
         mScreenLifecycle.dispatchScreenTurningOff();
 
-        verify(mMockOneHandedManagerImpl, times(1)).stopOneHanded();
+        verify(mMockOneHandedManagerImpl).stopOneHanded();
     }
 
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index d338cbf..b46c6ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -150,7 +150,10 @@
         return new QSFragment(
                 new RemoteInputQuickSettingsDisabler(context, mock(ConfigurationController.class),
                         commandQueue),
-                new InjectionInflationController(SystemUIFactory.getInstance().getRootComponent()),
+                new InjectionInflationController(
+                        SystemUIFactory.getInstance()
+                                .getSysUIComponent()
+                                .createViewInstanceCreatorFactory()),
                 mock(QSTileHost.class),
                 mock(StatusBarStateController.class),
                 commandQueue,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
index 184329ec..e23f926 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
@@ -86,7 +86,7 @@
         CompletableFuture<List<Notification.Action>> smartActionsFuture =
                 mScreenshotSmartActions.getSmartActionsFuture(
                         "", Uri.parse("content://authority/data"), bitmap, smartActionsProvider,
-                        true, UserHandle.getUserHandleForUid(UserHandle.myUserId()));
+                        true, UserHandle.of(UserHandle.myUserId()));
         assertNotNull(smartActionsFuture);
         List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS);
         assertEquals(Collections.emptyList(), smartActions);
@@ -126,7 +126,7 @@
         CompletableFuture<List<Notification.Action>> smartActionsFuture =
                 mScreenshotSmartActions.getSmartActionsFuture(
                         "", Uri.parse("content://autority/data"), bitmap, mSmartActionsProvider,
-                        true, UserHandle.getUserHandleForUid(UserHandle.myUserId()));
+                        true, UserHandle.of(UserHandle.myUserId()));
         verify(mSmartActionsProvider, never()).getActions(any(), any(), any(), any(), any());
         assertNotNull(smartActionsFuture);
         List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS);
@@ -140,7 +140,7 @@
         when(bitmap.getConfig()).thenReturn(Bitmap.Config.HARDWARE);
         mScreenshotSmartActions.getSmartActionsFuture(
                 "", Uri.parse("content://autority/data"), bitmap, mSmartActionsProvider, true,
-                UserHandle.getUserHandleForUid(UserHandle.myUserId()));
+                UserHandle.of(UserHandle.myUserId()));
         verify(mSmartActionsProvider, times(1)).getActions(any(), any(), any(), any(), any());
     }
 
@@ -156,7 +156,7 @@
         CompletableFuture<List<Notification.Action>> smartActionsFuture =
                 mScreenshotSmartActions.getSmartActionsFuture("", null, bitmap,
                         actionsProvider,
-                        true, UserHandle.getUserHandleForUid(UserHandle.myUserId()));
+                        true, UserHandle.of(UserHandle.myUserId()));
         assertNotNull(smartActionsFuture);
         List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS);
         assertEquals(smartActions.size(), 0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
index 644ed3d..8089561 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
@@ -34,7 +34,6 @@
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
 import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index 56df193..a36a4c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -29,7 +29,6 @@
 import com.android.systemui.statusbar.notification.ActivityLaunchAnimator
 import com.android.systemui.statusbar.phone.BiometricUnlockController
 import com.android.systemui.statusbar.phone.DozeParameters
-import com.android.systemui.statusbar.phone.NotificationShadeWindowController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.mockito.eq
 import org.junit.Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
index 6f46923..1259d28 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
@@ -19,12 +19,10 @@
 import static junit.framework.Assert.assertTrue;
 
 import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 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;
@@ -46,9 +44,9 @@
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.inflation.LowPriorityInflationHelper;
+import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -212,19 +210,6 @@
     }
 
     @Test
-    public void testUpdateNotificationViews_appOps() throws Exception {
-        NotificationEntry entry0 = createEntry();
-        entry0.setRow(spy(entry0.getRow()));
-        when(mEntryManager.getVisibleNotifications()).thenReturn(
-                Lists.newArrayList(entry0));
-        mListContainer.addContainerView(entry0.getRow());
-
-        mViewHierarchyManager.updateNotificationViews();
-
-        verify(entry0.getRow(), times(1)).showAppOpsIcons(any());
-    }
-
-    @Test
     public void testReentrantCallsToOnDynamicPrivacyChangedPostForLater() {
         // GIVEN a ListContainer that will make a re-entrant call to updateNotificationViews()
         mMadeReentrantCall = false;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java
index ea1b498..baeedcf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java
@@ -32,14 +32,17 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.NotificationPresenter;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -54,109 +57,126 @@
     private ExpandableNotificationRow mRow = mock(ExpandableNotificationRow.class);
     private NotificationEntry mEntry;
 
+    private StatusBarStateController.StateListener mStatusBarStateListener;
+    private WakefulnessLifecycle.Observer mWakefulnessObserver;
+
     @Before
     public void setUp() {
+        StatusBarStateController statusBarStateController = mock(StatusBarStateController.class);
+        WakefulnessLifecycle wakefulnessLifecycle = mock(WakefulnessLifecycle.class);
+
         mTestableLooper = TestableLooper.get(this);
         mVisualStabilityManager = new VisualStabilityManager(
                 mock(NotificationEntryManager.class),
-                new Handler(mTestableLooper.getLooper()));
+                new Handler(mTestableLooper.getLooper()),
+                statusBarStateController,
+                wakefulnessLifecycle);
 
-        mVisualStabilityManager.setUpWithPresenter(mock(NotificationPresenter.class));
         mVisualStabilityManager.setVisibilityLocationProvider(mLocationProvider);
         mEntry = new NotificationEntryBuilder().build();
         mEntry.setRow(mRow);
 
         when(mRow.getEntry()).thenReturn(mEntry);
+
+        ArgumentCaptor<StatusBarStateController.StateListener> stateListenerCaptor =
+                ArgumentCaptor.forClass(StatusBarStateController.StateListener.class);
+        verify(statusBarStateController).addCallback(stateListenerCaptor.capture());
+        mStatusBarStateListener = stateListenerCaptor.getValue();
+
+        ArgumentCaptor<WakefulnessLifecycle.Observer> wakefulnessObserverCaptor =
+                ArgumentCaptor.forClass(WakefulnessLifecycle.Observer.class);
+        verify(wakefulnessLifecycle).addObserver(wakefulnessObserverCaptor.capture());
+        mWakefulnessObserver = wakefulnessObserverCaptor.getValue();
     }
 
     @Test
     public void testPanelExpansion() {
-        mVisualStabilityManager.setPanelExpanded(true);
-        mVisualStabilityManager.setScreenOn(true);
+        setPanelExpanded(true);
+        setScreenOn(true);
         assertFalse(mVisualStabilityManager.canReorderNotification(mRow));
-        mVisualStabilityManager.setPanelExpanded(false);
+        setPanelExpanded(false);
         assertTrue(mVisualStabilityManager.canReorderNotification(mRow));
     }
 
     @Test
     public void testScreenOn() {
-        mVisualStabilityManager.setPanelExpanded(true);
-        mVisualStabilityManager.setScreenOn(true);
+        setPanelExpanded(true);
+        setScreenOn(true);
         assertFalse(mVisualStabilityManager.canReorderNotification(mRow));
-        mVisualStabilityManager.setScreenOn(false);
+        setScreenOn(false);
         assertTrue(mVisualStabilityManager.canReorderNotification(mRow));
     }
 
     @Test
     public void testReorderingAllowedChangesScreenOn() {
-        mVisualStabilityManager.setPanelExpanded(true);
-        mVisualStabilityManager.setScreenOn(true);
+        setPanelExpanded(true);
+        setScreenOn(true);
         assertFalse(mVisualStabilityManager.isReorderingAllowed());
-        mVisualStabilityManager.setScreenOn(false);
+        setScreenOn(false);
         assertTrue(mVisualStabilityManager.isReorderingAllowed());
     }
 
     @Test
     public void testReorderingAllowedChangesPanel() {
-        mVisualStabilityManager.setPanelExpanded(true);
-        mVisualStabilityManager.setScreenOn(true);
+        setPanelExpanded(true);
+        setScreenOn(true);
         assertFalse(mVisualStabilityManager.isReorderingAllowed());
-        mVisualStabilityManager.setPanelExpanded(false);
+        setPanelExpanded(false);
         assertTrue(mVisualStabilityManager.isReorderingAllowed());
     }
 
     @Test
     public void testCallBackCalledScreenOn() {
-        mVisualStabilityManager.setPanelExpanded(true);
-        mVisualStabilityManager.setScreenOn(true);
+        setPanelExpanded(true);
+        setScreenOn(true);
         mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false  /* persistent */);
-        mVisualStabilityManager.setScreenOn(false);
+        setScreenOn(false);
         verify(mCallback).onChangeAllowed();
     }
 
     @Test
     public void testCallBackCalledPanelExpanded() {
-        mVisualStabilityManager.setPanelExpanded(true);
-        mVisualStabilityManager.setScreenOn(true);
+        setPanelExpanded(true);
+        setScreenOn(true);
         mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false  /* persistent */);
-        mVisualStabilityManager.setPanelExpanded(false);
+        setPanelExpanded(false);
         verify(mCallback).onChangeAllowed();
     }
 
     @Test
     public void testCallBackExactlyOnce() {
-        mVisualStabilityManager.setPanelExpanded(true);
-        mVisualStabilityManager.setScreenOn(true);
+        setPanelExpanded(true);
+        setScreenOn(true);
         mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false  /* persistent */);
-        mVisualStabilityManager.setScreenOn(false);
-        mVisualStabilityManager.setScreenOn(true);
-        mVisualStabilityManager.setScreenOn(false);
+        setScreenOn(false);
+        setScreenOn(true);
+        setScreenOn(false);
         verify(mCallback).onChangeAllowed();
     }
 
     @Test
     public void testCallBackCalledContinuouslyWhenRequested() {
-        mVisualStabilityManager.setPanelExpanded(true);
-        mVisualStabilityManager.setScreenOn(true);
+        setPanelExpanded(true);
+        setScreenOn(true);
         mVisualStabilityManager.addReorderingAllowedCallback(mCallback, true  /* persistent */);
-        mVisualStabilityManager.setScreenOn(false);
-        mVisualStabilityManager.setScreenOn(true);
-        mVisualStabilityManager.setScreenOn(false);
+        setScreenOn(false);
+        setScreenOn(true);
+        setScreenOn(false);
         verify(mCallback, times(2)).onChangeAllowed();
     }
 
     @Test
     public void testAddedCanReorder() {
-        mVisualStabilityManager.setPanelExpanded(true);
-        mVisualStabilityManager.setScreenOn(true);
+        setPanelExpanded(true);
+        setScreenOn(true);
         mVisualStabilityManager.notifyViewAddition(mRow);
         assertTrue(mVisualStabilityManager.canReorderNotification(mRow));
     }
 
     @Test
     public void testReorderingVisibleHeadsUpNotAllowed() {
-        mVisualStabilityManager.setPanelExpanded(true);
-        mVisualStabilityManager.setScreenOn(true);
+        setPanelExpanded(true);
+        setScreenOn(true);
         when(mLocationProvider.isInVisibleLocation(any(NotificationEntry.class))).thenReturn(true);
         mVisualStabilityManager.onHeadsUpStateChanged(mEntry, true);
         assertFalse(mVisualStabilityManager.canReorderNotification(mRow));
@@ -164,8 +184,8 @@
 
     @Test
     public void testReorderingVisibleHeadsUpAllowed() {
-        mVisualStabilityManager.setPanelExpanded(true);
-        mVisualStabilityManager.setScreenOn(true);
+        setPanelExpanded(true);
+        setScreenOn(true);
         when(mLocationProvider.isInVisibleLocation(any(NotificationEntry.class))).thenReturn(false);
         mVisualStabilityManager.onHeadsUpStateChanged(mEntry, true);
         assertTrue(mVisualStabilityManager.canReorderNotification(mRow));
@@ -173,8 +193,8 @@
 
     @Test
     public void testReorderingVisibleHeadsUpAllowedOnce() {
-        mVisualStabilityManager.setPanelExpanded(true);
-        mVisualStabilityManager.setScreenOn(true);
+        setPanelExpanded(true);
+        setScreenOn(true);
         when(mLocationProvider.isInVisibleLocation(any(NotificationEntry.class))).thenReturn(false);
         mVisualStabilityManager.onHeadsUpStateChanged(mEntry, true);
         mVisualStabilityManager.onReorderingFinished();
@@ -183,33 +203,33 @@
 
     @Test
     public void testPulsing() {
-        mVisualStabilityManager.setPulsing(true);
+        setPulsing(true);
         assertFalse(mVisualStabilityManager.canReorderNotification(mRow));
-        mVisualStabilityManager.setPulsing(false);
+        setPulsing(false);
         assertTrue(mVisualStabilityManager.canReorderNotification(mRow));
     }
 
     @Test
     public void testReorderingAllowedChanges_Pulsing() {
-        mVisualStabilityManager.setPulsing(true);
+        setPulsing(true);
         assertFalse(mVisualStabilityManager.isReorderingAllowed());
-        mVisualStabilityManager.setPulsing(false);
+        setPulsing(false);
         assertTrue(mVisualStabilityManager.isReorderingAllowed());
     }
 
     @Test
     public void testCallBackCalled_Pulsing() {
-        mVisualStabilityManager.setPulsing(true);
+        setPulsing(true);
         mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false  /* persistent */);
-        mVisualStabilityManager.setPulsing(false);
+        setPulsing(false);
         verify(mCallback).onChangeAllowed();
     }
 
     @Test
     public void testTemporarilyAllowReorderingNotifiesCallbacks() {
         // GIVEN having the panel open (which would block reordering)
-        mVisualStabilityManager.setScreenOn(true);
-        mVisualStabilityManager.setPanelExpanded(true);
+        setScreenOn(true);
+        setPanelExpanded(true);
         mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false  /* persistent */);
 
         // WHEN we temprarily allow reordering
@@ -223,7 +243,7 @@
     @Test
     public void testTemporarilyAllowReorderingDoesntOverridePulsing() {
         // GIVEN we are in a pulsing state
-        mVisualStabilityManager.setPulsing(true);
+        setPulsing(true);
         mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false  /* persistent */);
 
         // WHEN we temprarily allow reordering
@@ -237,8 +257,8 @@
     @Test
     public void testTemporarilyAllowReorderingExpires() {
         // GIVEN having the panel open (which would block reordering)
-        mVisualStabilityManager.setScreenOn(true);
-        mVisualStabilityManager.setPanelExpanded(true);
+        setScreenOn(true);
+        setPanelExpanded(true);
         mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false  /* persistent */);
 
         // WHEN we temprarily allow reordering and then wait until the window expires
@@ -249,4 +269,20 @@
         // THEN reordering is no longer allowed
         assertFalse(mVisualStabilityManager.isReorderingAllowed());
     }
+
+    private void setPanelExpanded(boolean expanded) {
+        mStatusBarStateListener.onExpandedChanged(expanded);
+    }
+
+    private void setPulsing(boolean pulsing) {
+        mStatusBarStateListener.onPulsingChanged(pulsing);
+    }
+
+    private void setScreenOn(boolean screenOn) {
+        if (screenOn) {
+            mWakefulnessObserver.onStartedWakingUp();
+        } else {
+            mWakefulnessObserver.onFinishedGoingToSleep();
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java
index 960ea79..ce8ce2e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java
@@ -77,8 +77,6 @@
     private NotificationEntryBuilder mEntryBuilder;
     private AppOpsCoordinator mAppOpsCoordinator;
     private NotifFilter mForegroundFilter;
-    private NotifCollectionListener mNotifCollectionListener;
-    private AppOpsController.Callback mAppOpsCallback;
     private NotifLifetimeExtender mForegroundNotifLifetimeExtender;
     private NotifSection mFgsSection;
 
@@ -113,19 +111,6 @@
                 lifetimeExtenderCaptor.capture());
         mForegroundNotifLifetimeExtender = lifetimeExtenderCaptor.getValue();
 
-        // capture notifCollectionListener
-        ArgumentCaptor<NotifCollectionListener> notifCollectionCaptor =
-                ArgumentCaptor.forClass(NotifCollectionListener.class);
-        verify(mNotifPipeline, times(1)).addCollectionListener(
-                notifCollectionCaptor.capture());
-        mNotifCollectionListener = notifCollectionCaptor.getValue();
-
-        // capture app ops callback
-        ArgumentCaptor<AppOpsController.Callback> appOpsCaptor =
-                ArgumentCaptor.forClass(AppOpsController.Callback.class);
-        verify(mAppOpsController).addCallback(any(int[].class), appOpsCaptor.capture());
-        mAppOpsCallback = appOpsCaptor.getValue();
-
         mFgsSection = mAppOpsCoordinator.getSection();
     }
 
@@ -230,136 +215,6 @@
     }
 
     @Test
-    public void testAppOpsUpdateOnlyAppliedToRelevantNotificationWithStandardLayout() {
-        // GIVEN three current notifications, two with the same key but from different users
-        NotificationEntry entry1 = new NotificationEntryBuilder()
-                .setUser(new UserHandle(NOTIF_USER_ID))
-                .setPkg(TEST_PKG)
-                .setId(1)
-                .build();
-        NotificationEntry entry2 = new NotificationEntryBuilder()
-                .setUser(new UserHandle(NOTIF_USER_ID))
-                .setPkg(TEST_PKG)
-                .setId(2)
-                .build();
-        NotificationEntry entry3_diffUser = new NotificationEntryBuilder()
-                .setUser(new UserHandle(NOTIF_USER_ID + 1))
-                .setPkg(TEST_PKG)
-                .setId(2)
-                .build();
-        when(mNotifPipeline.getAllNotifs()).thenReturn(List.of(entry1, entry2, entry3_diffUser));
-
-        // GIVEN that only entry2 has a standard layout
-        when(mForegroundServiceController.getStandardLayoutKeys(NOTIF_USER_ID, TEST_PKG))
-                .thenReturn(new ArraySet<>(List.of(entry2.getKey())));
-
-        // WHEN a new app ops code comes in
-        mAppOpsCallback.onActiveStateChanged(47, NOTIF_USER_ID, TEST_PKG, true);
-        mExecutor.runAllReady();
-
-        // THEN entry2's app ops are updated, but no one else's are
-        assertEquals(
-                new ArraySet<>(),
-                entry1.mActiveAppOps);
-        assertEquals(
-                new ArraySet<>(List.of(47)),
-                entry2.mActiveAppOps);
-        assertEquals(
-                new ArraySet<>(),
-                entry3_diffUser.mActiveAppOps);
-    }
-
-    @Test
-    public void testAppOpsUpdateAppliedToAllNotificationsWithStandardLayouts() {
-        // GIVEN three notifications with standard layouts
-        NotificationEntry entry1 = new NotificationEntryBuilder()
-                .setUser(new UserHandle(NOTIF_USER_ID))
-                .setPkg(TEST_PKG)
-                .setId(1)
-                .build();
-        NotificationEntry entry2 = new NotificationEntryBuilder()
-                .setUser(new UserHandle(NOTIF_USER_ID))
-                .setPkg(TEST_PKG)
-                .setId(2)
-                .build();
-        NotificationEntry entry3 = new NotificationEntryBuilder()
-                .setUser(new UserHandle(NOTIF_USER_ID))
-                .setPkg(TEST_PKG)
-                .setId(3)
-                .build();
-        when(mNotifPipeline.getAllNotifs()).thenReturn(List.of(entry1, entry2, entry3));
-        when(mForegroundServiceController.getStandardLayoutKeys(NOTIF_USER_ID, TEST_PKG))
-                .thenReturn(new ArraySet<>(List.of(entry1.getKey(), entry2.getKey(),
-                        entry3.getKey())));
-
-        // WHEN a new app ops code comes in
-        mAppOpsCallback.onActiveStateChanged(47, NOTIF_USER_ID, TEST_PKG, true);
-        mExecutor.runAllReady();
-
-        // THEN all entries get updated
-        assertEquals(
-                new ArraySet<>(List.of(47)),
-                entry1.mActiveAppOps);
-        assertEquals(
-                new ArraySet<>(List.of(47)),
-                entry2.mActiveAppOps);
-        assertEquals(
-                new ArraySet<>(List.of(47)),
-                entry3.mActiveAppOps);
-    }
-
-    @Test
-    public void testAppOpsAreRemoved() {
-        // GIVEN One notification which is associated with app ops
-        NotificationEntry entry = new NotificationEntryBuilder()
-                .setUser(new UserHandle(NOTIF_USER_ID))
-                .setPkg(TEST_PKG)
-                .setId(2)
-                .build();
-        when(mNotifPipeline.getAllNotifs()).thenReturn(List.of(entry));
-        when(mForegroundServiceController.getStandardLayoutKeys(0, TEST_PKG))
-                .thenReturn(new ArraySet<>(List.of(entry.getKey())));
-
-        // GIVEN that the notification's app ops are already [47, 33]
-        mAppOpsCallback.onActiveStateChanged(47, NOTIF_USER_ID, TEST_PKG, true);
-        mAppOpsCallback.onActiveStateChanged(33, NOTIF_USER_ID, TEST_PKG, true);
-        mExecutor.runAllReady();
-        assertEquals(
-                new ArraySet<>(List.of(47, 33)),
-                entry.mActiveAppOps);
-
-        // WHEN one of the app ops is removed
-        mAppOpsCallback.onActiveStateChanged(47, NOTIF_USER_ID, TEST_PKG, false);
-        mExecutor.runAllReady();
-
-        // THEN the entry's active app ops are updated as well
-        assertEquals(
-                new ArraySet<>(List.of(33)),
-                entry.mActiveAppOps);
-    }
-
-    @Test
-    public void testNullAppOps() {
-        // GIVEN one notification with app ops
-        NotificationEntry entry = new NotificationEntryBuilder()
-                .setUser(new UserHandle(NOTIF_USER_ID))
-                .setPkg(TEST_PKG)
-                .setId(2)
-                .build();
-        entry.mActiveAppOps.clear();
-        entry.mActiveAppOps.addAll(List.of(47, 33));
-
-        // WHEN the notification is updated and the foreground service controller returns null for
-        // this notification
-        when(mForegroundServiceController.getAppOps(entry.getSbn().getUser().getIdentifier(),
-                entry.getSbn().getPackageName())).thenReturn(null);
-        mNotifCollectionListener.onEntryUpdated(entry);
-
-        // THEN the entry's active app ops is updated to empty
-        assertTrue(entry.mActiveAppOps.isEmpty());
-    }
-
-    @Test
     public void testIncludeFGSInSection_importanceDefault() {
         // GIVEN the notification represents a colorized foreground service with > min importance
         mEntryBuilder
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
new file mode 100644
index 0000000..605b4d1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
@@ -0,0 +1,341 @@
+/*
+ * 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.coordinator;
+
+import static junit.framework.Assert.assertFalse;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+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.NotifStabilityManager;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class VisualStabilityCoordinatorTest extends SysuiTestCase {
+
+    private VisualStabilityCoordinator mCoordinator;
+
+    // captured listeners and pluggables:
+    private NotifCollectionListener mCollectionListener;
+
+    @Mock private NotifPipeline mNotifPipeline;
+    @Mock private WakefulnessLifecycle mWakefulnessLifecycle;
+    @Mock private StatusBarStateController mStatusBarStateController;
+    @Mock private Pluggable.PluggableListener<NotifStabilityManager> mInvalidateListener;
+    @Mock private HeadsUpManager mHeadsUpManager;
+
+    @Captor private ArgumentCaptor<WakefulnessLifecycle.Observer> mWakefulnessObserverCaptor;
+    @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mSBStateListenerCaptor;
+    @Captor private ArgumentCaptor<NotifStabilityManager> mNotifStabilityManagerCaptor;
+    @Captor private ArgumentCaptor<NotifCollectionListener> mNotifCollectionListenerCaptor;
+
+    private FakeSystemClock mFakeSystemClock = new FakeSystemClock();
+    private FakeExecutor mFakeExecutor = new FakeExecutor(mFakeSystemClock);
+
+    private WakefulnessLifecycle.Observer mWakefulnessObserver;
+    private StatusBarStateController.StateListener mStatusBarStateListener;
+    private NotifStabilityManager mNotifStabilityManager;
+    private NotificationEntry mEntry;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mCoordinator = new VisualStabilityCoordinator(
+                mHeadsUpManager,
+                mWakefulnessLifecycle,
+                mStatusBarStateController,
+                mFakeExecutor);
+
+        mCoordinator.attach(mNotifPipeline);
+
+        // capture arguments:
+        verify(mWakefulnessLifecycle).addObserver(mWakefulnessObserverCaptor.capture());
+        mWakefulnessObserver = mWakefulnessObserverCaptor.getValue();
+
+        verify(mStatusBarStateController).addCallback(mSBStateListenerCaptor.capture());
+        mStatusBarStateListener = mSBStateListenerCaptor.getValue();
+
+        verify(mNotifPipeline).setVisualStabilityManager(mNotifStabilityManagerCaptor.capture());
+        mNotifStabilityManager = mNotifStabilityManagerCaptor.getValue();
+        mNotifStabilityManager.setInvalidationListener(mInvalidateListener);
+
+        mEntry = new NotificationEntryBuilder()
+                .setPkg("testPkg1")
+                .build();
+
+        when(mHeadsUpManager.isAlerting(mEntry.getKey())).thenReturn(false);
+    }
+
+    @Test
+    public void testScreenOff_groupAndSectionChangesAllowed() {
+        // GIVEN screen is off, panel isn't expanded and device isn't pulsing
+        setScreenOn(false);
+        setPanelExpanded(false);
+        setPulsing(false);
+
+        // THEN group changes are allowed
+        assertTrue(mNotifStabilityManager.isGroupChangeAllowed(mEntry));
+
+        // THEN section changes are allowed
+        assertTrue(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
+    }
+
+    @Test
+    public void testPanelNotExpanded_groupAndSectionChangesAllowed() {
+        // GIVEN screen is on but the panel isn't expanded and device isn't pulsing
+        setScreenOn(true);
+        setPanelExpanded(false);
+        setPulsing(false);
+
+        // THEN group changes are allowed
+        assertTrue(mNotifStabilityManager.isGroupChangeAllowed(mEntry));
+
+        // THEN section changes are allowed
+        assertTrue(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
+    }
+
+    @Test
+    public void testPanelExpanded_groupAndSectionChangesNotAllowed() {
+        // GIVEN the panel true expanded and device isn't pulsing
+        setScreenOn(true);
+        setPanelExpanded(true);
+        setPulsing(false);
+
+        // THEN group changes are NOT allowed
+        assertFalse(mNotifStabilityManager.isGroupChangeAllowed(mEntry));
+
+        // THEN section changes are NOT allowed
+        assertFalse(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
+    }
+
+    @Test
+    public void testPulsing_screenOff_groupAndSectionChangesNotAllowed() {
+        // GIVEN the device is pulsing and screen is off
+        setScreenOn(false);
+        setPulsing(true);
+
+        // THEN group changes are NOT allowed
+        assertFalse(mNotifStabilityManager.isGroupChangeAllowed(mEntry));
+
+        // THEN section changes are NOT allowed
+        assertFalse(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
+    }
+
+    @Test
+    public void testPulsing_panelNotExpanded_groupAndSectionChangesNotAllowed() {
+        // GIVEN the device is pulsing and screen is off with the panel not expanded
+        setScreenOn(false);
+        setPanelExpanded(false);
+        setPulsing(true);
+
+        // THEN group changes are NOT allowed
+        assertFalse(mNotifStabilityManager.isGroupChangeAllowed(mEntry));
+
+        // THEN section changes are NOT allowed
+        assertFalse(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
+    }
+
+    @Test
+    public void testOverrideReorderingSuppression_onlySectionChangesAllowed() {
+        // GIVEN section changes typically wouldn't be allowed because the panel is expanded and
+        // we're not pulsing
+        setScreenOn(true);
+        setPanelExpanded(true);
+        setPulsing(true);
+
+        // WHEN we temporarily allow section changes for this notification entry
+        mCoordinator.temporarilyAllowSectionChanges(mEntry, mFakeSystemClock.currentTimeMillis());
+
+        // THEN group changes aren't allowed
+        assertFalse(mNotifStabilityManager.isGroupChangeAllowed(mEntry));
+
+        // THEN section changes are allowed for this notification but not other notifications
+        assertTrue(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
+        assertFalse(mNotifStabilityManager.isSectionChangeAllowed(
+                new NotificationEntryBuilder()
+                        .setPkg("testPkg2")
+                        .build()));
+    }
+
+    @Test
+    public void testTemporarilyAllowSectionChanges_callsInvalidate() {
+        // GIVEN section changes typically wouldn't be allowed because the panel is expanded
+        setScreenOn(true);
+        setPanelExpanded(true);
+        setPulsing(false);
+
+        // WHEN we temporarily allow section changes for this notification entry
+        mCoordinator.temporarilyAllowSectionChanges(mEntry, mFakeSystemClock.uptimeMillis());
+
+        // THEN the notification list is invalidated
+        verifyInvalidateCalled(true);
+    }
+
+    @Test
+    public void testTemporarilyAllowSectionChanges_noInvalidationCalled() {
+        // GIVEN section changes typically WOULD be allowed
+        setScreenOn(false);
+        setPanelExpanded(false);
+        setPulsing(false);
+
+        // WHEN we temporarily allow section changes for this notification entry
+        mCoordinator.temporarilyAllowSectionChanges(mEntry, mFakeSystemClock.currentTimeMillis());
+
+        // THEN invalidate is not called because this entry was never suppressed from reordering
+        verifyInvalidateCalled(false);
+    }
+
+    @Test
+    public void testTemporarilyAllowSectionChangesTimeout() {
+        // GIVEN section changes typically WOULD be allowed
+        setScreenOn(false);
+        setPanelExpanded(false);
+        setPulsing(false);
+        assertTrue(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
+
+        // WHEN we temporarily allow section changes for this notification entry
+        mCoordinator.temporarilyAllowSectionChanges(mEntry, mFakeSystemClock.currentTimeMillis());
+
+        // THEN invalidate is not called because this entry was never suppressed from reordering;
+        // THEN section changes are allowed for this notification
+        verifyInvalidateCalled(false);
+        assertTrue(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
+
+        // WHEN we're pulsing (now disallowing reordering)
+        setPulsing(true);
+
+        // THEN we're still allowed to reorder this section because it's still in the list of
+        // notifications to allow section changes
+        assertTrue(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
+
+        // WHEN the timeout for the temporarily allow section reordering runnable is finsihed
+        mFakeExecutor.advanceClockToNext();
+        mFakeExecutor.runNextReady();
+
+        // THEN section changes aren't allowed anymore
+        assertFalse(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
+    }
+
+    @Test
+    public void testTemporarilyAllowSectionChanges_isPulsingChangeBeforeTimeout() {
+        // GIVEN section changes typically wouldn't be allowed because the device is pulsing
+        setScreenOn(false);
+        setPanelExpanded(false);
+        setPulsing(true);
+
+        // WHEN we temporarily allow section changes for this notification entry
+        mCoordinator.temporarilyAllowSectionChanges(mEntry, mFakeSystemClock.currentTimeMillis());
+        verifyInvalidateCalled(true); // can now reorder, so invalidates
+
+        // WHEN reordering is now allowed because device isn't pulsing anymore
+        setPulsing(false);
+
+        // THEN invalidate isn't called since reordering was already allowed
+        verifyInvalidateCalled(false);
+    }
+
+    @Test
+    public void testNeverSuppressedChanges_noInvalidationCalled() {
+        // GIVEN no notifications are currently being suppressed from grouping nor being sorted
+
+        // WHEN device isn't pulsing anymore
+        setPulsing(false);
+
+        // WHEN screen isn't on
+        setScreenOn(false);
+
+        // WHEN panel isn't expanded
+        setPanelExpanded(false);
+
+        // THEN we never see any calls to invalidate since there weren't any notifications that
+        // were being suppressed from grouping or section changes
+        verifyInvalidateCalled(false);
+    }
+
+    @Test
+    public void testHeadsUp_allowedToChangeGroupAndSection() {
+        // GIVEN group + section changes disallowed
+        setScreenOn(true);
+        setPanelExpanded(true);
+        setPulsing(true);
+        assertFalse(mNotifStabilityManager.isGroupChangeAllowed(mEntry));
+        assertFalse(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
+
+        // GIVEN mEntry is a HUN
+        when(mHeadsUpManager.isAlerting(mEntry.getKey())).thenReturn(true);
+
+        // THEN group + section changes are allowed
+        assertTrue(mNotifStabilityManager.isGroupChangeAllowed(mEntry));
+        assertTrue(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
+
+    }
+
+    private void setPulsing(boolean pulsing) {
+        mStatusBarStateListener.onPulsingChanged(pulsing);
+    }
+
+    private void setScreenOn(boolean screenOn) {
+        if (screenOn) {
+            mWakefulnessObserver.onStartedWakingUp();
+        } else {
+            mWakefulnessObserver.onFinishedGoingToSleep();
+        }
+    }
+
+    private void setPanelExpanded(boolean expanded) {
+        mStatusBarStateListener.onExpandedChanged(expanded);
+    }
+
+    private void verifyInvalidateCalled(boolean invalidateCalled) {
+        if (invalidateCalled) {
+            verify(mInvalidateListener).onPluggableInvalidated(mNotifStabilityManager);
+        } else {
+            verify(mInvalidateListener, never()).onPluggableInvalidated(mNotifStabilityManager);
+        }
+
+        reset(mInvalidateListener);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java
new file mode 100644
index 0000000..bbe92f6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java
@@ -0,0 +1,304 @@
+/*
+ * 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.render;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.Context;
+import android.testing.AndroidTestingRunner;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ShadeViewDifferTest extends SysuiTestCase {
+    private ShadeViewDiffer mDiffer;
+
+    private FakeController mRootController = new FakeController(mContext, "RootController");
+    private FakeController mController1 = new FakeController(mContext, "Controller1");
+    private FakeController mController2 = new FakeController(mContext, "Controller2");
+    private FakeController mController3 = new FakeController(mContext, "Controller3");
+    private FakeController mController4 = new FakeController(mContext, "Controller4");
+    private FakeController mController5 = new FakeController(mContext, "Controller5");
+    private FakeController mController6 = new FakeController(mContext, "Controller6");
+    private FakeController mController7 = new FakeController(mContext, "Controller7");
+
+    @Mock
+    ShadeViewDifferLogger mLogger;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mDiffer = new ShadeViewDiffer(mRootController, mLogger);
+    }
+
+    @Test
+    public void testAddInitialViews() {
+        // WHEN a spec is applied to an empty root
+        // THEN the final tree matches the spec
+        applySpecAndCheck(
+                node(mController1),
+                node(mController2,
+                        node(mController3),
+                        node(mController4)
+                ),
+                node(mController5)
+        );
+    }
+
+    @Test
+    public void testDetachViews() {
+        // GIVEN a preexisting tree of controllers
+        applySpecAndCheck(
+                node(mController1),
+                node(mController2,
+                        node(mController3),
+                        node(mController4)
+                ),
+                node(mController5)
+        );
+
+        // WHEN the new spec removes nodes
+        // THEN the final tree matches the spec
+        applySpecAndCheck(
+                node(mController5)
+        );
+    }
+
+    @Test
+    public void testReparentChildren() {
+        // GIVEN a preexisting tree of controllers
+        applySpecAndCheck(
+                node(mController1),
+                node(mController2,
+                        node(mController3),
+                        node(mController4)
+                ),
+                node(mController5)
+        );
+
+        // WHEN the parents of the controllers are all shuffled around
+        // THEN the final tree matches the spec
+        applySpecAndCheck(
+                node(mController1),
+                node(mController4),
+                node(mController3,
+                        node(mController2)
+                )
+        );
+    }
+
+    @Test
+    public void testReorderChildren() {
+        // GIVEN a preexisting tree of controllers
+        applySpecAndCheck(
+                node(mController1),
+                node(mController2),
+                node(mController3),
+                node(mController4)
+        );
+
+        // WHEN the children change order
+        // THEN the final tree matches the spec
+        applySpecAndCheck(
+                node(mController3),
+                node(mController2),
+                node(mController4),
+                node(mController1)
+        );
+    }
+
+    @Test
+    public void testRemovedGroupsAreKeptTogether() {
+        // GIVEN a preexisting tree with a group
+        applySpecAndCheck(
+                node(mController1),
+                node(mController2,
+                        node(mController3),
+                        node(mController4),
+                        node(mController5)
+                )
+        );
+
+        // WHEN the new spec removes the entire group
+        applySpecAndCheck(
+                node(mController1)
+        );
+
+        // THEN the group children are still attached to their parent
+        assertEquals(mController2.getView(), mController3.getView().getParent());
+        assertEquals(mController2.getView(), mController4.getView().getParent());
+        assertEquals(mController2.getView(), mController5.getView().getParent());
+    }
+
+    @Test
+    public void testUnmanagedViews() {
+        // GIVEN a preexisting tree of controllers
+        applySpecAndCheck(
+                node(mController1),
+                node(mController2,
+                        node(mController3),
+                        node(mController4)
+                ),
+                node(mController5)
+        );
+
+        // GIVEN some additional unmanaged views attached to the tree
+        View unmanagedView1 = new View(mContext);
+        View unmanagedView2 = new View(mContext);
+
+        mRootController.getView().addView(unmanagedView1, 1);
+        mController2.getView().addView(unmanagedView2, 0);
+
+        // WHEN a new spec is applied with additional nodes
+        // THEN the final tree matches the spec
+        applySpecAndCheck(
+                node(mController1),
+                node(mController2,
+                        node(mController3),
+                        node(mController4),
+                        node(mController6)
+                ),
+                node(mController5),
+                node(mController7)
+        );
+
+        // THEN the unmanaged views have been pushed to the end of their parents
+        assertEquals(unmanagedView1, mRootController.view.getChildAt(4));
+        assertEquals(unmanagedView2, mController2.view.getChildAt(3));
+    }
+
+    private void applySpecAndCheck(NodeSpec spec) {
+        mDiffer.applySpec(spec);
+        checkMatchesSpec(spec);
+    }
+
+    private void applySpecAndCheck(SpecBuilder... children) {
+        applySpecAndCheck(node(mRootController, children).build());
+    }
+
+    private void checkMatchesSpec(NodeSpec spec) {
+        final NodeController parent = spec.getController();
+        final List<NodeSpec> children = spec.getChildren();
+
+        for (int i = 0; i < children.size(); i++) {
+            NodeSpec childSpec = children.get(i);
+            View view = parent.getChildAt(i);
+
+            assertEquals(
+                    "Child " + i + " of parent " + parent.getNodeLabel() + " should be "
+                            + childSpec.getController().getNodeLabel() + " but is instead "
+                            + (view != null ? mDiffer.getViewLabel(view) : "null"),
+                    view,
+                    childSpec.getController().getView());
+
+            if (!childSpec.getChildren().isEmpty()) {
+                checkMatchesSpec(childSpec);
+            }
+        }
+    }
+
+    private static class FakeController implements NodeController {
+
+        public final FrameLayout view;
+        private final String mLabel;
+
+        FakeController(Context context, String label) {
+            view = new FrameLayout(context);
+            mLabel = label;
+        }
+
+        @NonNull
+        @Override
+        public String getNodeLabel() {
+            return mLabel;
+        }
+
+        @NonNull
+        @Override
+        public FrameLayout getView() {
+            return view;
+        }
+
+        @Override
+        public int getChildCount() {
+            return view.getChildCount();
+        }
+
+        @Override
+        public View getChildAt(int index) {
+            return view.getChildAt(index);
+        }
+
+        @Override
+        public void addChildAt(@NonNull NodeController child, int index) {
+            view.addView(child.getView(), index);
+        }
+
+        @Override
+        public void moveChildTo(@NonNull NodeController child, int index) {
+            view.removeView(child.getView());
+            view.addView(child.getView(), index);
+        }
+
+        @Override
+        public void removeChild(@NonNull NodeController child, boolean isTransfer) {
+            view.removeView(child.getView());
+        }
+    }
+
+    private static class SpecBuilder {
+        private final NodeController mController;
+        private final SpecBuilder[] mChildren;
+
+        SpecBuilder(NodeController controller, SpecBuilder... children) {
+            mController = controller;
+            mChildren = children;
+        }
+
+        public NodeSpec build() {
+            return build(null);
+        }
+
+        public NodeSpec build(@Nullable NodeSpec parent) {
+            final NodeSpecImpl spec = new NodeSpecImpl(parent, mController);
+            for (SpecBuilder childBuilder : mChildren) {
+                spec.getChildren().add(childBuilder.build(spec));
+            }
+            return spec;
+        }
+    }
+
+    private static SpecBuilder node(NodeController controller, SpecBuilder... children) {
+        return new SpecBuilder(controller, children);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/AppOpsInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/AppOpsInfoTest.java
deleted file mode 100644
index 43d8b50..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/AppOpsInfoTest.java
+++ /dev/null
@@ -1,230 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.notification.row;
-
-import static android.app.AppOpsManager.OP_CAMERA;
-import static android.app.AppOpsManager.OP_RECORD_AUDIO;
-import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertTrue;
-
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyBoolean;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.anyString;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.mock;
-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.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.graphics.drawable.Drawable;
-import android.os.UserHandle;
-import android.service.notification.StatusBarNotification;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.testing.AndroidTestingRunner;
-import android.testing.UiThreadTest;
-import android.util.ArraySet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import com.android.internal.logging.testing.UiEventLoggerFake;
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.CountDownLatch;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@UiThreadTest
-public class AppOpsInfoTest extends SysuiTestCase {
-    private static final String TEST_PACKAGE_NAME = "test_package";
-    private static final int TEST_UID = 1;
-
-    private AppOpsInfo mAppOpsInfo;
-    private final PackageManager mMockPackageManager = mock(PackageManager.class);
-    private final NotificationGuts mGutsParent = mock(NotificationGuts.class);
-    private StatusBarNotification mSbn;
-    private UiEventLoggerFake mUiEventLogger = new UiEventLoggerFake();
-
-    @Before
-    public void setUp() throws Exception {
-        // Inflate the layout
-        final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
-        mAppOpsInfo = (AppOpsInfo) layoutInflater.inflate(R.layout.app_ops_info, null);
-        mAppOpsInfo.setGutsParent(mGutsParent);
-
-        // PackageManager must return a packageInfo and applicationInfo.
-        final PackageInfo packageInfo = new PackageInfo();
-        packageInfo.packageName = TEST_PACKAGE_NAME;
-        when(mMockPackageManager.getPackageInfo(eq(TEST_PACKAGE_NAME), anyInt()))
-                .thenReturn(packageInfo);
-        final ApplicationInfo applicationInfo = new ApplicationInfo();
-        applicationInfo.uid = TEST_UID;  // non-zero
-        when(mMockPackageManager.getApplicationInfo(anyString(), anyInt())).thenReturn(
-                applicationInfo);
-
-        mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0,
-                new Notification(), UserHandle.CURRENT, null, 0);
-    }
-
-    @Test
-    public void testBindNotification_SetsTextApplicationName() {
-        when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name");
-        mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, new ArraySet<>());
-        final TextView textView = mAppOpsInfo.findViewById(R.id.pkgname);
-        assertTrue(textView.getText().toString().contains("App Name"));
-    }
-
-    @Test
-    public void testBindNotification_SetsPackageIcon() {
-        final Drawable iconDrawable = mock(Drawable.class);
-        when(mMockPackageManager.getApplicationIcon(any(ApplicationInfo.class)))
-                .thenReturn(iconDrawable);
-        mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, new ArraySet<>());
-        final ImageView iconView = mAppOpsInfo.findViewById(R.id.pkgicon);
-        assertEquals(iconDrawable, iconView.getDrawable());
-    }
-
-    @Test
-    public void testBindNotification_SetsOnClickListenerForSettings() throws Exception {
-        ArraySet<Integer> expectedOps = new ArraySet<>();
-        expectedOps.add(OP_CAMERA);
-        final CountDownLatch latch = new CountDownLatch(1);
-        mAppOpsInfo.bindGuts(mMockPackageManager, (View v, String pkg, int uid,
-                ArraySet<Integer> ops) -> {
-            assertEquals(TEST_PACKAGE_NAME, pkg);
-            assertEquals(expectedOps, ops);
-            assertEquals(TEST_UID, uid);
-            latch.countDown();
-        }, mSbn, mUiEventLogger, expectedOps);
-
-        final View settingsButton = mAppOpsInfo.findViewById(R.id.settings);
-        settingsButton.performClick();
-        // Verify that listener was triggered.
-        assertEquals(0, latch.getCount());
-    }
-
-    @Test
-    public void testBindNotification_LogsOpen() throws Exception {
-        mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, new ArraySet<>());
-        assertEquals(1, mUiEventLogger.numLogs());
-        assertEquals(NotificationAppOpsEvent.NOTIFICATION_APP_OPS_OPEN.getId(),
-                mUiEventLogger.eventId(0));
-    }
-
-    @Test
-    public void testOk() {
-        ArraySet<Integer> expectedOps = new ArraySet<>();
-        expectedOps.add(OP_CAMERA);
-        final CountDownLatch latch = new CountDownLatch(1);
-        mAppOpsInfo.bindGuts(mMockPackageManager, (View v, String pkg, int uid,
-                ArraySet<Integer> ops) -> {
-            assertEquals(TEST_PACKAGE_NAME, pkg);
-            assertEquals(expectedOps, ops);
-            assertEquals(TEST_UID, uid);
-            latch.countDown();
-        }, mSbn, mUiEventLogger, expectedOps);
-
-        final View okButton = mAppOpsInfo.findViewById(R.id.ok);
-        okButton.performClick();
-        assertEquals(1, latch.getCount());
-        verify(mGutsParent, times(1)).closeControls(eq(okButton), anyBoolean());
-    }
-
-    @Test
-    public void testPrompt_camera() {
-        ArraySet<Integer> expectedOps = new ArraySet<>();
-        expectedOps.add(OP_CAMERA);
-        mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, expectedOps);
-        TextView prompt = mAppOpsInfo.findViewById(R.id.prompt);
-        assertEquals("This app is using the camera.", prompt.getText());
-    }
-
-    @Test
-    public void testPrompt_mic() {
-        ArraySet<Integer> expectedOps = new ArraySet<>();
-        expectedOps.add(OP_RECORD_AUDIO);
-        mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, expectedOps);
-        TextView prompt = mAppOpsInfo.findViewById(R.id.prompt);
-        assertEquals("This app is using the microphone.", prompt.getText());
-    }
-
-    @Test
-    public void testPrompt_overlay() {
-        ArraySet<Integer> expectedOps = new ArraySet<>();
-        expectedOps.add(OP_SYSTEM_ALERT_WINDOW);
-        mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, expectedOps);
-        TextView prompt = mAppOpsInfo.findViewById(R.id.prompt);
-        assertEquals("This app is displaying over other apps on your screen.", prompt.getText());
-    }
-
-    @Test
-    public void testPrompt_camera_mic() {
-        ArraySet<Integer> expectedOps = new ArraySet<>();
-        expectedOps.add(OP_CAMERA);
-        expectedOps.add(OP_RECORD_AUDIO);
-        mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, expectedOps);
-        TextView prompt = mAppOpsInfo.findViewById(R.id.prompt);
-        assertEquals("This app is using the microphone and camera.", prompt.getText());
-    }
-
-    @Test
-    public void testPrompt_camera_mic_overlay() {
-        ArraySet<Integer> expectedOps = new ArraySet<>();
-        expectedOps.add(OP_CAMERA);
-        expectedOps.add(OP_RECORD_AUDIO);
-        expectedOps.add(OP_SYSTEM_ALERT_WINDOW);
-        mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, expectedOps);
-        TextView prompt = mAppOpsInfo.findViewById(R.id.prompt);
-        assertEquals("This app is displaying over other apps on your screen and using"
-                + " the microphone and camera.", prompt.getText());
-    }
-
-    @Test
-    public void testPrompt_camera_overlay() {
-        ArraySet<Integer> expectedOps = new ArraySet<>();
-        expectedOps.add(OP_CAMERA);
-        expectedOps.add(OP_SYSTEM_ALERT_WINDOW);
-        mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, expectedOps);
-        TextView prompt = mAppOpsInfo.findViewById(R.id.prompt);
-        assertEquals("This app is displaying over other apps on your screen and using"
-                + " the camera.", prompt.getText());
-    }
-
-    @Test
-    public void testPrompt_mic_overlay() {
-        ArraySet<Integer> expectedOps = new ArraySet<>();
-        expectedOps.add(OP_RECORD_AUDIO);
-        expectedOps.add(OP_SYSTEM_ALERT_WINDOW);
-        mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, expectedOps);
-        TextView prompt = mAppOpsInfo.findViewById(R.id.prompt);
-        assertEquals("This app is displaying over other apps on your screen and using"
-                + " the microphone.", prompt.getText());
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index dc4a6ca..f29b46c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -35,12 +35,10 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.app.AppOpsManager;
 import android.app.NotificationChannel;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
-import android.util.ArraySet;
 import android.view.View;
 
 import androidx.test.filters.SmallTest;
@@ -213,46 +211,6 @@
     }
 
     @Test
-    public void testShowAppOps_noHeader() {
-        // public notification is custom layout - no header
-        mGroupRow.setSensitive(true, true);
-        mGroupRow.setAppOpsOnClickListener(null);
-        mGroupRow.showAppOpsIcons(null);
-    }
-
-    @Test
-    public void testShowAppOpsIcons_header() {
-        NotificationContentView publicLayout = mock(NotificationContentView.class);
-        mGroupRow.setPublicLayout(publicLayout);
-        NotificationContentView privateLayout = mock(NotificationContentView.class);
-        mGroupRow.setPrivateLayout(privateLayout);
-        NotificationChildrenContainer mockContainer = mock(NotificationChildrenContainer.class);
-        when(mockContainer.getNotificationChildCount()).thenReturn(1);
-        mGroupRow.setChildrenContainer(mockContainer);
-
-        ArraySet<Integer> ops = new ArraySet<>();
-        ops.add(AppOpsManager.OP_ANSWER_PHONE_CALLS);
-        mGroupRow.showAppOpsIcons(ops);
-
-        verify(mockContainer, times(1)).showAppOpsIcons(ops);
-        verify(privateLayout, times(1)).showAppOpsIcons(ops);
-        verify(publicLayout, times(1)).showAppOpsIcons(ops);
-
-    }
-
-    @Test
-    public void testAppOpsOnClick() {
-        ExpandableNotificationRow.CoordinateOnClickListener l = mock(
-                ExpandableNotificationRow.CoordinateOnClickListener.class);
-        View view = mock(View.class);
-
-        mGroupRow.setAppOpsOnClickListener(l);
-
-        mGroupRow.getAppOpsOnClickListener().onClick(view);
-        verify(l, times(1)).onClick(any(), anyInt(), anyInt(), any());
-    }
-
-    @Test
     public void testFeedback_noHeader() {
         // public notification is custom layout - no header
         mGroupRow.setSensitive(true, true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
index 6d4a711..c2091da 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
@@ -76,32 +76,6 @@
 
     @Test
     @UiThreadTest
-    public void testShowAppOpsIcons() {
-        View mockContracted = mock(NotificationHeaderView.class);
-        when(mockContracted.findViewById(com.android.internal.R.id.mic))
-                .thenReturn(mockContracted);
-        View mockExpanded = mock(NotificationHeaderView.class);
-        when(mockExpanded.findViewById(com.android.internal.R.id.mic))
-                .thenReturn(mockExpanded);
-        View mockHeadsUp = mock(NotificationHeaderView.class);
-        when(mockHeadsUp.findViewById(com.android.internal.R.id.mic))
-                .thenReturn(mockHeadsUp);
-
-        mView.setContractedChild(mockContracted);
-        mView.setExpandedChild(mockExpanded);
-        mView.setHeadsUpChild(mockHeadsUp);
-
-        ArraySet<Integer> ops = new ArraySet<>();
-        ops.add(AppOpsManager.OP_RECORD_AUDIO);
-        mView.showAppOpsIcons(ops);
-
-        verify(mockContracted, times(1)).setVisibility(View.VISIBLE);
-        verify(mockExpanded, times(1)).setVisibility(View.VISIBLE);
-        verify(mockHeadsUp, times(1)).setVisibility(View.VISIBLE);
-    }
-
-    @Test
-    @UiThreadTest
     public void testShowFeedbackIcon() {
         View mockContracted = mock(NotificationHeaderView.class);
         when(mockContracted.findViewById(com.android.internal.R.id.feedback))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
index 83fc826..3c5aa1a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
@@ -79,7 +79,6 @@
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.bubbles.BubblesTestActivity;
 import com.android.systemui.statusbar.SbnBuilder;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.phone.ShadeController;
@@ -135,7 +134,7 @@
     @Mock
     private PackageManager mMockPackageManager;
     @Mock
-    private VisualStabilityManager mVisualStabilityManager;
+    private OnUserInteractionCallback mOnUserInteractionCallback;
     @Mock
     private BubbleController mBubbleController;
     @Mock
@@ -244,7 +243,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -268,7 +267,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -293,7 +292,7 @@
                 mLauncherApps,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -319,7 +318,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -344,7 +343,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -368,7 +367,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -403,7 +402,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 entry,
@@ -428,7 +427,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -457,7 +456,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -481,7 +480,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -509,7 +508,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -537,7 +536,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -568,7 +567,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -598,7 +597,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -642,7 +641,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -685,7 +684,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -729,7 +728,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -766,7 +765,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -802,7 +801,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -840,7 +839,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -876,7 +875,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -912,7 +911,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -947,7 +946,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -981,7 +980,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -1006,7 +1005,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -1041,7 +1040,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -1081,7 +1080,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
index a90af87..7a0a19b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
@@ -221,6 +221,7 @@
                 .thenAnswer((Answer<ExpandableNotificationRowController>) invocation ->
                         new ExpandableNotificationRowController(
                                 viewCaptor.getValue(),
+                                mListContainer,
                                 mock(ActivatableNotificationViewController.class),
                                 mNotificationMediaManager,
                                 mock(PluginManager.class),
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 14994d5..c2c40ca 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
@@ -74,12 +74,11 @@
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.notification.AssistantFeedbackController;
 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.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
+import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 
@@ -114,10 +113,10 @@
 
     @Rule public MockitoRule mockito = MockitoJUnit.rule();
     @Mock private MetricsLogger mMetricsLogger;
-    @Mock private VisualStabilityManager mVisualStabilityManager;
+    @Mock private OnUserInteractionCallback mOnUserInteractionCallback;
     @Mock private NotificationPresenter mPresenter;
     @Mock private NotificationActivityStarter mNotificationActivityStarter;
-    @Mock private NotificationStackScrollLayout mStackScroller;
+    @Mock private NotificationListContainer mNotificationListContainer;
     @Mock private NotificationInfo.CheckSaveListener mCheckSaveListener;
     @Mock private OnSettingsClickListener mOnSettingsClickListener;
     @Mock private DeviceProvisionedController mDeviceProvisionedController;
@@ -143,20 +142,22 @@
         mDependency.injectTestDependency(DeviceProvisionedController.class,
                 mDeviceProvisionedController);
         mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
-        mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager);
+        mDependency.injectTestDependency(
+                OnUserInteractionCallback.class,
+                mOnUserInteractionCallback);
         mDependency.injectTestDependency(BubbleController.class, mBubbleController);
         mDependency.injectMockDependency(NotificationLockscreenUserManager.class);
         mHandler = Handler.createAsync(mTestableLooper.getLooper());
         mHelper = new NotificationTestHelper(mContext, mDependency, TestableLooper.get(this));
         when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
 
-        mGutsManager = new NotificationGutsManager(mContext, mVisualStabilityManager,
+        mGutsManager = new NotificationGutsManager(mContext,
                 () -> mStatusBar, mHandler, mHandler, mAccessibilityManager, mHighPriorityProvider,
                 mINotificationManager, mLauncherApps, mShortcutManager,
                 mChannelEditorDialogController, mContextTracker, mProvider,
                 mAssistantFeedbackController, mBubbleController,
-                new UiEventLoggerFake());
-        mGutsManager.setUpWithPresenter(mPresenter, mStackScroller,
+                new UiEventLoggerFake(), mOnUserInteractionCallback);
+        mGutsManager.setUpWithPresenter(mPresenter, mNotificationListContainer,
                 mCheckSaveListener, mOnSettingsClickListener);
         mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
     }
@@ -360,7 +361,7 @@
         verify(notificationInfoView).bindNotification(
                 any(PackageManager.class),
                 any(INotificationManager.class),
-                eq(mVisualStabilityManager),
+                eq(mOnUserInteractionCallback),
                 eq(mChannelEditorDialogController),
                 eq(statusBarNotification.getPackageName()),
                 any(NotificationChannel.class),
@@ -394,7 +395,7 @@
         verify(notificationInfoView).bindNotification(
                 any(PackageManager.class),
                 any(INotificationManager.class),
-                eq(mVisualStabilityManager),
+                eq(mOnUserInteractionCallback),
                 eq(mChannelEditorDialogController),
                 eq(statusBarNotification.getPackageName()),
                 any(NotificationChannel.class),
@@ -426,7 +427,7 @@
         verify(notificationInfoView).bindNotification(
                 any(PackageManager.class),
                 any(INotificationManager.class),
-                eq(mVisualStabilityManager),
+                eq(mOnUserInteractionCallback),
                 eq(mChannelEditorDialogController),
                 eq(statusBarNotification.getPackageName()),
                 any(NotificationChannel.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
index fd8b72b..02a3e11 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
@@ -66,7 +66,6 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 
@@ -114,7 +113,7 @@
     @Mock
     private PackageManager mMockPackageManager;
     @Mock
-    private VisualStabilityManager mVisualStabilityManager;
+    private OnUserInteractionCallback mOnUserInteractionCallback;
     @Mock
     private ChannelEditorDialogController mChannelEditorDialogController;
 
@@ -181,7 +180,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -207,7 +206,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -229,7 +228,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -260,7 +259,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -283,7 +282,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -311,7 +310,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -334,7 +333,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -356,7 +355,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mDefaultNotificationChannel,
@@ -382,7 +381,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mDefaultNotificationChannel,
@@ -404,7 +403,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -427,7 +426,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -455,7 +454,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -478,7 +477,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -502,7 +501,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -518,7 +517,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -541,7 +540,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME, mNotificationChannel,
                 createMultipleChannelSet(MULTIPLE_CHANNEL_COUNT),
@@ -569,7 +568,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -593,7 +592,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -617,7 +616,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -643,7 +642,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -665,7 +664,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -688,7 +687,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -709,7 +708,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -730,7 +729,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -751,7 +750,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -774,7 +773,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -798,7 +797,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -825,7 +824,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -852,7 +851,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -879,7 +878,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -914,7 +913,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -942,7 +941,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -982,7 +981,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -1017,7 +1016,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -1047,7 +1046,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -1082,7 +1081,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -1120,7 +1119,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -1157,7 +1156,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -1175,7 +1174,7 @@
         mNotificationInfo.findViewById(R.id.done).performClick();
         mNotificationInfo.handleCloseControls(true, false);
 
-        verify(mVisualStabilityManager).temporarilyAllowReordering();
+        verify(mOnUserInteractionCallback).onImportanceChanged(mEntry);
     }
 
     @Test
@@ -1185,7 +1184,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -1216,7 +1215,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -1250,7 +1249,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -1283,7 +1282,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -1316,7 +1315,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -1342,7 +1341,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 8ccbb2e..9a8678f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -52,6 +52,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.SmartReplyController;
 import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -68,7 +69,6 @@
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.statusbar.policy.SmartReplyConstants;
 
 import org.mockito.ArgumentCaptor;
@@ -422,10 +422,10 @@
                 mock(OnExpandClickListener.class),
                 mock(NotificationMediaManager.class),
                 mock(ExpandableNotificationRow.CoordinateOnClickListener.class),
-                mock(ExpandableNotificationRow.CoordinateOnClickListener.class),
                 mock(FalsingManager.class),
                 mStatusBarStateController,
-                mPeopleNotificationIdentifier);
+                mPeopleNotificationIdentifier,
+                mock(OnUserInteractionCallback.class));
         row.setAboveShelfChangedListener(aboveShelf -> { });
         mBindStage.getStageParams(entry).requireContentViews(extraInflationFlags);
         inflateAndWait(entry);
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 fec4677..9a6674e 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
@@ -74,13 +74,13 @@
 import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger;
 import com.android.systemui.statusbar.notification.NotificationFilter;
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 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.inflation.NotificationRowBinder;
+import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -239,7 +239,6 @@
         mStackScroller.setScrimController(mock(ScrimController.class));
         mStackScroller.setGroupManager(mGroupManager);
         mStackScroller.setEmptyShadeView(mEmptyShadeView);
-        mStackScroller.setIconAreaController(mNotificationIconAreaController);
 
         // Stub out functionality that isn't necessary to test.
         doNothing().when(mBar)
@@ -437,9 +436,9 @@
     }
 
     @Test
-    public void testOnDensityOrFontScaleChanged_reInflatesFooterViews() {
+    public void testReInflatesFooterViews() {
         clearInvocations(mStackScroller);
-        mStackScroller.onDensityOrFontScaleChanged();
+        mStackScroller.reinflateViews();
         verify(mStackScroller).setFooterView(any());
         verify(mStackScroller).setEmptyShadeView(any());
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java
new file mode 100644
index 0000000..e3acf02
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java
@@ -0,0 +1,115 @@
+/*
+ * 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.stack;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.statusbar.notification.DynamicPrivacyController;
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.tuner.TunerService;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link NotificationStackScrollLayoutController}.
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class NotificationStackScrollerControllerTest extends SysuiTestCase {
+
+    @Mock
+    private NotificationGutsManager mNotificationGutsManager;
+    @Mock
+    private HeadsUpManagerPhone mHeadsUpManager;
+    @Mock
+    private NotificationRoundnessManager mNotificationRoundnessManager;
+    @Mock
+    private TunerService mTunerService;
+    @Mock
+    private AmbientState mAmbientState;
+    @Mock
+    private DynamicPrivacyController mDynamicPrivacyController;
+    @Mock
+    private ConfigurationController mConfigurationController;
+    @Mock
+    private NotificationStackScrollLayout mNotificationStackScrollLayout;
+
+    NotificationStackScrollLayoutController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mController = new NotificationStackScrollLayoutController(
+                true,
+                mNotificationGutsManager,
+                mHeadsUpManager,
+                mNotificationRoundnessManager,
+                mTunerService,
+                mDynamicPrivacyController,
+                mConfigurationController
+        );
+
+        when(mNotificationStackScrollLayout.isAttachedToWindow()).thenReturn(true);
+    }
+
+
+    @Test
+    public void testAttach_viewAlreadyAttached() {
+        mController.attach(mNotificationStackScrollLayout);
+
+        verify(mConfigurationController).addCallback(
+                any(ConfigurationController.ConfigurationListener.class));
+    }
+    @Test
+    public void testAttach_viewAttachedAfterInit() {
+        when(mNotificationStackScrollLayout.isAttachedToWindow()).thenReturn(false);
+
+        mController.attach(mNotificationStackScrollLayout);
+
+        verify(mConfigurationController, never()).addCallback(
+                any(ConfigurationController.ConfigurationListener.class));
+
+        mController.mOnAttachStateChangeListener.onViewAttachedToWindow(
+                mNotificationStackScrollLayout);
+
+        verify(mConfigurationController).addCallback(
+                any(ConfigurationController.ConfigurationListener.class));
+    }
+
+    @Test
+    public void testOnDensityOrFontScaleChanged_reInflatesFooterViews() {
+        mController.attach(mNotificationStackScrollLayout);
+        mController.mConfigurationListener.onDensityOrFontScaleChanged();
+        verify(mNotificationStackScrollLayout).reinflateViews();
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 64907ee..f1c8ece 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -43,6 +43,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.Before;
@@ -76,7 +77,7 @@
     @Mock
     private ScrimController mScrimController;
     @Mock
-    private StatusBar mStatusBar;
+    private BiometricUnlockController.BiometricModeListener mBiometricModeListener;
     @Mock
     private ShadeController mShadeController;
     @Mock
@@ -105,11 +106,12 @@
         mDependency.injectTestDependency(NotificationMediaManager.class, mMediaManager);
         res.addOverride(com.android.internal.R.integer.config_wakeUpDelayDoze, 0);
         mBiometricUnlockController = new BiometricUnlockController(mContext, mDozeScrimController,
-                mKeyguardViewMediator, mScrimController, mStatusBar, mShadeController,
+                mKeyguardViewMediator, mScrimController, mShadeController,
                 mNotificationShadeWindowController, mKeyguardStateController, mHandler,
                 mUpdateMonitor, res.getResources(), mKeyguardBypassController, mDozeParameters,
                 mMetricsLogger, mDumpManager);
         mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
+        mBiometricUnlockController.setBiometricModeListener(mBiometricModeListener);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
index a6e2918..37ccac0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
@@ -36,15 +36,16 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.assist.AssistManager;
+import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.doze.DozeHost;
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.PulseExpansionHandler;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 
@@ -68,7 +69,6 @@
     @Mock private HeadsUpManagerPhone mHeadsUpManager;
     @Mock private ScrimController mScrimController;
     @Mock private DozeScrimController mDozeScrimController;
-    @Mock private VisualStabilityManager mVisualStabilityManager;
     @Mock private KeyguardViewMediator mKeyguardViewMediator;
     @Mock private StatusBarStateControllerImpl mStatusBarStateController;
     @Mock private BatteryController mBatteryController;
@@ -89,6 +89,7 @@
     @Mock private View mAmbientIndicationContainer;
     @Mock private BiometricUnlockController mBiometricUnlockController;
     @Mock private LockscreenLockIconController mLockscreenLockIconController;
+    @Mock private AuthController mAuthController;
 
     @Before
     public void setup() {
@@ -97,13 +98,16 @@
                 mStatusBarStateController, mDeviceProvisionedController, mHeadsUpManager,
                 mBatteryController, mScrimController, () -> mBiometricUnlockController,
                 mKeyguardViewMediator, () -> mAssistManager, mDozeScrimController,
-                mKeyguardUpdateMonitor, mVisualStabilityManager, mPulseExpansionHandler,
+                mKeyguardUpdateMonitor, mPulseExpansionHandler,
                 mNotificationShadeWindowController, mNotificationWakeUpCoordinator,
-                mLockscreenLockIconController);
+                mLockscreenLockIconController, mAuthController, mNotificationIconAreaController);
 
-        mDozeServiceHost.initialize(mStatusBar, mNotificationIconAreaController,
-                mStatusBarKeyguardViewManager, mNotificationShadeWindowViewController,
-                mNotificationPanel, mAmbientIndicationContainer);
+        mDozeServiceHost.initialize(
+                mStatusBar,
+                mStatusBarKeyguardViewManager,
+                mNotificationShadeWindowViewController,
+                mNotificationPanel,
+                mAmbientIndicationContainer);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
index 6d642ec..2239b1b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
@@ -33,9 +33,10 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.AlertingNotificationManager;
 import com.android.systemui.statusbar.AlertingNotificationManagerTest;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
index 3e46907..ccdc69a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
@@ -34,6 +34,7 @@
 
 import com.android.internal.view.AppearanceRegion;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.statusbar.policy.BatteryController;
 
 import org.junit.Before;
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
deleted file mode 100644
index 00cbddc..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
+++ /dev/null
@@ -1,336 +0,0 @@
-/*
- * Copyright (C) 2017 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.phone;
-
-import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT;
-import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
-import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT;
-import static android.inputmethodservice.InputMethodService.IME_VISIBLE;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
-
-import static com.android.systemui.statusbar.phone.NavigationBarFragment.NavBarActionEvent.NAVBAR_ASSIST_LONGPRESS;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.annotation.LayoutRes;
-import android.annotation.Nullable;
-import android.app.Fragment;
-import android.app.FragmentController;
-import android.app.FragmentHostCallback;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.IntentFilter;
-import android.hardware.display.DisplayManagerGlobal;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.UserHandle;
-import android.testing.AndroidTestingRunner;
-import android.testing.LeakCheck.Tracker;
-import android.testing.TestableLooper;
-import android.testing.TestableLooper.RunWithLooper;
-import android.view.Display;
-import android.view.DisplayInfo;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.Dependency;
-import com.android.systemui.SysuiBaseFragmentTest;
-import com.android.systemui.SysuiTestableContext;
-import com.android.systemui.accessibility.SystemActions;
-import com.android.systemui.assist.AssistManager;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.model.SysUiState;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.recents.OverviewProxyService;
-import com.android.systemui.recents.Recents;
-import com.android.systemui.stackdivider.Divider;
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.Optional;
-
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper(setAsMainLooper = true)
-@SmallTest
-public class NavigationBarFragmentTest extends SysuiBaseFragmentTest {
-    private static final int EXTERNAL_DISPLAY_ID = 2;
-    private static final int NAV_BAR_VIEW_ID = 43;
-
-    private Fragment mFragmentExternalDisplay;
-    private FragmentController mControllerExternalDisplay;
-
-    private SysuiTestableContext mSysuiTestableContextExternal;
-    private OverviewProxyService mOverviewProxyService;
-    private CommandQueue mCommandQueue;
-    private SysUiState mMockSysUiState;
-    @Mock
-    private BroadcastDispatcher mBroadcastDispatcher;
-    @Mock
-    private Divider mDivider;
-    @Mock
-    private Recents mRecents;
-    @Mock
-    private SystemActions mSystemActions;
-    @Mock
-    private UiEventLogger mUiEventLogger;
-
-    private AccessibilityManagerWrapper mAccessibilityWrapper =
-            new AccessibilityManagerWrapper(mContext) {
-                Tracker mTracker = mLeakCheck.getTracker("accessibility_manager");
-
-                @Override
-                public void addCallback(AccessibilityServicesStateChangeListener listener) {
-                    mTracker.getLeakInfo(listener).addAllocation(new Throwable());
-                }
-
-                @Override
-                public void removeCallback(AccessibilityServicesStateChangeListener listener) {
-                    mTracker.getLeakInfo(listener).clearAllocations();
-                }
-            };
-
-    public NavigationBarFragmentTest() {
-        super(NavigationBarFragment.class);
-    }
-
-    protected void createRootView() {
-        mView = new NavigationBarFrame(mSysuiContext);
-        mView.setId(NAV_BAR_VIEW_ID);
-    }
-
-    @Before
-    public void setupFragment() throws Exception {
-        MockitoAnnotations.initMocks(this);
-
-        mCommandQueue = new CommandQueue(mContext);
-        setupSysuiDependency();
-        createRootView();
-        mOverviewProxyService =
-                mDependency.injectMockDependency(OverviewProxyService.class);
-        TestableLooper.get(this).runWithLooper(() -> {
-            mHandler = new Handler();
-
-            mFragment = instantiate(mSysuiContext, NavigationBarFragment.class.getName(), null);
-            mFragments = FragmentController.createController(
-                    new HostCallbacksForExternalDisplay(mSysuiContext));
-            mFragments.attachHost(null);
-            mFragments.getFragmentManager().beginTransaction()
-                    .replace(NAV_BAR_VIEW_ID, mFragment)
-                    .commit();
-            mControllerExternalDisplay = FragmentController.createController(
-                    new HostCallbacksForExternalDisplay(mSysuiTestableContextExternal));
-            mControllerExternalDisplay.attachHost(null);
-            mFragmentExternalDisplay = instantiate(mSysuiTestableContextExternal,
-                    NavigationBarFragment.class.getName(), null);
-            mControllerExternalDisplay.getFragmentManager().beginTransaction()
-                    .replace(NAV_BAR_VIEW_ID, mFragmentExternalDisplay)
-                    .commit();
-        });
-    }
-
-    private void setupSysuiDependency() {
-        Display display = new Display(DisplayManagerGlobal.getInstance(), EXTERNAL_DISPLAY_ID,
-                new DisplayInfo(), DEFAULT_DISPLAY_ADJUSTMENTS);
-        mSysuiTestableContextExternal = (SysuiTestableContext) mSysuiContext.createDisplayContext(
-                display);
-
-        injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
-        WindowManager windowManager = mock(WindowManager.class);
-        Display defaultDisplay = mContext.getSystemService(WindowManager.class).getDefaultDisplay();
-        when(windowManager.getDefaultDisplay()).thenReturn(
-                defaultDisplay);
-        mContext.addMockSystemService(Context.WINDOW_SERVICE, windowManager);
-
-        mDependency.injectTestDependency(Dependency.BG_LOOPER, Looper.getMainLooper());
-        mDependency.injectTestDependency(AccessibilityManagerWrapper.class, mAccessibilityWrapper);
-
-        mMockSysUiState = mock(SysUiState.class);
-        when(mMockSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mMockSysUiState);
-    }
-
-    @Test
-    public void testHomeLongPress() {
-        NavigationBarFragment navigationBarFragment = (NavigationBarFragment) mFragment;
-
-        mFragments.dispatchResume();
-        processAllMessages();
-        navigationBarFragment.onHomeLongClick(navigationBarFragment.getView());
-
-        verify(mUiEventLogger, times(1)).log(NAVBAR_ASSIST_LONGPRESS);
-    }
-
-    @Test
-    public void testRegisteredWithDispatcher() {
-        mFragments.dispatchResume();
-        processAllMessages();
-
-        verify(mBroadcastDispatcher).registerReceiverWithHandler(
-                any(BroadcastReceiver.class),
-                any(IntentFilter.class),
-                any(Handler.class),
-                any(UserHandle.class));
-    }
-
-    @Test
-    public void testSetImeWindowStatusWhenImeSwitchOnDisplay() {
-        // Create default & external NavBar fragment.
-        NavigationBarFragment defaultNavBar = (NavigationBarFragment) mFragment;
-        NavigationBarFragment externalNavBar = (NavigationBarFragment) mFragmentExternalDisplay;
-        mFragments.dispatchCreate();
-        processAllMessages();
-        mFragments.dispatchResume();
-        processAllMessages();
-        mControllerExternalDisplay.dispatchCreate();
-        processAllMessages();
-        mControllerExternalDisplay.dispatchResume();
-        processAllMessages();
-
-        // Set IME window status for default NavBar.
-        mCommandQueue.setImeWindowStatus(DEFAULT_DISPLAY, null, IME_VISIBLE,
-                BACK_DISPOSITION_DEFAULT, true, false);
-        processAllMessages();
-
-        // Verify IME window state will be updated in default NavBar & external NavBar state reset.
-        assertEquals(NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_SHOWN,
-                defaultNavBar.getNavigationIconHints());
-        assertFalse((externalNavBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0);
-        assertFalse((externalNavBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0);
-
-        // Set IME window status for external NavBar.
-        mCommandQueue.setImeWindowStatus(EXTERNAL_DISPLAY_ID, null,
-                IME_VISIBLE, BACK_DISPOSITION_DEFAULT, true, false);
-        processAllMessages();
-
-        // Verify IME window state will be updated in external NavBar & default NavBar state reset.
-        assertEquals(NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_SHOWN,
-                externalNavBar.getNavigationIconHints());
-        assertFalse((defaultNavBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0);
-        assertFalse((defaultNavBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0);
-    }
-
-    @Override
-    protected Fragment instantiate(Context context, String className, Bundle arguments) {
-        DeviceProvisionedController deviceProvisionedController =
-                mock(DeviceProvisionedController.class);
-        when(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
-        assertNotNull(mAccessibilityWrapper);
-        return new NavigationBarFragment(
-                context.getDisplayId() == DEFAULT_DISPLAY ? mAccessibilityWrapper
-                        : mock(AccessibilityManagerWrapper.class),
-                deviceProvisionedController,
-                new MetricsLogger(),
-                mock(AssistManager.class),
-                mOverviewProxyService,
-                mock(NavigationModeController.class),
-                mock(StatusBarStateController.class),
-                mMockSysUiState,
-                mBroadcastDispatcher,
-                mCommandQueue,
-                mDivider,
-                Optional.of(mRecents),
-                () -> mock(StatusBar.class),
-                mock(ShadeController.class),
-                mock(NotificationRemoteInputManager.class),
-                mock(SystemActions.class),
-                mHandler,
-                mUiEventLogger);
-    }
-
-    private class HostCallbacksForExternalDisplay extends
-            FragmentHostCallback<NavigationBarFragmentTest> {
-        private Context mDisplayContext;
-
-        HostCallbacksForExternalDisplay(Context context) {
-            super(context, mHandler, 0);
-            mDisplayContext = context;
-        }
-
-        @Override
-        public NavigationBarFragmentTest onGetHost() {
-            return NavigationBarFragmentTest.this;
-        }
-
-        @Override
-        public Fragment instantiate(Context context, String className, Bundle arguments) {
-            return NavigationBarFragmentTest.this.instantiate(context, className, arguments);
-        }
-
-        @Override
-        public View onFindViewById(int id) {
-            return mView.findViewById(id);
-        }
-
-        @Override
-        public LayoutInflater onGetLayoutInflater() {
-            return new LayoutInflaterWrapper(mDisplayContext);
-        }
-    }
-
-    private static class LayoutInflaterWrapper extends LayoutInflater {
-        protected LayoutInflaterWrapper(Context context) {
-            super(context);
-        }
-
-        @Override
-        public LayoutInflater cloneInContext(Context newContext) {
-            return null;
-        }
-
-        @Override
-        public View inflate(@LayoutRes int resource, @Nullable ViewGroup root,
-                boolean attachToRoot) {
-            NavigationBarView view = mock(NavigationBarView.class);
-            when(view.getDisplay()).thenReturn(mContext.getDisplay());
-            when(view.getBackButton()).thenReturn(mock(ButtonDispatcher.class));
-            when(view.getHomeButton()).thenReturn(mock(ButtonDispatcher.class));
-            when(view.getRecentsButton()).thenReturn(mock(ButtonDispatcher.class));
-            when(view.getAccessibilityButton()).thenReturn(mock(ButtonDispatcher.class));
-            when(view.getRotateSuggestionButton()).thenReturn(mock(RotationContextButton.class));
-            when(view.getBarTransitions()).thenReturn(mock(NavigationBarTransitions.class));
-            when(view.getLightTransitionsController()).thenReturn(
-                    mock(LightBarTransitionsController.class));
-            when(view.getRotationButtonController()).thenReturn(
-                    mock(RotationButtonController.class));
-            when(view.isRecentsButtonVisible()).thenReturn(true);
-            return view;
-        }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java
index d1a439f..5222fff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java
@@ -17,8 +17,6 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -29,10 +27,13 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.demomode.DemoModeController;
+import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -48,8 +49,6 @@
     @Mock
     private NotificationListener mListener;
     @Mock
-    StatusBar mStatusBar;
-    @Mock
     StatusBarStateController mStatusBarStateController;
     @Mock
     NotificationWakeUpCoordinator mWakeUpCoordinator;
@@ -58,26 +57,37 @@
     @Mock
     NotificationMediaManager mNotificationMediaManager;
     @Mock
-    NotificationIconContainer mNotificationIconContainer;
-    @Mock
     DozeParameters mDozeParameters;
     @Mock
-    NotificationShadeWindowView mNotificationShadeWindowView;
+    CommonNotifCollection mNotifCollection;
+    @Mock
+    DarkIconDispatcher mDarkIconDispatcher;
+    @Mock
+    StatusBarWindowController mStatusBarWindowController;
     private NotificationIconAreaController mController;
     @Mock
     private BubbleController mBubbleController;
+    @Mock private DemoModeController mDemoModeController;
+    @Mock
+    private NotificationIconContainer mAodIcons;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
 
-        when(mStatusBar.getNotificationShadeWindowView()).thenReturn(mNotificationShadeWindowView);
-        when(mNotificationShadeWindowView.findViewById(anyInt())).thenReturn(
-                mNotificationIconContainer);
-
-        mController = new NotificationIconAreaController(mContext, mStatusBar,
-                mStatusBarStateController, mWakeUpCoordinator, mKeyguardBypassController,
-                mNotificationMediaManager, mListener, mDozeParameters, mBubbleController);
+        mController = new NotificationIconAreaController(
+                mContext,
+                mStatusBarStateController,
+                mWakeUpCoordinator,
+                mKeyguardBypassController,
+                mNotificationMediaManager,
+                mListener,
+                mDozeParameters,
+                mBubbleController,
+                mDemoModeController,
+                mDarkIconDispatcher,
+                mStatusBarWindowController);
+        mController.setupAodIcons(mAodIcons);
     }
 
     @Test
@@ -98,7 +108,7 @@
     public void testAppearResetsTranslation() {
         when(mDozeParameters.shouldControlScreenOff()).thenReturn(false);
         mController.appearAodIcons();
-        verify(mNotificationIconContainer).setTranslationY(0);
-        verify(mNotificationIconContainer).setAlpha(1.0f);
+        verify(mAodIcons).setTranslationY(0);
+        verify(mAodIcons).setAlpha(1.0f);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index f66fd56..c9e9d94 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -72,7 +72,6 @@
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
-import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
@@ -188,6 +187,9 @@
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     @Mock
     private KeyguardClockSwitchController mKeyguardClockSwitchController;
+    @Mock
+    private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
+
     private NotificationPanelViewController mNotificationPanelViewController;
     private View.AccessibilityDelegate mAccessibiltyDelegate;
 
@@ -205,8 +207,11 @@
         when(mView.findViewById(R.id.keyguard_clock_container)).thenReturn(mKeyguardClockSwitch);
         when(mView.findViewById(R.id.notification_stack_scroller))
                 .thenReturn(mNotificationStackScrollLayout);
-        when(mNotificationStackScrollLayout.getHeight()).thenReturn(1000);
-        when(mNotificationStackScrollLayout.getHeadsUpCallback()).thenReturn(mHeadsUpCallback);
+        when(mNotificationStackScrollLayout.getController())
+                .thenReturn(mNotificationStackScrollLayoutController);
+        when(mNotificationStackScrollLayoutController.getHeight()).thenReturn(1000);
+        when(mNotificationStackScrollLayoutController.getHeadsUpCallback())
+                .thenReturn(mHeadsUpCallback);
         when(mView.findViewById(R.id.keyguard_bottom_area)).thenReturn(mKeyguardBottomArea);
         when(mKeyguardBottomArea.getLeftView()).thenReturn(mock(KeyguardAffordanceView.class));
         when(mKeyguardBottomArea.getRightView()).thenReturn(mock(KeyguardAffordanceView.class));
@@ -233,11 +238,6 @@
                 mock(NotificationRoundnessManager.class),
                 mStatusBarStateController,
                 new FalsingManagerFake());
-        NotificationStackScrollLayoutController notificationStackScrollLayoutController =
-                new NotificationStackScrollLayoutController(
-                        true /* allowLongPress */,
-                        mock(NotificationGutsManager.class)
-                );
         mNotificationPanelViewController = new NotificationPanelViewController(mView,
                 mInjectionInflationController,
                 coordinator, expansionHandler, mDynamicPrivacyController, mKeyguardBypassController,
@@ -251,9 +251,13 @@
                 mConversationNotificationManager, mMediaHiearchyManager,
                 mBiometricUnlockController, mStatusBarKeyguardViewManager,
                 () -> mKeyguardClockSwitchController,
-                notificationStackScrollLayoutController);
-        mNotificationPanelViewController.initDependencies(mStatusBar, mGroupManager,
-                mNotificationShelfController, mNotificationAreaController, mScrimController);
+                mNotificationStackScrollLayoutController,
+                mNotificationAreaController);
+        mNotificationPanelViewController.initDependencies(
+                mStatusBar,
+                mGroupManager,
+                mNotificationShelfController,
+                mScrimController);
         mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);
         mNotificationPanelViewController.setBar(mPanelBar);
 
@@ -267,8 +271,10 @@
     public void testSetDozing_notifiesNsslAndStateController() {
         mNotificationPanelViewController.setDozing(true /* dozing */, true /* animate */,
                 null /* touch */);
-        InOrder inOrder = inOrder(mNotificationStackScrollLayout, mStatusBarStateController);
-        inOrder.verify(mNotificationStackScrollLayout).setDozing(eq(true), eq(true), eq(null));
+        InOrder inOrder = inOrder(
+                mNotificationStackScrollLayoutController, mStatusBarStateController);
+        inOrder.verify(mNotificationStackScrollLayoutController)
+                .setDozing(eq(true), eq(true), eq(null));
         inOrder.verify(mStatusBarStateController).setDozeAmount(eq(1f), eq(true));
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java
similarity index 97%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java
index 8c37cf1..fcea17c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java
@@ -57,7 +57,7 @@
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
 @SmallTest
-public class NotificationShadeWindowControllerTest extends SysuiTestCase {
+public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
 
     @Mock private WindowManager mWindowManager;
     @Mock private DozeParameters mDozeParameters;
@@ -72,7 +72,7 @@
     @Mock private DumpManager mDumpManager;
     @Captor private ArgumentCaptor<WindowManager.LayoutParams> mLayoutParameters;
 
-    private NotificationShadeWindowController mNotificationShadeWindowController;
+    private NotificationShadeWindowControllerImpl mNotificationShadeWindowController;
 
     @Before
     public void setUp() {
@@ -80,7 +80,7 @@
         when(mDozeParameters.getAlwaysOn()).thenReturn(true);
         when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
 
-        mNotificationShadeWindowController = new NotificationShadeWindowController(mContext,
+        mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(mContext,
                 mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController,
                 mConfigurationController, mKeyguardViewMediator, mKeyguardBypassController,
                 mColorExtractor, mDumpManager);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
index e04d25b..c1d51f3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
@@ -38,6 +38,7 @@
 import com.android.systemui.statusbar.DragDownHelper;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.PulseExpansionHandler;
 import com.android.systemui.statusbar.SuperStatusBarViewFactory;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -100,7 +101,9 @@
 
         mController = new NotificationShadeWindowViewController(
                 new InjectionInflationController(
-                        SystemUIFactory.getInstance().getRootComponent()),
+                        SystemUIFactory.getInstance()
+                                .getSysUIComponent()
+                                .createViewInstanceCreatorFactory()),
                 mCoordinator,
                 mPulseExpansionHandler,
                 mDynamicPrivacyController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 675a2df..ccc3078 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -43,9 +43,11 @@
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 3306734..3f631b1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -73,7 +73,7 @@
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
-import com.android.systemui.statusbar.notification.row.OnDismissCallback;
+import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
@@ -132,7 +132,7 @@
     @Mock
     private Intent mContentIntentInner;
     @Mock
-    private OnDismissCallback mOnDismissCallback;
+    private OnUserInteractionCallback mOnUserInteractionCallback;
     @Mock
     private NotificationActivityStarter mNotificationActivityStarter;
     private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
@@ -184,7 +184,6 @@
                         getContext(),
                         mock(CommandQueue.class),
                         mHandler,
-                        mHandler,
                         mUiBgExecutor,
                         mEntryManager,
                         mNotifPipeline,
@@ -211,7 +210,7 @@
                         mFeatureFlags,
                         mock(MetricsLogger.class),
                         mock(StatusBarNotificationActivityStarterLogger.class),
-                        mOnDismissCallback)
+                        mOnUserInteractionCallback)
                 .setStatusBar(mStatusBar)
                 .setNotificationPresenter(mock(NotificationPresenter.class))
                 .setNotificationPanelViewController(mock(NotificationPanelViewController.class))
@@ -234,9 +233,6 @@
         // set up Handler to synchronously invoke the Runnable arg
         doAnswer(answerVoid(Runnable::run))
                 .when(mHandler).post(any(Runnable.class));
-
-        doAnswer(answerVoid(Runnable::run))
-                .when(mHandler).postAtFrontOfQueue(any(Runnable.class));
     }
 
     @Test
@@ -271,7 +267,7 @@
                 eq(sbn.getKey()), any(NotificationVisibility.class));
 
         // Notification calls dismiss callback to remove notification due to FLAG_AUTO_CANCEL
-        verify(mOnDismissCallback).onDismiss(mNotificationRow.getEntry(), REASON_CLICK);
+        verify(mOnUserInteractionCallback).onDismiss(mNotificationRow.getEntry(), REASON_CLICK);
     }
 
     @Test
@@ -300,7 +296,8 @@
         verifyZeroInteractions(mContentIntent);
 
         // Notification should not be cancelled.
-        verify(mOnDismissCallback, never()).onDismiss(eq(mNotificationRow.getEntry()), anyInt());
+        verify(mOnUserInteractionCallback, never()).onDismiss(eq(mNotificationRow.getEntry()),
+                anyInt());
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index 318e9b8..c0ebfad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -23,13 +23,11 @@
 
 import android.app.Notification;
 import android.app.StatusBarManager;
-import android.content.Context;
 import android.metrics.LogMaker;
 import android.support.test.metricshelper.MetricsAsserts;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
-import android.view.ViewGroup;
 
 import androidx.test.filters.SmallTest;
 
@@ -45,20 +43,23 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.NotificationViewHierarchyManager;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.Before;
@@ -112,11 +113,17 @@
 
         NotificationShadeWindowView notificationShadeWindowView =
                 mock(NotificationShadeWindowView.class);
+        NotificationStackScrollLayoutController stackScrollLayoutController =
+                mock(NotificationStackScrollLayoutController.class);
+        when(stackScrollLayoutController.getView()).thenReturn(
+                mock(NotificationStackScrollLayout.class));
+        when(stackScrollLayoutController.getNotificationListContainer()).thenReturn(
+                mock(NotificationListContainer.class));
         when(notificationShadeWindowView.getResources()).thenReturn(mContext.getResources());
 
         mStatusBarNotificationPresenter = new StatusBarNotificationPresenter(mContext,
                 mock(NotificationPanelViewController.class), mock(HeadsUpManagerPhone.class),
-                notificationShadeWindowView, mock(NotificationListContainerViewGroup.class),
+                notificationShadeWindowView, stackScrollLayoutController,
                 mock(DozeScrimController.class), mock(ScrimController.class),
                 mock(ActivityLaunchAnimator.class), mock(DynamicPrivacyController.class),
                 mock(KeyguardStateController.class),
@@ -205,14 +212,4 @@
                 new LogMaker(MetricsEvent.ACTION_LS_NOTE)
                         .setType(MetricsEvent.TYPE_ACTION));
     }
-
-    // We need this because mockito doesn't know how to construct a mock that extends ViewGroup
-    // and implements NotificationListContainer without it because of classloader issues.
-    private abstract static class NotificationListContainerViewGroup extends ViewGroup
-            implements NotificationListContainer {
-
-        public NotificationListContainerViewGroup(Context context) {
-            super(context);
-        }
-    }
 }
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 5a08c9c..8462386 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
@@ -83,10 +83,12 @@
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.PluginDependencyProvider;
@@ -97,12 +99,12 @@
 import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.KeyguardIndicationController;
-import com.android.systemui.statusbar.NavigationBarController;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.NotificationViewHierarchyManager;
 import com.android.systemui.statusbar.PulseExpansionHandler;
 import com.android.systemui.statusbar.RemoteInputController;
@@ -115,16 +117,18 @@
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationFilter;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.logging.NotificationPanelLoggerFake;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
+import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -171,6 +175,8 @@
     @Mock private KeyguardStateController mKeyguardStateController;
     @Mock private KeyguardIndicationController mKeyguardIndicationController;
     @Mock private NotificationStackScrollLayout mStackScroller;
+    @Mock private NotificationStackScrollLayoutController mStackScrollerController;
+    @Mock private NotificationListContainer mNotificationListContainer;
     @Mock private HeadsUpManagerPhone mHeadsUpManager;
     @Mock private NotificationPanelViewController mNotificationPanelViewController;
     @Mock private NotificationPanelView mNotificationPanelView;
@@ -248,6 +254,7 @@
     @Mock private ExtensionController mExtensionController;
     @Mock private UserInfoControllerImpl mUserInfoControllerImpl;
     @Mock private PhoneStatusBarPolicy mPhoneStatusBarPolicy;
+    @Mock private DemoModeController mDemoModeController;
     @Mock private Lazy<NotificationShadeDepthController> mNotificationShadeDepthControllerLazy;
     private ShadeController mShadeController;
     private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
@@ -283,6 +290,10 @@
 
         mContext.setTheme(R.style.Theme_SystemUI_Light);
 
+        when(mStackScroller.getController()).thenReturn(mStackScrollerController);
+        when(mStackScrollerController.getView()).thenReturn(mStackScroller);
+        when(mStackScrollerController.getNotificationListContainer()).thenReturn(
+                mNotificationListContainer);
         when(mStackScroller.generateLayoutParams(any())).thenReturn(new LayoutParams(0, 0));
         when(mNotificationPanelViewController.getView()).thenReturn(mNotificationPanelView);
         when(mNotificationPanelView.getLayoutParams()).thenReturn(new LayoutParams(0, 0));
@@ -394,7 +405,6 @@
                 mStatusBarKeyguardViewManager,
                 mViewMediatorCallback,
                 mInitController,
-                mDarkIconDispatcher,
                 new Handler(TestableLooper.get(this).getLooper()),
                 mPluginDependencyProvider,
                 mKeyguardDismissUtil,
@@ -403,8 +413,10 @@
                 mPhoneStatusBarPolicy,
                 mKeyguardIndicationController,
                 mDismissCallbackRegistry,
+                mDemoModeController,
                 mNotificationShadeDepthControllerLazy,
-                mStatusBarTouchableRegionManager);
+                mStatusBarTouchableRegionManager,
+                mNotificationIconAreaController);
 
         when(mNotificationShadeWindowView.findViewById(R.id.lock_icon_container)).thenReturn(
                 mLockIconContainer);
@@ -422,14 +434,13 @@
         mStatusBar.mNotificationShadeWindowView = mNotificationShadeWindowView;
         mStatusBar.mNotificationPanelViewController = mNotificationPanelViewController;
         mStatusBar.mDozeScrimController = mDozeScrimController;
-        mStatusBar.mNotificationIconAreaController = mNotificationIconAreaController;
         mStatusBar.mPresenter = mNotificationPresenter;
         mStatusBar.mKeyguardIndicationController = mKeyguardIndicationController;
         mStatusBar.mBarService = mBarService;
         mStatusBar.mStackScroller = mStackScroller;
         mStatusBar.startKeyguard();
         mInitController.executePostInitTasks();
-        notificationLogger.setUpWithContainer(mStackScroller);
+        notificationLogger.setUpWithContainer(mNotificationListContainer);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
index eca48c8..23fa6fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
@@ -29,6 +29,7 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.power.EnhancedEstimates;
 
 import org.junit.Assert;
@@ -44,17 +45,21 @@
 @TestableLooper.RunWithLooper
 public class BatteryControllerTest extends SysuiTestCase {
 
-    @Mock
-    private PowerManager mPowerManager;
-    @Mock
-    private BroadcastDispatcher mBroadcastDispatcher;
+    @Mock private PowerManager mPowerManager;
+    @Mock private BroadcastDispatcher mBroadcastDispatcher;
+    @Mock private DemoModeController mDemoModeController;
     private BatteryControllerImpl mBatteryController;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mBatteryController = new BatteryControllerImpl(getContext(), mock(EnhancedEstimates.class),
-                mPowerManager, mBroadcastDispatcher, new Handler(), new Handler());
+        mBatteryController = new BatteryControllerImpl(getContext(),
+                mock(EnhancedEstimates.class),
+                mPowerManager,
+                mBroadcastDispatcher,
+                mDemoModeController,
+                new Handler(),
+                new Handler());
         mBatteryController.init();
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index aef454f..7db1b83 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -66,6 +66,7 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
 import com.android.systemui.statusbar.policy.NetworkController.IconState;
 import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
@@ -113,6 +114,7 @@
     protected DeviceProvisionedController mMockProvisionController;
     protected DeviceProvisionedListener mUserCallback;
     protected Instrumentation mInstrumentation;
+    protected DemoModeController mDemoModeController;
 
     protected int mSubId;
 
@@ -146,6 +148,7 @@
         res.addOverride(R.string.cell_data_off_content_description, NO_DATA_STRING);
         res.addOverride(R.string.not_default_data_content_description, NOT_DEFAULT_DATA_STRING);
 
+        mDemoModeController = mock(DemoModeController.class);
         mMockWm = mock(WifiManager.class);
         mMockTm = mock(TelephonyManager.class);
         mMockSm = mock(SubscriptionManager.class);
@@ -200,10 +203,21 @@
             return null;
         }).when(mMockProvisionController).addCallback(any());
 
-        mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm,
-                mMockNsm, mMockSm, mConfig, TestableLooper.get(this).getLooper(), mCallbackHandler,
-                mock(AccessPointControllerImpl.class), mock(DataUsageController.class),
-                mMockSubDefaults, mMockProvisionController, mMockBd);
+        mNetworkController = new NetworkControllerImpl(mContext,
+                mMockCm,
+                mMockTm,
+                mMockWm,
+                mMockNsm,
+                mMockSm,
+                mConfig,
+                TestableLooper.get(this).getLooper(),
+                mCallbackHandler,
+                mock(AccessPointControllerImpl.class),
+                mock(DataUsageController.class),
+                mMockSubDefaults,
+                mMockProvisionController,
+                mMockBd,
+                mDemoModeController);
         setupNetworkController();
 
         // Trigger blank callbacks to always get the current state (some tests don't trigger
@@ -254,7 +268,7 @@
                         mConfig, TestableLooper.get(this).getLooper(), mCallbackHandler,
                         mock(AccessPointControllerImpl.class),
                         mock(DataUsageController.class), mMockSubDefaults,
-                        mock(DeviceProvisionedController.class), mMockBd);
+                        mock(DeviceProvisionedController.class), mMockBd, mDemoModeController);
 
         setupNetworkController();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
index 6fffcff..d8aa29e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
@@ -106,7 +106,7 @@
                 mMockNsm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler,
                 mock(AccessPointControllerImpl.class),
                 mock(DataUsageController.class), mMockSubDefaults,
-                mock(DeviceProvisionedController.class), mMockBd);
+                mock(DeviceProvisionedController.class), mMockBd, mDemoModeController);
         setupNetworkController();
 
         setupDefaultSignal();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
index 3b27437..61f71b7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
@@ -63,7 +63,8 @@
         mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm,
                 mMockNsm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler,
                 mock(AccessPointControllerImpl.class), mock(DataUsageController.class),
-                mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd);
+                mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd,
+                mDemoModeController);
         setupNetworkController();
 
         verifyLastMobileDataIndicators(false, -1, 0);
@@ -81,7 +82,8 @@
         mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm,
                 mMockNsm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler,
                 mock(AccessPointControllerImpl.class), mock(DataUsageController.class),
-                mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd);
+                mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd,
+                mDemoModeController);
         mNetworkController.registerListeners();
 
         // Wait for the main looper to execute the previous command
@@ -147,7 +149,8 @@
         mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm,
                 mMockNsm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler,
                 mock(AccessPointControllerImpl.class), mock(DataUsageController.class),
-                mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd);
+                mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd,
+                mDemoModeController);
         setupNetworkController();
 
         // No Subscriptions.
diff --git a/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index 9bb01ae..64be2d9 100644
--- a/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -50,7 +50,7 @@
 import androidx.test.filters.MediumTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.testutils.HandlerUtilsKt;
+import com.android.testutils.HandlerUtils;
 import com.android.testutils.TapPacketReader;
 
 import org.junit.After;
@@ -366,7 +366,7 @@
     private TapPacketReader makePacketReader(FileDescriptor fd, int mtu) {
         final TapPacketReader reader = new TapPacketReader(mHandler, fd, mtu);
         mHandler.post(() -> reader.start());
-        HandlerUtilsKt.waitForIdle(mHandler, TIMEOUT_MS);
+        HandlerUtils.waitForIdle(mHandler, TIMEOUT_MS);
         return reader;
     }
 
diff --git a/packages/Tethering/tests/unit/src/android/net/util/TetheringUtilsTest.java b/packages/Tethering/tests/unit/src/android/net/util/TetheringUtilsTest.java
index 1499f3b..91c7771 100644
--- a/packages/Tethering/tests/unit/src/android/net/util/TetheringUtilsTest.java
+++ b/packages/Tethering/tests/unit/src/android/net/util/TetheringUtilsTest.java
@@ -27,7 +27,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.testutils.MiscAssertsKt;
+import com.android.testutils.MiscAsserts;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -82,6 +82,6 @@
         request.showProvisioningUi = false;
         assertFalse(TetheringUtils.isTetheringRequestEquals(mTetheringRequest, request));
 
-        MiscAssertsKt.assertFieldCountEquals(5, TetheringRequestParcel.class);
+        MiscAsserts.assertFieldCountEquals(5, TetheringRequestParcel.class);
     }
 }
diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java
index b291438..ce52ae2 100644
--- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java
@@ -30,8 +30,8 @@
 import static com.android.networkstack.tethering.OffloadController.StatsType.STATS_PER_UID;
 import static com.android.networkstack.tethering.OffloadHardwareInterface.ForwardedStats;
 import static com.android.networkstack.tethering.TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS;
-import static com.android.testutils.MiscAssertsKt.assertContainsAll;
-import static com.android.testutils.MiscAssertsKt.assertThrows;
+import static com.android.testutils.MiscAsserts.assertContainsAll;
+import static com.android.testutils.MiscAsserts.assertThrows;
 import static com.android.testutils.NetworkStatsUtilsKt.assertNetworkStatsEquals;
 
 import static junit.framework.Assert.assertNotNull;
diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index 46fe5cf..1fe3840 100644
--- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -143,7 +143,7 @@
 import com.android.internal.util.StateMachine;
 import com.android.internal.util.test.BroadcastInterceptingContext;
 import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.testutils.MiscAssertsKt;
+import com.android.testutils.MiscAsserts;
 
 import org.junit.After;
 import org.junit.AfterClass;
@@ -1360,7 +1360,7 @@
         assertEquals(0, parcel.localOnlyList.length);
         assertEquals(0, parcel.erroredIfaceList.length);
         assertEquals(0, parcel.lastErrorList.length);
-        MiscAssertsKt.assertFieldCountEquals(5, TetherStatesParcel.class);
+        MiscAsserts.assertFieldCountEquals(5, TetherStatesParcel.class);
     }
 
     @Test
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 833aeec..c41f400 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1530,12 +1530,12 @@
         int serviceCount = userState.mBoundServices.size();
         for (int i = 0; i < serviceCount; i++) {
             AccessibilityServiceConnection service = userState.mBoundServices.get(i);
-            relevantEventTypes |= isClientInPackageWhitelist(service.getServiceInfo(), client)
+            relevantEventTypes |= isClientInPackageAllowlist(service.getServiceInfo(), client)
                     ? service.getRelevantEventTypes()
                     : 0;
         }
 
-        relevantEventTypes |= isClientInPackageWhitelist(
+        relevantEventTypes |= isClientInPackageAllowlist(
                 mUiAutomationManager.getServiceInfo(), client)
                 ? mUiAutomationManager.getRelevantEventTypes()
                 : 0;
@@ -1571,7 +1571,7 @@
         }
     }
 
-    private static boolean isClientInPackageWhitelist(
+    private static boolean isClientInPackageAllowlist(
             @Nullable AccessibilityServiceInfo serviceInfo, Client client) {
         if (serviceInfo == null) return false;
 
@@ -1590,7 +1590,7 @@
                 Slog.d(LOG_TAG, "Dropping events: "
                         + Arrays.toString(clientPackages) + " -> "
                         + serviceInfo.getComponentName().flattenToShortString()
-                        + " due to not being in package whitelist "
+                        + " due to not being in package allowlist "
                         + Arrays.toString(serviceInfo.packageNames));
             }
         }
@@ -1991,9 +1991,9 @@
     }
 
     private void updateLegacyCapabilitiesLocked(AccessibilityUserState userState) {
-        // Up to JB-MR1 we had a white list with services that can enable touch
+        // Up to JB-MR1 we had a allowlist with services that can enable touch
         // exploration. When a service is first started we show a dialog to the
-        // use to get a permission to white list the service.
+        // use to get a permission to allowlist the service.
         final int installedServiceCount = userState.mInstalledServices.size();
         for (int i = 0; i < installedServiceCount; i++) {
             AccessibilityServiceInfo serviceInfo = userState.mInstalledServices.get(i);
@@ -2261,9 +2261,9 @@
         }
         if (service.getServiceInfo().getResolveInfo().serviceInfo.applicationInfo.targetSdkVersion
                 <= Build.VERSION_CODES.JELLY_BEAN_MR1) {
-            // Up to JB-MR1 we had a white list with services that can enable touch
+            // Up to JB-MR1 we had a allowlist with services that can enable touch
             // exploration. When a service is first started we show a dialog to the
-            // use to get a permission to white list the service.
+            // use to get a permission to allowlist the service.
             if (userState.mTouchExplorationGrantedServices.contains(service.mComponentName)) {
                 return true;
             } else if (mEnableTouchExplorationDialog == null
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index c45da86..a2d58c8 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -631,7 +631,7 @@
                     // Two pointers moving in the same direction within
                     // a given distance perform a drag.
                     mState.startDragging();
-                    adjustEventLocationForDrag(event);
+                    computeDraggingPointerIdIfNeeded(event);
                     pointerIdBits = 1 << mDraggingPointerId;
                     event.setEdgeFlags(mReceivedPointerTracker.getLastReceivedDownEdgeFlags());
                     mDispatcher.sendMotionEvent(
@@ -794,7 +794,7 @@
                     case 2:
                         if (isDraggingGesture(event)) {
                             // If still dragging send a drag event.
-                            adjustEventLocationForDrag(event);
+                            computeDraggingPointerIdIfNeeded(event);
                             mDispatcher.sendMotionEvent(
                                     event, ACTION_MOVE, rawEvent, pointerIdBits, policyFlags);
                         } else {
@@ -959,44 +959,27 @@
     }
 
     /**
-     * Adjust the location of an injected event when performing a drag. The location will be the
-     * location of the finger closest to an edge of the screen.
+     * Computes {@link #mDraggingPointerId} if it is invalid. The pointer will be the finger
+     * closet to an edge of the screen.
      */
-    private void adjustEventLocationForDrag(MotionEvent event) {
+    private void computeDraggingPointerIdIfNeeded(MotionEvent event) {
+        if (mDraggingPointerId != INVALID_POINTER_ID) {
+            // If we have a valid pointer ID, we should be good
+            final int pointerIndex = event.findPointerIndex(mDraggingPointerId);
+            if (event.findPointerIndex(pointerIndex) >= 0) {
+                return;
+            }
+        }
+        // Use the pointer that is closest to its closest edge.
         final float firstPtrX = event.getX(0);
         final float firstPtrY = event.getY(0);
         final int firstPtrId = event.getPointerId(0);
         final float secondPtrX = event.getX(1);
         final float secondPtrY = event.getY(1);
         final int secondPtrId = event.getPointerId(1);
-        float draggingX = firstPtrX;
-        float draggingY = firstPtrY;
-        if (mDraggingPointerId != INVALID_POINTER_ID) {
-            // Just use the coordinates of the dragging pointer.
-            int pointerIndex = event.findPointerIndex(mDraggingPointerId);
-            if (pointerIndex >= 0) {
-                draggingX = event.getX(pointerIndex);
-                draggingY = event.getY(pointerIndex);
-            } else {
-                // We've lost track of the dragging pointer. Try to recover by invalidating it.
-                // We'll the drop into the code below to choose a new one.
-                mDraggingPointerId = INVALID_POINTER_ID;
-            }
-        }
-        // Not quite an else, since the above code can invalidate the pointer
-        if (mDraggingPointerId == INVALID_POINTER_ID) {
-            // The goal is to use the coordinates of the finger that is closest to its closest edge.
-            if (getDistanceToClosestEdge(firstPtrX, firstPtrY)
-                    < getDistanceToClosestEdge(secondPtrX, secondPtrY)) {
-                // X and Y initialized to firstPtrX and Y was right
-                mDraggingPointerId = firstPtrId;
-            } else {
-                draggingX = secondPtrX;
-                draggingY = secondPtrY;
-                mDraggingPointerId = secondPtrId;
-            }
-        }
-        event.setLocation(draggingX, draggingY);
+        mDraggingPointerId = (getDistanceToClosestEdge(firstPtrX, firstPtrY)
+                 < getDistanceToClosestEdge(secondPtrX, secondPtrY))
+                 ? firstPtrId : secondPtrId;
     }
 
     private float getDistanceToClosestEdge(float x, float y) {
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 663fd62..ad85784 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -360,7 +360,7 @@
 
     @Override // from SystemService
     public boolean isUserSupported(TargetUser user) {
-        return user.getUserInfo().isFull() || user.getUserInfo().isManagedProfile();
+        return user.isFull() || user.isManagedProfile();
     }
 
     @Override // from SystemService
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 29235dd..e68c07ed 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -441,6 +441,7 @@
     private long mAncestralToken = 0;
     private long mCurrentToken = 0;
     @Nullable private File mAncestralSerialNumberFile;
+    @OperationType private volatile long mAncestralOperationType;
 
     private final ContentObserver mSetupObserver;
     private final BroadcastReceiver mRunInitReceiver;
@@ -881,6 +882,10 @@
         mAncestralToken = ancestralToken;
     }
 
+    public void setAncestralOperationType(@OperationType int operationType) {
+        mAncestralOperationType = operationType;
+    }
+
     public long getCurrentToken() {
         return mCurrentToken;
     }
@@ -1808,6 +1813,16 @@
         }
     }
 
+    private BackupEligibilityRules getEligibilityRulesForRestoreAtInstall(long restoreToken) {
+        if (mAncestralOperationType == OperationType.MIGRATION && restoreToken == mAncestralToken) {
+            return getEligibilityRulesForOperation(OperationType.MIGRATION);
+        } else {
+            // If we're not using the ancestral data set, it means we're restoring from a backup
+            // that happened on this device.
+            return mScheduledBackupEligibility;
+        }
+    }
+
     /**
      * Get the restore-set token for the best-available restore set for this {@code packageName}:
      * the active set if possible, else the ancestral one. Returns zero if none available.
@@ -3976,7 +3991,7 @@
                                 packageName,
                                 token,
                                 listener,
-                                mScheduledBackupEligibility);
+                                getEligibilityRulesForRestoreAtInstall(restoreSet));
                 mBackupHandler.sendMessage(msg);
             } catch (Exception e) {
                 // Calling into the transport broke; back off and proceed with the installation.
diff --git a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
index 6220679..b962539 100644
--- a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
+++ b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
@@ -27,6 +27,7 @@
 
 import android.app.ApplicationThreadConstants;
 import android.app.IBackupAgent;
+import android.app.backup.BackupManager;
 import android.app.backup.FullBackup;
 import android.app.backup.IBackupManagerMonitor;
 import android.app.backup.IFullBackupRestoreObserver;
@@ -633,7 +634,11 @@
         setRunning(false);
     }
 
-    private static boolean isRestorableFile(FileMetadata info) {
+    private boolean isRestorableFile(FileMetadata info) {
+        if (mBackupEligibilityRules.getOperationType() == BackupManager.OperationType.MIGRATION) {
+            // Everything is eligible for device-to-device migration.
+            return true;
+        }
         if (FullBackup.CACHE_TREE_TOKEN.equals(info.domain)) {
             if (MORE_DEBUG) {
                 Slog.i(TAG, "Dropping cache file path " + info.path);
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index 7baf559..abf11bd 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -1136,6 +1136,8 @@
         if (mIsSystemRestore && mPmAgent != null) {
             backupManagerService.setAncestralPackages(mPmAgent.getRestoredPackages());
             backupManagerService.setAncestralToken(mToken);
+            backupManagerService.setAncestralOperationType(
+                    mBackupEligibilityRules.getOperationType());
             backupManagerService.writeRestoreTokens();
         }
 
diff --git a/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java b/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
index d659897..73ba1f1 100644
--- a/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
+++ b/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
@@ -85,12 +85,15 @@
      *     <li>they run as a system-level uid but do not supply their own backup agent
      *     <li>it is the special shared-storage backup package used for 'adb backup'
      * </ol>
+     *
+     * However, the above eligibility rules are ignored for non-system apps in in case of
+     * device-to-device migration, see {@link OperationType}.
      */
     @VisibleForTesting
     public boolean appIsEligibleForBackup(ApplicationInfo app) {
-        // 1. their manifest states android:allowBackup="false"
-        boolean appAllowsBackup = (app.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0;
-        if (!appAllowsBackup && !forceFullBackup(app.uid, mOperationType)) {
+        // 1. their manifest states android:allowBackup="false" and this is not a device-to-device
+        // migration
+        if (!isAppBackupAllowed(app)) {
             return false;
         }
 
@@ -123,6 +126,23 @@
     }
 
     /**
+    * Check if this app allows backup. Apps can opt out of backup by stating
+    * android:allowBackup="false" in their manifest. However, this flag is ignored for non-system
+    * apps during device-to-device migrations, see {@link OperationType}.
+    *
+    * @param app The app under check.
+    * @return boolean indicating whether backup is allowed.
+    */
+    public boolean isAppBackupAllowed(ApplicationInfo app) {
+        if (mOperationType == OperationType.MIGRATION && !UserHandle.isCore(app.uid)) {
+            // Backup / restore of all apps is force allowed during device-to-device migration.
+            return true;
+        }
+
+        return (app.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0;
+    }
+
+    /**
      * Returns whether an app is eligible for backup at runtime. That is, the app has to:
      * <ol>
      *     <li>Return true for {@link #appIsEligibleForBackup(ApplicationInfo, int)}
diff --git a/services/backup/java/com/android/server/backup/utils/TarBackupReader.java b/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
index bf8e9c8..3789fa1 100644
--- a/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
+++ b/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
@@ -402,7 +402,7 @@
                     info.packageName, PackageManager.GET_SIGNING_CERTIFICATES, userId);
             // Fall through to IGNORE if the app explicitly disallows backup
             final int flags = pkgInfo.applicationInfo.flags;
-            if ((flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0) {
+            if (eligibilityRules.isAppBackupAllowed(pkgInfo.applicationInfo)) {
                 // Restore system-uid-space packages only if they have
                 // defined a custom backup agent
                 if (!UserHandle.isCore(pkgInfo.applicationInfo.uid)
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index ea94ad0..e742015 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -219,7 +219,7 @@
 
     @Override // from SystemService
     public boolean isUserSupported(TargetUser user) {
-        return user.getUserInfo().isFull() || user.getUserInfo().isManagedProfile();
+        return user.isFull() || user.isManagedProfile();
     }
 
     @Override // from SystemService
diff --git a/services/core/java/android/os/UserManagerInternal.java b/services/core/java/android/os/UserManagerInternal.java
index fbe8c04..61e8128 100644
--- a/services/core/java/android/os/UserManagerInternal.java
+++ b/services/core/java/android/os/UserManagerInternal.java
@@ -262,8 +262,7 @@
     public abstract boolean hasUserRestriction(String restriction, int userId);
 
     /**
-     * Gets an {@link UserInfo} for the given {@code userId}, or {@code null} if not
-     * found.
+     * Gets a {@link UserInfo} for the given {@code userId}, or {@code null} if not found.
      */
     public abstract @Nullable UserInfo getUserInfo(@UserIdInt int userId);
 
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index ef62a99..bd590d3 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -4966,7 +4966,7 @@
                 Slog.w(TAG, "User " + userId + " has no Vpn configuration");
                 return null;
             }
-            return vpn.getLockdownWhitelist();
+            return vpn.getLockdownAllowlist();
         }
     }
 
diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java
index 6402e07..b2f0c83 100644
--- a/services/core/java/com/android/server/IpSecService.java
+++ b/services/core/java/com/android/server/IpSecService.java
@@ -1477,7 +1477,7 @@
     }
 
     /**
-     * Checks an IpSecConfig parcel to ensure that the contents are sane and throws an
+     * Checks an IpSecConfig parcel to ensure that the contents are valid and throws an
      * IllegalArgumentException if they are not.
      */
     private void checkIpSecConfig(IpSecConfig config) {
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 97f3b37..ee794ba 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -20,14 +20,14 @@
 import static android.Manifest.permission.NETWORK_SETTINGS;
 import static android.Manifest.permission.OBSERVE_NETWORK_POLICY;
 import static android.Manifest.permission.SHUTDOWN;
-import static android.net.INetd.FIREWALL_BLACKLIST;
+import static android.net.INetd.FIREWALL_ALLOWLIST;
 import static android.net.INetd.FIREWALL_CHAIN_DOZABLE;
 import static android.net.INetd.FIREWALL_CHAIN_NONE;
 import static android.net.INetd.FIREWALL_CHAIN_POWERSAVE;
 import static android.net.INetd.FIREWALL_CHAIN_STANDBY;
+import static android.net.INetd.FIREWALL_DENYLIST;
 import static android.net.INetd.FIREWALL_RULE_ALLOW;
 import static android.net.INetd.FIREWALL_RULE_DENY;
-import static android.net.INetd.FIREWALL_WHITELIST;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_STANDBY;
@@ -1575,7 +1575,7 @@
         enforceSystemUid();
         try {
             mNetdService.firewallSetFirewallType(
-                    enabled ? INetd.FIREWALL_WHITELIST : INetd.FIREWALL_BLACKLIST);
+                    enabled ? INetd.FIREWALL_ALLOWLIST : INetd.FIREWALL_DENYLIST);
             mFirewallEnabled = enabled;
         } catch (RemoteException | ServiceSpecificException e) {
             throw new IllegalStateException(e);
@@ -1608,7 +1608,7 @@
 
         int numUids = 0;
         if (DBG) Slog.d(TAG, "Closing sockets after enabling chain " + chainName);
-        if (getFirewallType(chain) == FIREWALL_WHITELIST) {
+        if (getFirewallType(chain) == FIREWALL_ALLOWLIST) {
             // Close all sockets on all non-system UIDs...
             ranges = new UidRangeParcel[] {
                 // TODO: is there a better way of finding all existing users? If so, we could
@@ -1714,13 +1714,13 @@
     private int getFirewallType(int chain) {
         switch (chain) {
             case FIREWALL_CHAIN_STANDBY:
-                return FIREWALL_BLACKLIST;
+                return FIREWALL_DENYLIST;
             case FIREWALL_CHAIN_DOZABLE:
-                return FIREWALL_WHITELIST;
+                return FIREWALL_ALLOWLIST;
             case FIREWALL_CHAIN_POWERSAVE:
-                return FIREWALL_WHITELIST;
+                return FIREWALL_ALLOWLIST;
             default:
-                return isFirewallEnabled() ? FIREWALL_WHITELIST : FIREWALL_BLACKLIST;
+                return isFirewallEnabled() ? FIREWALL_ALLOWLIST : FIREWALL_DENYLIST;
         }
     }
 
@@ -1822,7 +1822,7 @@
 
     private @NonNull String getFirewallRuleName(int chain, int rule) {
         String ruleName;
-        if (getFirewallType(chain) == FIREWALL_WHITELIST) {
+        if (getFirewallType(chain) == FIREWALL_ALLOWLIST) {
             if (rule == FIREWALL_RULE_ALLOW) {
                 ruleName = "allow";
             } else {
@@ -1856,7 +1856,7 @@
 
     private int getFirewallRuleType(int chain, int rule) {
         if (rule == NetworkPolicyManager.FIREWALL_RULE_DEFAULT) {
-            return getFirewallType(chain) == FIREWALL_WHITELIST
+            return getFirewallType(chain) == FIREWALL_ALLOWLIST
                     ? INetd.FIREWALL_RULE_DENY : INetd.FIREWALL_RULE_ALLOW;
         }
         return rule;
diff --git a/services/core/java/com/android/server/ServiceWatcher.java b/services/core/java/com/android/server/ServiceWatcher.java
index 0038dc2..b78b5d9 100644
--- a/services/core/java/com/android/server/ServiceWatcher.java
+++ b/services/core/java/com/android/server/ServiceWatcher.java
@@ -238,24 +238,13 @@
 
         new PackageMonitor() {
             @Override
-            public void onPackageUpdateFinished(String packageName, int uid) {
-                ServiceWatcher.this.onPackageChanged(packageName);
-            }
-
-            @Override
-            public void onPackageAdded(String packageName, int uid) {
-                ServiceWatcher.this.onPackageChanged(packageName);
-            }
-
-            @Override
-            public void onPackageRemoved(String packageName, int uid) {
-                ServiceWatcher.this.onPackageChanged(packageName);
-            }
-
-            @Override
             public boolean onPackageChanged(String packageName, int uid, String[] components) {
-                ServiceWatcher.this.onPackageChanged(packageName);
-                return super.onPackageChanged(packageName, uid, components);
+                return true;
+            }
+
+            @Override
+            public void onSomePackagesChanged() {
+                onBestServiceChanged(false);
             }
         }.register(mContext, UserHandle.ALL, true, mHandler);
 
@@ -320,7 +309,7 @@
 
         if (!mTargetService.equals(ServiceInfo.NONE)) {
             if (D) {
-                Log.i(TAG, "[" + mIntent.getAction() + "] unbinding from " + mTargetService);
+                Log.d(TAG, "[" + mIntent.getAction() + "] unbinding from " + mTargetService);
             }
 
             mContext.unbindService(this);
@@ -335,9 +324,7 @@
 
         Preconditions.checkState(mTargetService.component != null);
 
-        if (D) {
-            Log.i(TAG, getLogPrefix() + " binding to " + mTargetService);
-        }
+        Log.i(TAG, getLogPrefix() + " binding to " + mTargetService);
 
         Intent bindIntent = new Intent(mIntent).setComponent(mTargetService.component);
         if (!mContext.bindServiceAsUser(bindIntent, this,
@@ -355,7 +342,7 @@
         Preconditions.checkState(mBinder == null);
 
         if (D) {
-            Log.i(TAG, getLogPrefix() + " connected to " + component.toShortString());
+            Log.d(TAG, getLogPrefix() + " connected to " + component.toShortString());
         }
 
         mBinder = binder;
@@ -379,7 +366,7 @@
         }
 
         if (D) {
-            Log.i(TAG, getLogPrefix() + " disconnected from " + component.toShortString());
+            Log.d(TAG, getLogPrefix() + " disconnected from " + component.toShortString());
         }
 
         mBinder = null;
@@ -392,13 +379,16 @@
     public final void onBindingDied(ComponentName component) {
         Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
 
-        if (D) {
-            Log.i(TAG, getLogPrefix() + " " + component.toShortString() + " died");
-        }
+        Log.i(TAG, getLogPrefix() + " " + component.toShortString() + " died");
 
         onBestServiceChanged(true);
     }
 
+    @Override
+    public final void onNullBinding(ComponentName component) {
+        Log.e(TAG, getLogPrefix() + " " + component.toShortString() + " has null binding");
+    }
+
     void onUserSwitched(@UserIdInt int userId) {
         mCurrentUserId = userId;
         onBestServiceChanged(false);
@@ -410,11 +400,6 @@
         }
     }
 
-    void onPackageChanged(String packageName) {
-        // force a rebind if the changed package was the currently connected package
-        onBestServiceChanged(packageName.equals(mTargetService.getPackageName()));
-    }
-
     /**
      * Runs the given function asynchronously if and only if currently connected. Suppresses any
      * RemoteException thrown during execution.
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index eca6036..b72985c 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -1141,13 +1141,6 @@
     }
 
     private void completeUnlockUser(int userId) {
-        // If user 0 has completed unlock, perform a one-time migration of legacy obb data
-        // to its new location. This may take time depending on the size of the data to be copied
-        // so it's done on the StorageManager handler thread.
-        if (userId == 0) {
-            mPmInternal.migrateLegacyObbData();
-        }
-
         onKeyguardStateChanged(false);
 
         // Record user as started so newly mounted volumes kick off events
@@ -1540,9 +1533,21 @@
                 mFuseMountedUser.remove(vol.getMountUserId());
             } else if (mVoldAppDataIsolationEnabled){
                 final int userId = vol.getMountUserId();
-                mFuseMountedUser.add(userId);
                 // Async remount app storage so it won't block the main thread.
                 new Thread(() -> {
+
+                    // If user 0 has completed unlock, perform a one-time migration of legacy
+                    // obb data to its new location. This may take time depending on the size of
+                    // the data to be copied so it's done on the StorageManager worker thread.
+                    // This needs to be finished before start mounting obb directories.
+                    if (userId == 0) {
+                        mPmInternal.migrateLegacyObbData();
+                    }
+
+                    // Add fuse mounted user after migration to prevent ProcessList tries to
+                    // create obb directory before migration is done.
+                    mFuseMountedUser.add(userId);
+
                     Map<Integer, String> pidPkgMap = null;
                     // getProcessesWithPendingBindMounts() could fail when a new app process is
                     // starting and it's not planning to mount storage dirs in zygote, but it's
@@ -3282,7 +3287,7 @@
         final UserManagerInternal umInternal =
                 LocalServices.getService(UserManagerInternal.class);
 
-        for (UserInfo user : um.getUsers(false /* includeDying */)) {
+        for (UserInfo user : um.getUsers()) {
             final int flags;
             if (umInternal.isUserUnlockingOrUnlocked(user.id)) {
                 flags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE;
diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java
index 1496e92..84d01ec 100644
--- a/services/core/java/com/android/server/SystemService.java
+++ b/services/core/java/com/android/server/SystemService.java
@@ -23,6 +23,7 @@
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.SystemApi.Client;
+import android.annotation.UserIdInt;
 import android.app.ActivityThread;
 import android.content.Context;
 import android.content.pm.UserInfo;
@@ -131,45 +132,89 @@
      */
     @SystemApi(client = Client.SYSTEM_SERVER)
     public static final class TargetUser {
-        @NonNull
-        private final UserInfo mUserInfo;
+
+        // NOTE: attributes below must be immutable while ther user is running (i.e., from the
+        // moment it's started until after it's shutdown).
+        private final @UserIdInt int mUserId;
+        private final boolean mFull;
+        private final boolean mManagedProfile;
+        private final boolean mPreCreated;
 
         /** @hide */
         public TargetUser(@NonNull UserInfo userInfo) {
-            mUserInfo = userInfo;
+            mUserId = userInfo.id;
+            mFull = userInfo.isFull();
+            mManagedProfile = userInfo.isManagedProfile();
+            mPreCreated = userInfo.preCreated;
         }
 
         /**
-         * @return The information about the user. <b>NOTE: </b> this is a "live" object
-         * referenced by {@link UserManagerService} and hence should not be modified.
+         * Checks if the target user is {@link UserInfo#isFull() full}.
          *
          * @hide
          */
-        @NonNull
-        public UserInfo getUserInfo() {
-            return mUserInfo;
+        public boolean isFull() {
+            return mFull;
         }
 
         /**
-         * @return the target {@link UserHandle}.
+         * Checks if the target user is a managed profile.
+         *
+         * @hide
+         */
+        public boolean isManagedProfile() {
+            return mManagedProfile;
+        }
+
+        /**
+         * Checks if the target user is a pre-created user.
+         *
+         * @hide
+         */
+        public boolean isPreCreated() {
+            return mPreCreated;
+        }
+
+        /**
+         * Gets the target user's {@link UserHandle}.
          */
         @NonNull
         public UserHandle getUserHandle() {
-            return mUserInfo.getUserHandle();
+            return UserHandle.of(mUserId);
         }
 
         /**
-         * @return the integer user id
+         * Gets the target user's id.
          *
          * @hide
          */
-        public int getUserIdentifier() {
-            return mUserInfo.id;
+        public @UserIdInt int getUserIdentifier() {
+            return mUserId;
         }
 
         @Override
         public String toString() {
-            return Integer.toString(getUserIdentifier());
+            return Integer.toString(mUserId);
+        }
+
+        /**
+         * @hide
+         */
+        public void dump(@NonNull StringBuilder builder) {
+            builder.append(getUserIdentifier());
+
+            if (!isFull() && !isManagedProfile()) return;
+
+            builder.append('(');
+            boolean addComma = false;
+            if (isFull()) {
+                builder.append("full");
+            }
+            if (isManagedProfile()) {
+                if (addComma) builder.append(',');
+                builder.append("mp");
+            }
+            builder.append(')');
         }
     }
 
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index 74bb7d7..34e6370 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -27,7 +27,10 @@
 import android.os.UserManagerInternal;
 import android.util.ArrayMap;
 import android.util.Slog;
+import android.util.SparseArray;
 
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
 import com.android.server.SystemService.TargetUser;
 import com.android.server.utils.TimingsTraceAndSlog;
 
@@ -44,8 +47,8 @@
  *
  * {@hide}
  */
-public class SystemServiceManager {
-    private static final String TAG = "SystemServiceManager";
+public final class SystemServiceManager {
+    private static final String TAG = SystemServiceManager.class.getSimpleName();
     private static final boolean DEBUG = false;
     private static final int SERVICE_CALL_WARN_TIME_MS = 50;
 
@@ -74,6 +77,13 @@
 
     private UserManagerInternal mUserManagerInternal;
 
+    /**
+     * Map of started {@link TargetUser TargetUsers} by user id; users are added on start and
+     * removed after they're completely shut down.
+     */
+    @GuardedBy("mTargetUsers")
+    private final SparseArray<TargetUser> mTargetUsers = new SparseArray<>();
+
     SystemServiceManager(Context context) {
         mContext = context;
     }
@@ -240,75 +250,85 @@
         mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
     }
 
-    private @NonNull UserInfo getUserInfo(@UserIdInt int userHandle) {
-        if (mUserManagerInternal == null) {
-            throw new IllegalStateException("mUserManagerInternal not set yet");
+    private @NonNull TargetUser getTargetUser(@UserIdInt int userId) {
+        final TargetUser targetUser;
+        synchronized (mTargetUsers) {
+            targetUser = mTargetUsers.get(userId);
         }
-        final UserInfo userInfo = mUserManagerInternal.getUserInfo(userHandle);
-        if (userInfo == null) {
-            throw new IllegalStateException("No UserInfo for " + userHandle);
-        }
-        return userInfo;
+        Preconditions.checkState(targetUser != null, "No TargetUser for " + userId);
+        return targetUser;
     }
 
     /**
      * Starts the given user.
      */
-    public void startUser(final @NonNull TimingsTraceAndSlog t, final @UserIdInt int userHandle) {
-        onUser(t, START, userHandle);
+    public void startUser(@NonNull TimingsTraceAndSlog t, @UserIdInt int userId) {
+        // Create cached TargetUser
+        final UserInfo userInfo = mUserManagerInternal.getUserInfo(userId);
+        Preconditions.checkState(userInfo != null, "No UserInfo for " + userId);
+        synchronized (mTargetUsers) {
+            mTargetUsers.put(userId, new TargetUser(userInfo));
+        }
+
+        onUser(t, START, userId);
     }
 
     /**
      * Unlocks the given user.
      */
-    public void unlockUser(final @UserIdInt int userHandle) {
-        onUser(UNLOCKING, userHandle);
+    public void unlockUser(@UserIdInt int userId) {
+        onUser(UNLOCKING, userId);
     }
 
     /**
      * Called after the user was unlocked.
      */
-    public void onUserUnlocked(final @UserIdInt int userHandle) {
-        onUser(UNLOCKED, userHandle);
+    public void onUserUnlocked(@UserIdInt int userId) {
+        onUser(UNLOCKED, userId);
     }
 
     /**
      * Switches to the given user.
      */
-    public void switchUser(final @UserIdInt int from, final @UserIdInt int to) {
+    public void switchUser(@UserIdInt int from, @UserIdInt int to) {
         onUser(TimingsTraceAndSlog.newAsyncLog(), SWITCH, to, from);
     }
 
     /**
      * Stops the given user.
      */
-    public void stopUser(final @UserIdInt int userHandle) {
-        onUser(STOP, userHandle);
+    public void stopUser(@UserIdInt int userId) {
+        onUser(STOP, userId);
     }
 
     /**
      * Cleans up the given user.
      */
-    public void cleanupUser(final @UserIdInt int userHandle) {
-        onUser(CLEANUP, userHandle);
+    public void cleanupUser(@UserIdInt int userId) {
+        onUser(CLEANUP, userId);
+
+        // Remove cached TargetUser
+        synchronized (mTargetUsers) {
+            mTargetUsers.remove(userId);
+        }
     }
 
-    private void onUser(@NonNull String onWhat, @UserIdInt int userHandle) {
-        onUser(TimingsTraceAndSlog.newAsyncLog(), onWhat, userHandle);
+    private void onUser(@NonNull String onWhat, @UserIdInt int userId) {
+        onUser(TimingsTraceAndSlog.newAsyncLog(), onWhat, userId);
     }
 
     private void onUser(@NonNull TimingsTraceAndSlog t, @NonNull String onWhat,
-            @UserIdInt int userHandle) {
-        onUser(t, onWhat, userHandle, UserHandle.USER_NULL);
+            @UserIdInt int userId) {
+        onUser(t, onWhat, userId, UserHandle.USER_NULL);
     }
 
     private void onUser(@NonNull TimingsTraceAndSlog t, @NonNull String onWhat,
             @UserIdInt int curUserId, @UserIdInt int prevUserId) {
         t.traceBegin("ssm." + onWhat + "User-" + curUserId);
         Slog.i(TAG, "Calling on" + onWhat + "User " + curUserId);
-        final TargetUser curUser = new TargetUser(getUserInfo(curUserId));
+        final TargetUser curUser = getTargetUser(curUserId);
         final TargetUser prevUser = prevUserId == UserHandle.USER_NULL ? null
-                : new TargetUser(getUserInfo(prevUserId));
+                : getTargetUser(prevUserId);
         final int serviceLen = mServices.size();
         for (int i = 0; i < serviceLen; i++) {
             final SystemService service = mServices.get(i);
@@ -437,7 +457,7 @@
      */
     public void dump() {
         StringBuilder builder = new StringBuilder();
-        builder.append("Current phase: ").append(mCurrentPhase).append("\n");
+        builder.append("Current phase: ").append(mCurrentPhase).append('\n');
         builder.append("Services:\n");
         final int startedLen = mServices.size();
         for (int i = 0; i < startedLen; i++) {
@@ -447,6 +467,14 @@
                     .append("\n");
         }
 
+        builder.append("Target users: ");
+        final int targetUsersSize = mTargetUsers.size();
+        for (int i = 0; i < targetUsersSize; i++) {
+            mTargetUsers.valueAt(i).dump(builder);
+            if (i != targetUsersSize - 1) builder.append(',');
+        }
+        builder.append('\n');
+
         Slog.e(TAG, builder.toString());
     }
 }
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 32d02fb..96d973e 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -127,6 +127,7 @@
     private final int mPreviousVibrationsLimit;
     private final boolean mAllowPriorityVibrationsInLowPowerMode;
     private final List<Integer> mSupportedEffects;
+    private final List<Integer> mSupportedPrimitives;
     private final long mCapabilities;
     private final int mDefaultVibrationAmplitude;
     private final SparseArray<VibrationEffect> mFallbackEffects;
@@ -184,6 +185,8 @@
 
     static native int[] vibratorGetSupportedEffects(long controllerPtr);
 
+    static native int[] vibratorGetSupportedPrimitives(long controllerPtr);
+
     static native long vibratorPerformEffect(
             long controllerPtr, long effect, long strength, Vibration vibration);
 
@@ -397,6 +400,7 @@
         mNativeWrapper.vibratorOff();
 
         mSupportedEffects = asList(mNativeWrapper.vibratorGetSupportedEffects());
+        mSupportedPrimitives = asList(mNativeWrapper.vibratorGetSupportedPrimitives());
         mCapabilities = mNativeWrapper.vibratorGetCapabilities();
 
         mContext = context;
@@ -647,8 +651,11 @@
     @Override // Binder call
     public boolean[] arePrimitivesSupported(int[] primitiveIds) {
         boolean[] supported = new boolean[primitiveIds.length];
-        if (hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) {
-            Arrays.fill(supported, true);
+        if (!hasCapability(IVibrator.CAP_COMPOSE_EFFECTS) || mSupportedPrimitives == null) {
+            return supported;
+        }
+        for (int i = 0; i < primitiveIds.length; i++) {
+            supported[i] = mSupportedPrimitives.contains(primitiveIds[i]);
         }
         return supported;
     }
@@ -1501,6 +1508,7 @@
             pw.println("  mNotificationIntensity=" + mNotificationIntensity);
             pw.println("  mRingIntensity=" + mRingIntensity);
             pw.println("  mSupportedEffects=" + mSupportedEffects);
+            pw.println("  mSupportedPrimitives=" + mSupportedPrimitives);
             pw.println();
             pw.println("  Previous ring vibrations:");
             for (VibrationInfo info : mPreviousRingVibrations) {
@@ -1759,6 +1767,11 @@
             return VibratorService.vibratorGetSupportedEffects(mNativeControllerPtr);
         }
 
+        /** Returns all compose primitives supported by the device vibrator. */
+        public int[] vibratorGetSupportedPrimitives() {
+            return VibratorService.vibratorGetSupportedPrimitives(mNativeControllerPtr);
+        }
+
         /** Turns vibrator on to perform one of the supported effects. */
         public long vibratorPerformEffect(long effect, long strength, Vibration vibration) {
             return VibratorService.vibratorPerformEffect(
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index b83aa4f..6328cb6 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -493,6 +493,8 @@
         }
 
         ServiceRecord r = res.record;
+        setFgsRestrictionLocked(callingPackage, callingPid, callingUid, service, r,
+                allowBackgroundActivityStarts);
 
         if (!mAm.mUserController.exists(r.userId)) {
             Slog.w(TAG, "Trying to start service with non-existent user! " + r.userId);
@@ -516,6 +518,21 @@
             forcedStandby = true;
         }
 
+        if (fgRequired) {
+            if (!r.mAllowStartForeground) {
+                if (!r.mLoggedInfoAllowStartForeground) {
+                    Slog.wtf(TAG, "Background started FGS " + r.mInfoAllowStartForeground);
+                    r.mLoggedInfoAllowStartForeground = true;
+                }
+                if (mAm.mConstants.mFlagFgsStartRestrictionEnabled) {
+                    Slog.w(TAG, "startForegroundService() not allowed due to "
+                                    + " mAllowStartForeground false: service "
+                                    + r.shortInstanceName);
+                    forcedStandby = true;
+                }
+            }
+        }
+
         // If this is a direct-to-foreground start, make sure it is allowed as per the app op.
         boolean forceSilentAbort = false;
         if (fgRequired) {
@@ -688,14 +705,10 @@
                         "Not potential delay (user " + r.userId + " not started): " + r);
             }
         }
-
         if (allowBackgroundActivityStarts) {
             r.allowBgActivityStartsOnServiceStart();
         }
         ComponentName cmp = startServiceInnerLocked(smap, service, r, callerFg, addToStarting);
-
-        setFgsRestrictionLocked(callingPackage, callingPid,
-                callingUid, service, r, allowBackgroundActivityStarts);
         return cmp;
     }
 
@@ -1422,8 +1435,8 @@
                         if (mAm.mConstants.mFlagFgsStartRestrictionEnabled) {
                             Slog.w(TAG,
                                     "Service.startForeground() not allowed due to "
-                                    + " mAllowStartForeground false: service "
-                                    + r.shortInstanceName);
+                                            + "mAllowStartForeground false: service "
+                                            + r.shortInstanceName);
                             updateServiceForegroundLocked(r.app, true);
                             ignoreForeground = true;
                         }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index f250647..3e600b7 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -98,7 +98,6 @@
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESS_OBSERVERS;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PSS;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SERVICE;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_UID_OBSERVERS;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_WHITELISTS;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BACKUP;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST;
@@ -146,7 +145,6 @@
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityManager.StackInfo;
 import android.app.ActivityManagerInternal;
-import android.app.ActivityManagerProto;
 import android.app.ActivityThread;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
@@ -176,7 +174,6 @@
 import android.app.ProcessMemoryState;
 import android.app.ProfilerInfo;
 import android.app.WaitResult;
-import android.app.backup.BackupManager;
 import android.app.backup.BackupManager.OperationType;
 import android.app.backup.IBackupManager;
 import android.app.usage.UsageEvents;
@@ -349,7 +346,6 @@
 import com.android.server.ThreadPriorityBooster;
 import com.android.server.UserspaceRebootLogger;
 import com.android.server.Watchdog;
-import com.android.server.am.ActivityManagerServiceDumpProcessesProto.UidObserverRegistrationProto;
 import com.android.server.appop.AppOpsService;
 import com.android.server.compat.PlatformCompat;
 import com.android.server.contentcapture.ContentCaptureManagerInternal;
@@ -490,9 +486,6 @@
     // as one line, but close enough for now.
     static final int RESERVED_BYTES_PER_LOGCAT_LINE = 100;
 
-    /** If a UID observer takes more than this long, send a WTF. */
-    private static final int SLOW_UID_OBSERVER_THRESHOLD_MS = 20;
-
     // Necessary ApplicationInfo flags to mark an app as persistent
     static final int PERSISTENT_MASK =
             ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PERSISTENT;
@@ -644,9 +637,6 @@
     @VisibleForTesting
     long mWaitForNetworkTimeoutMs;
 
-    /** Total # of UID change events dispatched, shown in dumpsys. */
-    int mUidChangeDispatchCount;
-
     /**
      * Uids of apps with current active camera sessions.  Access synchronized on
      * the IntArray instance itself, and no other locks must be acquired while that
@@ -1014,12 +1004,6 @@
             };
 
     /**
-     * This is for verifying the UID report flow.
-     */
-    static final boolean VALIDATE_UID_STATES = true;
-    final ActiveUids mValidateUids = new ActiveUids(this, false /* postChangesToAtm */);
-
-    /**
      * Fingerprints (hashCode()) of stack traces that we've
      * already logged DropBox entries for.  Guarded by itself.  If
      * something (rogue user app) forces this over
@@ -1399,69 +1383,6 @@
         int foregroundServiceTypes;
     }
 
-    static final class UidObserverRegistration {
-        final int uid;
-        final String pkg;
-        final int which;
-        final int cutpoint;
-
-        /**
-         * Total # of callback calls that took more than {@link #SLOW_UID_OBSERVER_THRESHOLD_MS}.
-         * We show it in dumpsys.
-         */
-        int mSlowDispatchCount;
-
-        /** Max time it took for each dispatch. */
-        int mMaxDispatchTime;
-
-        final SparseIntArray lastProcStates;
-
-        // Please keep the enum lists in sync
-        private static int[] ORIG_ENUMS = new int[]{
-                ActivityManager.UID_OBSERVER_IDLE,
-                ActivityManager.UID_OBSERVER_ACTIVE,
-                ActivityManager.UID_OBSERVER_GONE,
-                ActivityManager.UID_OBSERVER_PROCSTATE,
-        };
-        private static int[] PROTO_ENUMS = new int[]{
-                ActivityManagerProto.UID_OBSERVER_FLAG_IDLE,
-                ActivityManagerProto.UID_OBSERVER_FLAG_ACTIVE,
-                ActivityManagerProto.UID_OBSERVER_FLAG_GONE,
-                ActivityManagerProto.UID_OBSERVER_FLAG_PROCSTATE,
-        };
-
-        UidObserverRegistration(int _uid, String _pkg, int _which, int _cutpoint) {
-            uid = _uid;
-            pkg = _pkg;
-            which = _which;
-            cutpoint = _cutpoint;
-            if (cutpoint >= ActivityManager.MIN_PROCESS_STATE) {
-                lastProcStates = new SparseIntArray();
-            } else {
-                lastProcStates = null;
-            }
-        }
-
-        void dumpDebug(ProtoOutputStream proto, long fieldId) {
-            final long token = proto.start(fieldId);
-            proto.write(UidObserverRegistrationProto.UID, uid);
-            proto.write(UidObserverRegistrationProto.PACKAGE, pkg);
-            ProtoUtils.writeBitWiseFlagsToProtoEnum(proto, UidObserverRegistrationProto.FLAGS,
-                    which, ORIG_ENUMS, PROTO_ENUMS);
-            proto.write(UidObserverRegistrationProto.CUT_POINT, cutpoint);
-            if (lastProcStates != null) {
-                final int NI = lastProcStates.size();
-                for (int i=0; i<NI; i++) {
-                    final long pToken = proto.start(UidObserverRegistrationProto.LAST_PROC_STATES);
-                    proto.write(UidObserverRegistrationProto.ProcState.UID, lastProcStates.keyAt(i));
-                    proto.write(UidObserverRegistrationProto.ProcState.STATE, lastProcStates.valueAt(i));
-                    proto.end(pToken);
-                }
-            }
-            proto.end(token);
-        }
-    }
-
     // TODO: Move below 4 members and code to ProcessList
     final RemoteCallbackList<IProcessObserver> mProcessObservers = new RemoteCallbackList<>();
     ProcessChangeItem[] mActiveProcessChanges = new ProcessChangeItem[5];
@@ -1469,12 +1390,6 @@
     final ArrayList<ProcessChangeItem> mPendingProcessChanges = new ArrayList<>();
     final ArrayList<ProcessChangeItem> mAvailProcessChanges = new ArrayList<>();
 
-    final RemoteCallbackList<IUidObserver> mUidObservers = new RemoteCallbackList<>();
-    UidRecord.ChangeItem[] mActiveUidChanges = new UidRecord.ChangeItem[5];
-
-    final ArrayList<UidRecord.ChangeItem> mPendingUidChanges = new ArrayList<>();
-    final ArrayList<UidRecord.ChangeItem> mAvailUidChanges = new ArrayList<>();
-
     OomAdjObserver mCurOomAdjObserver;
     int mCurOomAdjUid;
 
@@ -1525,6 +1440,8 @@
     public final ActivityManagerInternal mInternal;
     final ActivityThread mSystemThread;
 
+    final UidObserverController mUidObserverController;
+
     private final class AppDeathRecipient implements IBinder.DeathRecipient {
         final ProcessRecord mApp;
         final int mPid;
@@ -1570,7 +1487,6 @@
     static final int NOTIFY_CLEARTEXT_NETWORK_MSG = 49;
     static final int POST_DUMP_HEAP_NOTIFICATION_MSG = 50;
     static final int ABORT_DUMPHEAP_MSG = 51;
-    static final int DISPATCH_UIDS_CHANGED_UI_MSG = 53;
     static final int SHUTDOWN_UI_AUTOMATION_CONNECTION_MSG = 56;
     static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG = 57;
     static final int IDLE_UIDS_MSG = 58;
@@ -1699,17 +1615,14 @@
                     break;
                 }
                 case DISPATCH_PROCESS_DIED_UI_MSG: {
+                    if (false) { // DO NOT SUBMIT WITH TRUE
+                        maybeTriggerWatchdog();
+                    }
                     final int pid = msg.arg1;
                     final int uid = msg.arg2;
                     dispatchProcessDied(pid, uid);
                     break;
                 }
-                case DISPATCH_UIDS_CHANGED_UI_MSG: {
-                    if (false) { // DO NOT SUBMIT WITH TRUE
-                        maybeTriggerWatchdog();
-                    }
-                    dispatchUidsChanged();
-                } break;
                 case DISPATCH_OOM_ADJ_OBSERVER_MSG: {
                     dispatchOomAdjObserver((String) msg.obj);
                 } break;
@@ -1795,12 +1708,10 @@
                     }
                 } break;
             case CHECK_EXCESSIVE_POWER_USE_MSG: {
-                synchronized (ActivityManagerService.this) {
-                    checkExcessivePowerUsageLocked();
-                    removeMessages(CHECK_EXCESSIVE_POWER_USE_MSG);
-                    Message nmsg = obtainMessage(CHECK_EXCESSIVE_POWER_USE_MSG);
-                    sendMessageDelayed(nmsg, mConstants.POWER_CHECK_INTERVAL);
-                }
+                checkExcessivePowerUsage();
+                removeMessages(CHECK_EXCESSIVE_POWER_USE_MSG);
+                Message nmsg = obtainMessage(CHECK_EXCESSIVE_POWER_USE_MSG);
+                sendMessageDelayed(nmsg, mConstants.POWER_CHECK_INTERVAL);
             } break;
             case REPORT_MEM_USAGE_MSG: {
                 final ArrayList<ProcessMemInfo> memInfos = (ArrayList<ProcessMemInfo>)msg.obj;
@@ -2511,6 +2422,7 @@
         mConstants = hasHandlerThread
                 ? new ActivityManagerConstants(mContext, this, mHandler) : null;
         final ActiveUids activeUids = new ActiveUids(this, false /* postChangesToAtm */);
+        mUidObserverController = new UidObserverController(this);
         mPlatformCompat = null;
         mProcessList = injector.getProcessList(this);
         mProcessList.init(this, activeUids, mPlatformCompat);
@@ -2606,6 +2518,7 @@
         mCpHelper = new ContentProviderHelper(this, true);
         mPackageWatchdog = PackageWatchdog.getInstance(mUiContext);
         mAppErrors = new AppErrors(mUiContext, this, mPackageWatchdog);
+        mUidObserverController = new UidObserverController(this);
 
         final File systemDir = SystemServiceManager.ensureSystemDir();
 
@@ -3422,153 +3335,6 @@
         mProcessObservers.finishBroadcast();
     }
 
-    @VisibleForTesting
-    void dispatchUidsChanged() {
-        int N;
-        synchronized (this) {
-            N = mPendingUidChanges.size();
-            if (mActiveUidChanges.length < N) {
-                mActiveUidChanges = new UidRecord.ChangeItem[N];
-            }
-            for (int i=0; i<N; i++) {
-                final UidRecord.ChangeItem change = mPendingUidChanges.get(i);
-                mActiveUidChanges[i] = change;
-                if (change.uidRecord != null) {
-                    change.uidRecord.pendingChange = null;
-                    change.uidRecord = null;
-                }
-            }
-            mPendingUidChanges.clear();
-            if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
-                    "*** Delivering " + N + " uid changes");
-        }
-
-        mUidChangeDispatchCount += N;
-        int i = mUidObservers.beginBroadcast();
-        while (i > 0) {
-            i--;
-            dispatchUidsChangedForObserver(mUidObservers.getBroadcastItem(i),
-                    (UidObserverRegistration) mUidObservers.getBroadcastCookie(i), N);
-        }
-        mUidObservers.finishBroadcast();
-
-        if (VALIDATE_UID_STATES && mUidObservers.getRegisteredCallbackCount() > 0) {
-            for (int j = 0; j < N; ++j) {
-                final UidRecord.ChangeItem item = mActiveUidChanges[j];
-                if ((item.change & UidRecord.CHANGE_GONE) != 0) {
-                    mValidateUids.remove(item.uid);
-                } else {
-                    UidRecord validateUid = mValidateUids.get(item.uid);
-                    if (validateUid == null) {
-                        validateUid = new UidRecord(item.uid);
-                        mValidateUids.put(item.uid, validateUid);
-                    }
-                    if ((item.change & UidRecord.CHANGE_IDLE) != 0) {
-                        validateUid.idle = true;
-                    } else if ((item.change & UidRecord.CHANGE_ACTIVE) != 0) {
-                        validateUid.idle = false;
-                    }
-                    validateUid.setCurProcState(validateUid.setProcState = item.processState);
-                    validateUid.curCapability = validateUid.setCapability = item.capability;
-                    validateUid.lastDispatchedProcStateSeq = item.procStateSeq;
-                }
-            }
-        }
-
-        synchronized (this) {
-            for (int j = 0; j < N; j++) {
-                mAvailUidChanges.add(mActiveUidChanges[j]);
-            }
-        }
-    }
-
-    private void dispatchUidsChangedForObserver(IUidObserver observer,
-            UidObserverRegistration reg, int changesSize) {
-        if (observer == null) {
-            return;
-        }
-        try {
-            for (int j = 0; j < changesSize; j++) {
-                UidRecord.ChangeItem item = mActiveUidChanges[j];
-                final int change = item.change;
-                if (change == UidRecord.CHANGE_PROCSTATE &&
-                        (reg.which & ActivityManager.UID_OBSERVER_PROCSTATE) == 0) {
-                    // No-op common case: no significant change, the observer is not
-                    // interested in all proc state changes.
-                    continue;
-                }
-                final long start = SystemClock.uptimeMillis();
-                if ((change & UidRecord.CHANGE_IDLE) != 0) {
-                    if ((reg.which & ActivityManager.UID_OBSERVER_IDLE) != 0) {
-                        if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
-                                "UID idle uid=" + item.uid);
-                        observer.onUidIdle(item.uid, item.ephemeral);
-                    }
-                } else if ((change & UidRecord.CHANGE_ACTIVE) != 0) {
-                    if ((reg.which & ActivityManager.UID_OBSERVER_ACTIVE) != 0) {
-                        if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
-                                "UID active uid=" + item.uid);
-                        observer.onUidActive(item.uid);
-                    }
-                }
-                if ((reg.which & ActivityManager.UID_OBSERVER_CACHED) != 0) {
-                    if ((change & UidRecord.CHANGE_CACHED) != 0) {
-                        if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
-                                "UID cached uid=" + item.uid);
-                        observer.onUidCachedChanged(item.uid, true);
-                    } else if ((change & UidRecord.CHANGE_UNCACHED) != 0) {
-                        if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
-                                "UID active uid=" + item.uid);
-                        observer.onUidCachedChanged(item.uid, false);
-                    }
-                }
-                if ((change & UidRecord.CHANGE_GONE) != 0) {
-                    if ((reg.which & ActivityManager.UID_OBSERVER_GONE) != 0) {
-                        if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
-                                "UID gone uid=" + item.uid);
-                        observer.onUidGone(item.uid, item.ephemeral);
-                    }
-                    if (reg.lastProcStates != null) {
-                        reg.lastProcStates.delete(item.uid);
-                    }
-                } else {
-                    if ((reg.which & ActivityManager.UID_OBSERVER_PROCSTATE) != 0) {
-                        if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
-                                "UID CHANGED uid=" + item.uid
-                                        + ": " + item.processState + ": " + item.capability);
-                        boolean doReport = true;
-                        if (reg.cutpoint >= ActivityManager.MIN_PROCESS_STATE) {
-                            final int lastState = reg.lastProcStates.get(item.uid,
-                                    ActivityManager.PROCESS_STATE_UNKNOWN);
-                            if (lastState != ActivityManager.PROCESS_STATE_UNKNOWN) {
-                                final boolean lastAboveCut = lastState <= reg.cutpoint;
-                                final boolean newAboveCut = item.processState <= reg.cutpoint;
-                                doReport = lastAboveCut != newAboveCut;
-                            } else {
-                                doReport = item.processState != PROCESS_STATE_NONEXISTENT;
-                            }
-                        }
-                        if (doReport) {
-                            if (reg.lastProcStates != null) {
-                                reg.lastProcStates.put(item.uid, item.processState);
-                            }
-                            observer.onUidStateChanged(item.uid, item.processState,
-                                    item.procStateSeq, item.capability);
-                        }
-                    }
-                }
-                final int duration = (int) (SystemClock.uptimeMillis() - start);
-                if (reg.mMaxDispatchTime < duration) {
-                    reg.mMaxDispatchTime = duration;
-                }
-                if (duration >= SLOW_UID_OBSERVER_THRESHOLD_MS) {
-                    reg.mSlowDispatchCount++;
-                }
-            }
-        } catch (RemoteException e) {
-        }
-    }
-
     void dispatchOomAdjObserver(String msg) {
         OomAdjObserver observer;
         synchronized (this) {
@@ -4609,10 +4375,12 @@
                     proc.lastMemInfoTime = SystemClock.uptimeMillis();
                     if (proc.thread != null && proc.setAdj == oomAdj) {
                         // Record this for posterity if the process has been stable.
-                        proc.baseProcessTracker.addPss(infos[i].getTotalPss(),
-                                infos[i].getTotalUss(), infos[i].getTotalRss(), false,
-                                ProcessStats.ADD_PSS_EXTERNAL_SLOW, endTime - startTime,
-                                proc.pkgList.mPkgList);
+                        synchronized (mProcessStats.mLock) {
+                            proc.baseProcessTracker.addPss(infos[i].getTotalPss(),
+                                    infos[i].getTotalUss(), infos[i].getTotalRss(), false,
+                                    ProcessStats.ADD_PSS_EXTERNAL_SLOW, endTime - startTime,
+                                    proc.pkgList.mPkgList);
+                        }
                         for (int ipkg = proc.pkgList.size() - 1; ipkg >= 0; ipkg--) {
                             ProcessStats.ProcessStateHolder holder = proc.pkgList.valueAt(ipkg);
                             FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_MEMORY_STAT_REPORTED,
@@ -4669,8 +4437,11 @@
                 synchronized (this) {
                     if (proc.thread != null && proc.setAdj == oomAdj) {
                         // Record this for posterity if the process has been stable.
-                        proc.baseProcessTracker.addPss(pss[i], tmpUss[0], tmpUss[2], false,
-                                ProcessStats.ADD_PSS_EXTERNAL, endTime-startTime, proc.pkgList.mPkgList);
+                        synchronized (mProcessStats.mLock) {
+                            proc.baseProcessTracker.addPss(pss[i], tmpUss[0], tmpUss[2], false,
+                                    ProcessStats.ADD_PSS_EXTERNAL, endTime - startTime,
+                                    proc.pkgList.mPkgList);
+                        }
                         for (int ipkg = proc.pkgList.size() - 1; ipkg >= 0; ipkg--) {
                             ProcessStats.ProcessStateHolder holder = proc.pkgList.valueAt(ipkg);
                             FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_MEMORY_STAT_REPORTED,
@@ -7500,15 +7271,15 @@
                     "registerUidObserver");
         }
         synchronized (this) {
-            mUidObservers.register(observer, new UidObserverRegistration(Binder.getCallingUid(),
-                    callingPackage, which, cutpoint));
+            mUidObserverController.register(observer, which, cutpoint, callingPackage,
+                    Binder.getCallingUid());
         }
     }
 
     @Override
     public void unregisterUidObserver(IUidObserver observer) {
         synchronized (this) {
-            mUidObservers.unregister(observer);
+            mUidObserverController.unregister(observer);
         }
     }
 
@@ -10127,9 +9898,9 @@
         }
 
         if (dumpAll) {
-            if (mValidateUids.size() > 0) {
-                if (dumpUids(pw, dumpPackage, dumpAppId, mValidateUids, "UID validation:",
-                        needSep)) {
+            if (mUidObserverController.mValidateUids.size() > 0) {
+                if (dumpUids(pw, dumpPackage, dumpAppId, mUidObserverController.mValidateUids,
+                                "UID validation:", needSep)) {
                     needSep = true;
                 }
             }
@@ -10261,45 +10032,8 @@
             }
         }
         if (dumpAll) {
-            final int NI = mUidObservers.getRegisteredCallbackCount();
-            boolean printed = false;
-            for (int i=0; i<NI; i++) {
-                final UidObserverRegistration reg = (UidObserverRegistration)
-                        mUidObservers.getRegisteredCallbackCookie(i);
-                if (dumpPackage == null || dumpPackage.equals(reg.pkg)) {
-                    if (!printed) {
-                        pw.println("  mUidObservers:");
-                        printed = true;
-                    }
-                    pw.print("    "); UserHandle.formatUid(pw, reg.uid);
-                    pw.print(" "); pw.print(reg.pkg);
-                    final IUidObserver observer = mUidObservers.getRegisteredCallbackItem(i);
-                    pw.print(" "); pw.print(observer.getClass().getTypeName()); pw.print(":");
-                    if ((reg.which&ActivityManager.UID_OBSERVER_IDLE) != 0) {
-                        pw.print(" IDLE");
-                    }
-                    if ((reg.which&ActivityManager.UID_OBSERVER_ACTIVE) != 0) {
-                        pw.print(" ACT" );
-                    }
-                    if ((reg.which&ActivityManager.UID_OBSERVER_GONE) != 0) {
-                        pw.print(" GONE");
-                    }
-                    if ((reg.which&ActivityManager.UID_OBSERVER_PROCSTATE) != 0) {
-                        pw.print(" STATE");
-                        pw.print(" (cut="); pw.print(reg.cutpoint);
-                        pw.print(")");
-                    }
-                    pw.println();
-                    if (reg.lastProcStates != null) {
-                        final int NJ = reg.lastProcStates.size();
-                        for (int j=0; j<NJ; j++) {
-                            pw.print("      Last ");
-                            UserHandle.formatUid(pw, reg.lastProcStates.keyAt(j));
-                            pw.print(": "); pw.println(reg.lastProcStates.valueAt(j));
-                        }
-                    }
-                }
-            }
+            mUidObserverController.dump(pw, dumpPackage);
+
             pw.println("  mDeviceIdleWhitelist=" + Arrays.toString(mDeviceIdleWhitelist));
             pw.println("  mDeviceIdleExceptIdleWhitelist="
                     + Arrays.toString(mDeviceIdleExceptIdleWhitelist));
@@ -10427,25 +10161,6 @@
                         pw.print(" mLowRamSinceLastIdle=");
                         TimeUtils.formatDuration(getLowRamTimeSinceIdle(now), pw);
                         pw.println();
-                pw.println();
-                pw.print("  mUidChangeDispatchCount=");
-                pw.print(mUidChangeDispatchCount);
-                pw.println();
-
-                pw.println("  Slow UID dispatches:");
-                final int N = mUidObservers.beginBroadcast();
-                for (int i = 0; i < N; i++) {
-                    UidObserverRegistration r =
-                            (UidObserverRegistration) mUidObservers.getBroadcastCookie(i);
-                    pw.print("    ");
-                    pw.print(mUidObservers.getBroadcastItem(i).getClass().getTypeName());
-                    pw.print(": ");
-                    pw.print(r.mSlowDispatchCount);
-                    pw.print(" / Max ");
-                    pw.print(r.mMaxDispatchTime);
-                    pw.println("ms");
-                }
-                mUidObservers.finishBroadcast();
 
                 pw.println();
                 pw.println("  ServiceManager statistics:");
@@ -10513,8 +10228,8 @@
             uidRec.dumpDebug(proto, ActivityManagerServiceDumpProcessesProto.ACTIVE_UIDS);
         }
 
-        for (int i = 0; i < mValidateUids.size(); i++) {
-            UidRecord uidRec = mValidateUids.valueAt(i);
+        for (int i = 0; i < mUidObserverController.mValidateUids.size(); i++) {
+            UidRecord uidRec = mUidObserverController.mValidateUids.valueAt(i);
             if (dumpPackage != null && UserHandle.getAppId(uidRec.uid) != whichAppId) {
                 continue;
             }
@@ -10599,14 +10314,7 @@
             ActivityManagerServiceDumpProcessesProto.USER_CONTROLLER);
         }
 
-        final int NI = mUidObservers.getRegisteredCallbackCount();
-        for (int i=0; i<NI; i++) {
-            final UidObserverRegistration reg = (UidObserverRegistration)
-                    mUidObservers.getRegisteredCallbackCookie(i);
-            if (dumpPackage == null || dumpPackage.equals(reg.pkg)) {
-                reg.dumpDebug(proto, ActivityManagerServiceDumpProcessesProto.UID_OBSERVERS);
-            }
-        }
+        mUidObserverController.dumpDebug(proto, dumpPackage);
 
         for (int v : mDeviceIdleWhitelist) {
             proto.write(ActivityManagerServiceDumpProcessesProto.DEVICE_IDLE_WHITELIST, v);
@@ -12107,8 +11815,10 @@
                 synchronized (this) {
                     if (r.thread != null && oomAdj == r.getSetAdjWithServices()) {
                         // Record this for posterity if the process has been stable.
-                        r.baseProcessTracker.addPss(myTotalPss, myTotalUss, myTotalRss, true,
-                                reportType, endTime-startTime, r.pkgList.mPkgList);
+                        synchronized (mProcessStats.mLock) {
+                            r.baseProcessTracker.addPss(myTotalPss, myTotalUss, myTotalRss, true,
+                                    reportType, endTime - startTime, r.pkgList.mPkgList);
+                        }
                         for (int ipkg = r.pkgList.size() - 1; ipkg >= 0; ipkg--) {
                             ProcessStats.ProcessStateHolder holder = r.pkgList.valueAt(ipkg);
                             FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_MEMORY_STAT_REPORTED,
@@ -12713,8 +12423,10 @@
             synchronized (this) {
                 if (r.thread != null && oomAdj == r.getSetAdjWithServices()) {
                     // Record this for posterity if the process has been stable.
-                    r.baseProcessTracker.addPss(myTotalPss, myTotalUss, myTotalRss, true,
-                            reportType, endTime-startTime, r.pkgList.mPkgList);
+                    synchronized (mProcessStats.mLock) {
+                        r.baseProcessTracker.addPss(myTotalPss, myTotalUss, myTotalRss, true,
+                                reportType, endTime - startTime, r.pkgList.mPkgList);
+                    }
                     for (int ipkg = r.pkgList.size() - 1; ipkg >= 0; ipkg--) {
                         ProcessStats.ProcessStateHolder holder = r.pkgList.valueAt(ipkg);
                         FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_MEMORY_STAT_REPORTED,
@@ -15927,8 +15639,10 @@
         EventLogTags.writeAmPss(proc.pid, proc.uid, proc.processName, pss * 1024, uss * 1024,
                 swapPss * 1024, rss * 1024, statType, procState, pssDuration);
         proc.lastPssTime = now;
-        proc.baseProcessTracker.addPss(
-                pss, uss, rss, true, statType, pssDuration, proc.pkgList.mPkgList);
+        synchronized (mProcessStats.mLock) {
+            proc.baseProcessTracker.addPss(
+                    pss, uss, rss, true, statType, pssDuration, proc.pkgList.mPkgList);
+        }
         for (int ipkg = proc.pkgList.mPkgList.size() - 1; ipkg >= 0; ipkg--) {
             ProcessStats.ProcessStateHolder holder = proc.pkgList.valueAt(ipkg);
             FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_MEMORY_STAT_REPORTED,
@@ -16230,182 +15944,94 @@
         }
     }
 
-    final void checkExcessivePowerUsageLocked() {
+    private void checkExcessivePowerUsage() {
         updateCpuStatsNow();
 
-        BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
-        boolean doCpuKills = true;
-        if (mLastPowerCheckUptime == 0) {
-            doCpuKills = false;
-        }
-        final long curUptime = SystemClock.uptimeMillis();
-        final long uptimeSince = curUptime - mLastPowerCheckUptime;
-        mLastPowerCheckUptime = curUptime;
-        int i = mProcessList.mLruProcesses.size();
-        while (i > 0) {
-            i--;
-            ProcessRecord app = mProcessList.mLruProcesses.get(i);
-            if (app.setProcState >= ActivityManager.PROCESS_STATE_HOME) {
-                if (app.lastCpuTime <= 0) {
-                    continue;
-                }
-                long cputimeUsed = app.curCpuTime - app.lastCpuTime;
-                if (DEBUG_POWER) {
-                    StringBuilder sb = new StringBuilder(128);
-                    sb.append("CPU for ");
-                    app.toShortString(sb);
-                    sb.append(": over ");
-                    TimeUtils.formatDuration(uptimeSince, sb);
-                    sb.append(" used ");
-                    TimeUtils.formatDuration(cputimeUsed, sb);
-                    sb.append(" (");
-                    sb.append((cputimeUsed*100)/uptimeSince);
-                    sb.append("%)");
-                    Slog.i(TAG_POWER, sb.toString());
-                }
-                // If the process has used too much CPU over the last duration, the
-                // user probably doesn't want this, so kill!
-                if (doCpuKills && uptimeSince > 0) {
-                    // What is the limit for this process?
-                    int cpuLimit;
-                    long checkDur = curUptime - app.getWhenUnimportant();
-                    if (checkDur <= mConstants.POWER_CHECK_INTERVAL) {
-                        cpuLimit = mConstants.POWER_CHECK_MAX_CPU_1;
-                    } else if (checkDur <= (mConstants.POWER_CHECK_INTERVAL*2)
-                            || app.setProcState <= ActivityManager.PROCESS_STATE_HOME) {
-                        cpuLimit = mConstants.POWER_CHECK_MAX_CPU_2;
-                    } else if (checkDur <= (mConstants.POWER_CHECK_INTERVAL*3)) {
-                        cpuLimit = mConstants.POWER_CHECK_MAX_CPU_3;
-                    } else {
-                        cpuLimit = mConstants.POWER_CHECK_MAX_CPU_4;
+        synchronized (this) {
+            BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
+            boolean doCpuKills = true;
+            if (mLastPowerCheckUptime == 0) {
+                doCpuKills = false;
+            }
+            final long curUptime = SystemClock.uptimeMillis();
+            final long uptimeSince = curUptime - mLastPowerCheckUptime;
+            mLastPowerCheckUptime = curUptime;
+            int i = mProcessList.mLruProcesses.size();
+            while (i > 0) {
+                i--;
+                ProcessRecord app = mProcessList.mLruProcesses.get(i);
+                if (app.setProcState >= ActivityManager.PROCESS_STATE_HOME) {
+                    if (app.lastCpuTime <= 0) {
+                        continue;
                     }
-                    if (((cputimeUsed*100)/uptimeSince) >= cpuLimit) {
-                        synchronized (stats) {
-                            stats.reportExcessiveCpuLocked(app.info.uid, app.processName,
-                                    uptimeSince, cputimeUsed);
+                    long cputimeUsed = app.curCpuTime - app.lastCpuTime;
+                    if (DEBUG_POWER) {
+                        StringBuilder sb = new StringBuilder(128);
+                        sb.append("CPU for ");
+                        app.toShortString(sb);
+                        sb.append(": over ");
+                        TimeUtils.formatDuration(uptimeSince, sb);
+                        sb.append(" used ");
+                        TimeUtils.formatDuration(cputimeUsed, sb);
+                        sb.append(" (");
+                        sb.append((cputimeUsed * 100) / uptimeSince);
+                        sb.append("%)");
+                        Slog.i(TAG_POWER, sb.toString());
+                    }
+                    // If the process has used too much CPU over the last duration, the
+                    // user probably doesn't want this, so kill!
+                    if (doCpuKills && uptimeSince > 0) {
+                        // What is the limit for this process?
+                        int cpuLimit;
+                        long checkDur = curUptime - app.getWhenUnimportant();
+                        if (checkDur <= mConstants.POWER_CHECK_INTERVAL) {
+                            cpuLimit = mConstants.POWER_CHECK_MAX_CPU_1;
+                        } else if (checkDur <= (mConstants.POWER_CHECK_INTERVAL * 2)
+                                || app.setProcState <= ActivityManager.PROCESS_STATE_HOME) {
+                            cpuLimit = mConstants.POWER_CHECK_MAX_CPU_2;
+                        } else if (checkDur <= (mConstants.POWER_CHECK_INTERVAL * 3)) {
+                            cpuLimit = mConstants.POWER_CHECK_MAX_CPU_3;
+                        } else {
+                            cpuLimit = mConstants.POWER_CHECK_MAX_CPU_4;
                         }
-                        app.kill("excessive cpu " + cputimeUsed + " during " + uptimeSince
-                                + " dur=" + checkDur + " limit=" + cpuLimit,
-                                ApplicationExitInfo.REASON_EXCESSIVE_RESOURCE_USAGE,
-                                ApplicationExitInfo.SUBREASON_EXCESSIVE_CPU,
-                                true);
-                        app.baseProcessTracker.reportExcessiveCpu(app.pkgList.mPkgList);
-                        for (int ipkg = app.pkgList.size() - 1; ipkg >= 0; ipkg--) {
-                            ProcessStats.ProcessStateHolder holder = app.pkgList.valueAt(ipkg);
-                            FrameworkStatsLog.write(FrameworkStatsLog.EXCESSIVE_CPU_USAGE_REPORTED,
-                                    app.info.uid,
-                                    holder.state.getName(),
-                                    holder.state.getPackage(),
-                                    holder.appVersion);
+                        if (((cputimeUsed * 100) / uptimeSince) >= cpuLimit) {
+                            synchronized (stats) {
+                                stats.reportExcessiveCpuLocked(app.info.uid, app.processName,
+                                        uptimeSince, cputimeUsed);
+                            }
+                            app.kill("excessive cpu " + cputimeUsed + " during " + uptimeSince
+                                    + " dur=" + checkDur + " limit=" + cpuLimit,
+                                    ApplicationExitInfo.REASON_EXCESSIVE_RESOURCE_USAGE,
+                                    ApplicationExitInfo.SUBREASON_EXCESSIVE_CPU,
+                                    true);
+                            synchronized (mProcessStats.mLock) {
+                                app.baseProcessTracker.reportExcessiveCpu(app.pkgList.mPkgList);
+                            }
+                            for (int ipkg = app.pkgList.size() - 1; ipkg >= 0; ipkg--) {
+                                ProcessStats.ProcessStateHolder holder = app.pkgList.valueAt(ipkg);
+                                FrameworkStatsLog.write(
+                                        FrameworkStatsLog.EXCESSIVE_CPU_USAGE_REPORTED,
+                                        app.info.uid,
+                                        holder.state.getName(),
+                                        holder.state.getPackage(),
+                                        holder.appVersion);
+                            }
                         }
                     }
+                    app.lastCpuTime = app.curCpuTime;
                 }
-                app.lastCpuTime = app.curCpuTime;
-            }
-        }
-    }
-
-    private boolean isEphemeralLocked(int uid) {
-        String packages[] = mContext.getPackageManager().getPackagesForUid(uid);
-        if (packages == null || packages.length != 1) { // Ephemeral apps cannot share uid
-            return false;
-        }
-        return getPackageManagerInternalLocked().isPackageEphemeral(UserHandle.getUserId(uid),
-                packages[0]);
-    }
-
-    @VisibleForTesting
-    final void enqueueUidChangeLocked(UidRecord uidRec, int uid, int change) {
-        final UidRecord.ChangeItem pendingChange;
-        if (uidRec == null || uidRec.pendingChange == null) {
-            if (mPendingUidChanges.size() == 0) {
-                if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
-                        "*** Enqueueing dispatch uid changed!");
-                mUiHandler.obtainMessage(DISPATCH_UIDS_CHANGED_UI_MSG).sendToTarget();
-            }
-            final int NA = mAvailUidChanges.size();
-            if (NA > 0) {
-                pendingChange = mAvailUidChanges.remove(NA-1);
-                if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
-                        "Retrieving available item: " + pendingChange);
-            } else {
-                pendingChange = new UidRecord.ChangeItem();
-                if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
-                        "Allocating new item: " + pendingChange);
-            }
-            if (uidRec != null) {
-                uidRec.pendingChange = pendingChange;
-                if ((change & UidRecord.CHANGE_GONE) != 0 && !uidRec.idle) {
-                    // If this uid is going away, and we haven't yet reported it is gone,
-                    // then do so now.
-                    change |= UidRecord.CHANGE_IDLE;
-                }
-            } else if (uid < 0) {
-                throw new IllegalArgumentException("No UidRecord or uid");
-            }
-            pendingChange.uidRecord = uidRec;
-            pendingChange.uid = uidRec != null ? uidRec.uid : uid;
-            mPendingUidChanges.add(pendingChange);
-        } else {
-            pendingChange = uidRec.pendingChange;
-            // If there is no change in idle or active state, then keep whatever was pending.
-            if ((change & (UidRecord.CHANGE_IDLE | UidRecord.CHANGE_ACTIVE)) == 0) {
-                change |= (pendingChange.change & (UidRecord.CHANGE_IDLE
-                        | UidRecord.CHANGE_ACTIVE));
-            }
-            // If there is no change in cached or uncached state, then keep whatever was pending.
-            if ((change & (UidRecord.CHANGE_CACHED | UidRecord.CHANGE_UNCACHED)) == 0) {
-                change |= (pendingChange.change & (UidRecord.CHANGE_CACHED
-                        | UidRecord.CHANGE_UNCACHED));
-            }
-            // If this is a report of the UID being gone, then we shouldn't keep any previous
-            // report of it being active or cached.  (That is, a gone uid is never active,
-            // and never cached.)
-            if ((change & UidRecord.CHANGE_GONE) != 0) {
-                change &= ~(UidRecord.CHANGE_ACTIVE | UidRecord.CHANGE_CACHED);
-                if (!uidRec.idle) {
-                    // If this uid is going away, and we haven't yet reported it is gone,
-                    // then do so now.
-                    change |= UidRecord.CHANGE_IDLE;
-                }
-            }
-        }
-        pendingChange.change = change;
-        pendingChange.processState = uidRec != null ? uidRec.setProcState : PROCESS_STATE_NONEXISTENT;
-        pendingChange.capability = uidRec != null ? uidRec.setCapability : 0;
-        pendingChange.ephemeral = uidRec != null ? uidRec.ephemeral : isEphemeralLocked(uid);
-        pendingChange.procStateSeq = uidRec != null ? uidRec.curProcStateSeq : 0;
-        if (uidRec != null) {
-            uidRec.lastReportedChange = change;
-            uidRec.updateLastDispatchedProcStateSeq(change);
-        }
-
-        // Directly update the power manager, since we sit on top of it and it is critical
-        // it be kept in sync (so wake locks will be held as soon as appropriate).
-        if (mLocalPowerManager != null) {
-            // TO DO: dispatch cached/uncached changes here, so we don't need to report
-            // all proc state changes.
-            if ((change & UidRecord.CHANGE_ACTIVE) != 0) {
-                mLocalPowerManager.uidActive(pendingChange.uid);
-            }
-            if ((change & UidRecord.CHANGE_IDLE) != 0) {
-                mLocalPowerManager.uidIdle(pendingChange.uid);
-            }
-            if ((change & UidRecord.CHANGE_GONE) != 0) {
-                mLocalPowerManager.uidGone(pendingChange.uid);
-            } else {
-                mLocalPowerManager.updateUidProcState(pendingChange.uid,
-                        pendingChange.processState);
             }
         }
     }
 
     final void setProcessTrackerStateLocked(ProcessRecord proc, int memFactor, long now) {
-        if (proc.thread != null && proc.baseProcessTracker != null) {
-            final int procState = proc.getReportedProcState();
-            if (procState != PROCESS_STATE_NONEXISTENT) {
-                proc.baseProcessTracker.setState(
-                        procState, memFactor, now, proc.pkgList.mPkgList);
+        synchronized (mProcessStats.mLock) {
+            if (proc.thread != null && proc.baseProcessTracker != null) {
+                final int procState = proc.getReportedProcState();
+                if (procState != PROCESS_STATE_NONEXISTENT) {
+                    proc.baseProcessTracker.setState(
+                            procState, memFactor, now, proc.pkgList.mPkgList);
+                }
             }
         }
     }
@@ -16818,7 +16444,7 @@
     @GuardedBy("this")
     final void doStopUidLocked(int uid, final UidRecord uidRec) {
         mServices.stopInBackgroundLocked(uid);
-        enqueueUidChangeLocked(uidRec, uid, UidRecord.CHANGE_IDLE);
+        mUidObserverController.enqueueUidChangeLocked(uidRec, uid, UidRecord.CHANGE_IDLE);
     }
 
     /**
@@ -18479,7 +18105,8 @@
                     Slog.w(TAG_NETWORK, "Total time waited for network rules to get updated: "
                             + totalTime + ". Uid: " + callingUid + " procStateSeq: "
                             + procStateSeq + " UidRec: " + record
-                            + " validateUidRec: " + mValidateUids.get(callingUid));
+                            + " validateUidRec: "
+                            + mUidObserverController.mValidateUids.get(callingUid));
                 }
             } catch (InterruptedException e) {
                 Thread.currentThread().interrupt();
diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java
index 0f2dfcc..757b4e8 100644
--- a/services/core/java/com/android/server/am/CoreSettingsObserver.java
+++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java
@@ -100,15 +100,20 @@
         sGlobalSettingToTypeMap.put(Settings.Global.GPU_DEBUG_LAYERS, String.class);
         sGlobalSettingToTypeMap.put(Settings.Global.GPU_DEBUG_LAYERS_GLES, String.class);
         sGlobalSettingToTypeMap.put(Settings.Global.GPU_DEBUG_LAYER_APP, String.class);
-        sGlobalSettingToTypeMap.put(Settings.Global.GAME_DRIVER_ALL_APPS, int.class);
-        sGlobalSettingToTypeMap.put(Settings.Global.GAME_DRIVER_OPT_IN_APPS, String.class);
+        sGlobalSettingToTypeMap.put(Settings.Global.UPDATABLE_DRIVER_ALL_APPS, int.class);
         sGlobalSettingToTypeMap.put(
-                Settings.Global.GAME_DRIVER_PRERELEASE_OPT_IN_APPS, String.class);
-        sGlobalSettingToTypeMap.put(Settings.Global.GAME_DRIVER_OPT_OUT_APPS, String.class);
-        sGlobalSettingToTypeMap.put(Settings.Global.GAME_DRIVER_DENYLIST, String.class);
-        sGlobalSettingToTypeMap.put(Settings.Global.GAME_DRIVER_ALLOWLIST, String.class);
-        sGlobalSettingToTypeMap.put(Settings.Global.GAME_DRIVER_DENYLISTS, String.class);
-        sGlobalSettingToTypeMap.put(Settings.Global.GAME_DRIVER_SPHAL_LIBRARIES, String.class);
+                Settings.Global.UPDATABLE_DRIVER_PRODUCTION_OPT_IN_APPS, String.class);
+        sGlobalSettingToTypeMap.put(
+                Settings.Global.UPDATABLE_DRIVER_PRERELEASE_OPT_IN_APPS, String.class);
+        sGlobalSettingToTypeMap.put(
+                Settings.Global.UPDATABLE_DRIVER_PRODUCTION_OPT_OUT_APPS, String.class);
+        sGlobalSettingToTypeMap.put(
+                Settings.Global.UPDATABLE_DRIVER_PRODUCTION_DENYLIST, String.class);
+        sGlobalSettingToTypeMap.put(
+                Settings.Global.UPDATABLE_DRIVER_PRODUCTION_ALLOWLIST, String.class);
+        sGlobalSettingToTypeMap.put(
+                Settings.Global.UPDATABLE_DRIVER_PRODUCTION_DENYLISTS, String.class);
+        sGlobalSettingToTypeMap.put(Settings.Global.UPDATABLE_DRIVER_SPHAL_LIBRARIES, String.class);
         // add other global settings here...
 
         sDeviceConfigEntries.add(new DeviceConfigEntry<Boolean>(
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index bf15f1737..bad042c 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -987,7 +987,7 @@
                 uidRec.setWhitelist = uidRec.curWhitelist;
                 uidRec.setIdle = uidRec.idle;
                 mService.mAtmInternal.onUidProcStateChanged(uidRec.uid, uidRec.setProcState);
-                mService.enqueueUidChangeLocked(uidRec, -1, uidChange);
+                mService.mUidObserverController.enqueueUidChangeLocked(uidRec, -1, uidChange);
                 mService.noteUidProcessState(uidRec.uid, uidRec.getCurProcState(),
                         uidRec.curCapability);
                 if (uidRec.foregroundServices) {
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 5721fb7..4673ecd 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -2222,7 +2222,8 @@
                 // access /mnt/user anyway.
                 && app.mountMode != Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE
                 && app.mountMode != Zygote.MOUNT_EXTERNAL_PASS_THROUGH
-                && app.mountMode != Zygote.MOUNT_EXTERNAL_INSTALLER;
+                && app.mountMode != Zygote.MOUNT_EXTERNAL_INSTALLER
+                && app.mountMode != Zygote.MOUNT_EXTERNAL_NONE;
     }
 
     private Process.ProcessStartResult startProcess(HostingRecord hostingRecord, String entryPoint,
@@ -2960,7 +2961,8 @@
                 // No more processes using this uid, tell clients it is gone.
                 if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
                         "No more processes in " + uidRecord);
-                mService.enqueueUidChangeLocked(uidRecord, -1, UidRecord.CHANGE_GONE);
+                mService.mUidObserverController.enqueueUidChangeLocked(uidRecord, -1,
+                        UidRecord.CHANGE_GONE);
                 EventLogTags.writeAmUidStopped(uid);
                 mActiveUids.remove(uid);
                 mService.noteUidProcessState(uid, ActivityManager.PROCESS_STATE_NONEXISTENT,
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index e3e1339..14bc6cb 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -1079,8 +1079,8 @@
      */
     public void resetPackageList(ProcessStatsService tracker) {
         final int N = pkgList.size();
-        if (baseProcessTracker != null) {
-            synchronized (tracker.mLock) {
+        synchronized (tracker.mLock) {
+            if (baseProcessTracker != null) {
                 long now = SystemClock.uptimeMillis();
                 baseProcessTracker.setState(ProcessStats.STATE_NOTHING,
                         tracker.getMemFactorLocked(), now, pkgList.mPkgList);
@@ -1108,10 +1108,11 @@
                         holder.state.makeActive();
                     }
                 }
+            } else if (N != 1) {
+                pkgList.clear();
+                pkgList.put(info.packageName,
+                        new ProcessStats.ProcessStateHolder(info.longVersionCode));
             }
-        } else if (N != 1) {
-            pkgList.clear();
-            pkgList.put(info.packageName, new ProcessStats.ProcessStateHolder(info.longVersionCode));
         }
     }
 
diff --git a/services/core/java/com/android/server/am/UidObserverController.java b/services/core/java/com/android/server/am/UidObserverController.java
new file mode 100644
index 0000000..4d9260a
--- /dev/null
+++ b/services/core/java/com/android/server/am/UidObserverController.java
@@ -0,0 +1,470 @@
+/*
+ * 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.am;
+
+import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_UID_OBSERVERS;
+import static com.android.server.am.ActivityManagerService.TAG_UID_OBSERVERS;
+
+import android.app.ActivityManager;
+import android.app.ActivityManagerProto;
+import android.app.IUidObserver;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.Slog;
+import android.util.SparseIntArray;
+import android.util.proto.ProtoOutputStream;
+import android.util.proto.ProtoUtils;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.am.ActivityManagerServiceDumpProcessesProto.UidObserverRegistrationProto;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+public class UidObserverController {
+    private final ActivityManagerService mService;
+    final RemoteCallbackList<IUidObserver> mUidObservers = new RemoteCallbackList<>();
+
+    UidRecord.ChangeItem[] mActiveUidChanges = new UidRecord.ChangeItem[5];
+    final ArrayList<UidRecord.ChangeItem> mPendingUidChanges = new ArrayList<>();
+    final ArrayList<UidRecord.ChangeItem> mAvailUidChanges = new ArrayList<>();
+
+    /** Total # of UID change events dispatched, shown in dumpsys. */
+    int mUidChangeDispatchCount;
+
+    /** If a UID observer takes more than this long, send a WTF. */
+    private static final int SLOW_UID_OBSERVER_THRESHOLD_MS = 20;
+
+    /**
+     * This is for verifying the UID report flow.
+     */
+    static final boolean VALIDATE_UID_STATES = true;
+    final ActiveUids mValidateUids;
+
+    UidObserverController(ActivityManagerService service) {
+        mService = service;
+        mValidateUids = new ActiveUids(mService, false /* postChangesToAtm */);
+    }
+
+    @GuardedBy("mService")
+    void register(IUidObserver observer, int which, int cutpoint, String callingPackage,
+            int callingUid) {
+        mUidObservers.register(observer, new UidObserverRegistration(callingUid,
+                callingPackage, which, cutpoint));
+    }
+
+    @GuardedBy("mService")
+    void unregister(IUidObserver observer) {
+        mUidObservers.unregister(observer);
+    }
+
+    @GuardedBy("mService")
+    final void enqueueUidChangeLocked(UidRecord uidRec, int uid, int change) {
+        final UidRecord.ChangeItem pendingChange;
+        if (uidRec == null || uidRec.pendingChange == null) {
+            if (mPendingUidChanges.size() == 0) {
+                if (DEBUG_UID_OBSERVERS) {
+                    Slog.i(TAG_UID_OBSERVERS, "*** Enqueueing dispatch uid changed!");
+                }
+                mService.mUiHandler.post(this::dispatchUidsChanged);
+            }
+            final int size = mAvailUidChanges.size();
+            if (size > 0) {
+                pendingChange = mAvailUidChanges.remove(size - 1);
+                if (DEBUG_UID_OBSERVERS) {
+                    Slog.i(TAG_UID_OBSERVERS, "Retrieving available item: " + pendingChange);
+                }
+            } else {
+                pendingChange = new UidRecord.ChangeItem();
+                if (DEBUG_UID_OBSERVERS) {
+                    Slog.i(TAG_UID_OBSERVERS, "Allocating new item: " + pendingChange);
+                }
+            }
+            if (uidRec != null) {
+                uidRec.pendingChange = pendingChange;
+                if ((change & UidRecord.CHANGE_GONE) != 0 && !uidRec.idle) {
+                    // If this uid is going away, and we haven't yet reported it is gone,
+                    // then do so now.
+                    change |= UidRecord.CHANGE_IDLE;
+                }
+            } else if (uid < 0) {
+                throw new IllegalArgumentException("No UidRecord or uid");
+            }
+            pendingChange.uidRecord = uidRec;
+            pendingChange.uid = uidRec != null ? uidRec.uid : uid;
+            mPendingUidChanges.add(pendingChange);
+        } else {
+            pendingChange = uidRec.pendingChange;
+            // If there is no change in idle or active state, then keep whatever was pending.
+            if ((change & (UidRecord.CHANGE_IDLE | UidRecord.CHANGE_ACTIVE)) == 0) {
+                change |= (pendingChange.change & (UidRecord.CHANGE_IDLE
+                        | UidRecord.CHANGE_ACTIVE));
+            }
+            // If there is no change in cached or uncached state, then keep whatever was pending.
+            if ((change & (UidRecord.CHANGE_CACHED | UidRecord.CHANGE_UNCACHED)) == 0) {
+                change |= (pendingChange.change & (UidRecord.CHANGE_CACHED
+                        | UidRecord.CHANGE_UNCACHED));
+            }
+            // If this is a report of the UID being gone, then we shouldn't keep any previous
+            // report of it being active or cached.  (That is, a gone uid is never active,
+            // and never cached.)
+            if ((change & UidRecord.CHANGE_GONE) != 0) {
+                change &= ~(UidRecord.CHANGE_ACTIVE | UidRecord.CHANGE_CACHED);
+                if (!uidRec.idle) {
+                    // If this uid is going away, and we haven't yet reported it is gone,
+                    // then do so now.
+                    change |= UidRecord.CHANGE_IDLE;
+                }
+            }
+        }
+        pendingChange.change = change;
+        pendingChange.processState = uidRec != null
+                ? uidRec.setProcState : PROCESS_STATE_NONEXISTENT;
+        pendingChange.capability = uidRec != null ? uidRec.setCapability : 0;
+        pendingChange.ephemeral = uidRec != null ? uidRec.ephemeral : isEphemeralLocked(uid);
+        pendingChange.procStateSeq = uidRec != null ? uidRec.curProcStateSeq : 0;
+        if (uidRec != null) {
+            uidRec.lastReportedChange = change;
+            uidRec.updateLastDispatchedProcStateSeq(change);
+        }
+
+        // Directly update the power manager, since we sit on top of it and it is critical
+        // it be kept in sync (so wake locks will be held as soon as appropriate).
+        if (mService.mLocalPowerManager != null) {
+            // TO DO: dispatch cached/uncached changes here, so we don't need to report
+            // all proc state changes.
+            if ((change & UidRecord.CHANGE_ACTIVE) != 0) {
+                mService.mLocalPowerManager.uidActive(pendingChange.uid);
+            }
+            if ((change & UidRecord.CHANGE_IDLE) != 0) {
+                mService.mLocalPowerManager.uidIdle(pendingChange.uid);
+            }
+            if ((change & UidRecord.CHANGE_GONE) != 0) {
+                mService.mLocalPowerManager.uidGone(pendingChange.uid);
+            } else {
+                mService.mLocalPowerManager.updateUidProcState(pendingChange.uid,
+                        pendingChange.processState);
+            }
+        }
+    }
+
+    @VisibleForTesting
+    void dispatchUidsChanged() {
+        int numUidChanges;
+        synchronized (mService) {
+            numUidChanges = mPendingUidChanges.size();
+            if (mActiveUidChanges.length < numUidChanges) {
+                mActiveUidChanges = new UidRecord.ChangeItem[numUidChanges];
+            }
+            for (int i = 0; i < numUidChanges; i++) {
+                final UidRecord.ChangeItem change = mPendingUidChanges.get(i);
+                mActiveUidChanges[i] = change;
+                if (change.uidRecord != null) {
+                    change.uidRecord.pendingChange = null;
+                    change.uidRecord = null;
+                }
+            }
+            mPendingUidChanges.clear();
+            if (DEBUG_UID_OBSERVERS) {
+                Slog.i(TAG_UID_OBSERVERS, "*** Delivering " + numUidChanges + " uid changes");
+            }
+        }
+
+        mUidChangeDispatchCount += numUidChanges;
+        int i = mUidObservers.beginBroadcast();
+        while (i > 0) {
+            i--;
+            dispatchUidsChangedForObserver(mUidObservers.getBroadcastItem(i),
+                    (UidObserverRegistration) mUidObservers.getBroadcastCookie(i), numUidChanges);
+        }
+        mUidObservers.finishBroadcast();
+
+        if (VALIDATE_UID_STATES && mUidObservers.getRegisteredCallbackCount() > 0) {
+            for (int j = 0; j < numUidChanges; ++j) {
+                final UidRecord.ChangeItem item = mActiveUidChanges[j];
+                if ((item.change & UidRecord.CHANGE_GONE) != 0) {
+                    mValidateUids.remove(item.uid);
+                } else {
+                    UidRecord validateUid = mValidateUids.get(item.uid);
+                    if (validateUid == null) {
+                        validateUid = new UidRecord(item.uid);
+                        mValidateUids.put(item.uid, validateUid);
+                    }
+                    if ((item.change & UidRecord.CHANGE_IDLE) != 0) {
+                        validateUid.idle = true;
+                    } else if ((item.change & UidRecord.CHANGE_ACTIVE) != 0) {
+                        validateUid.idle = false;
+                    }
+                    validateUid.setCurProcState(validateUid.setProcState = item.processState);
+                    validateUid.curCapability = validateUid.setCapability = item.capability;
+                    validateUid.lastDispatchedProcStateSeq = item.procStateSeq;
+                }
+            }
+        }
+
+        synchronized (mService) {
+            for (int j = 0; j < numUidChanges; j++) {
+                mAvailUidChanges.add(mActiveUidChanges[j]);
+            }
+        }
+    }
+
+    private void dispatchUidsChangedForObserver(IUidObserver observer,
+            UidObserverRegistration reg, int changesSize) {
+        if (observer == null) {
+            return;
+        }
+        try {
+            for (int j = 0; j < changesSize; j++) {
+                UidRecord.ChangeItem item = mActiveUidChanges[j];
+                final int change = item.change;
+                if (change == UidRecord.CHANGE_PROCSTATE
+                        && (reg.mWhich & ActivityManager.UID_OBSERVER_PROCSTATE) == 0) {
+                    // No-op common case: no significant change, the observer is not
+                    // interested in all proc state changes.
+                    continue;
+                }
+                final long start = SystemClock.uptimeMillis();
+                if ((change & UidRecord.CHANGE_IDLE) != 0) {
+                    if ((reg.mWhich & ActivityManager.UID_OBSERVER_IDLE) != 0) {
+                        if (DEBUG_UID_OBSERVERS) {
+                            Slog.i(TAG_UID_OBSERVERS, "UID idle uid=" + item.uid);
+                        }
+                        observer.onUidIdle(item.uid, item.ephemeral);
+                    }
+                } else if ((change & UidRecord.CHANGE_ACTIVE) != 0) {
+                    if ((reg.mWhich & ActivityManager.UID_OBSERVER_ACTIVE) != 0) {
+                        if (DEBUG_UID_OBSERVERS) {
+                            Slog.i(TAG_UID_OBSERVERS, "UID active uid=" + item.uid);
+                        }
+                        observer.onUidActive(item.uid);
+                    }
+                }
+                if ((reg.mWhich & ActivityManager.UID_OBSERVER_CACHED) != 0) {
+                    if ((change & UidRecord.CHANGE_CACHED) != 0) {
+                        if (DEBUG_UID_OBSERVERS) {
+                            Slog.i(TAG_UID_OBSERVERS, "UID cached uid=" + item.uid);
+                        }
+                        observer.onUidCachedChanged(item.uid, true);
+                    } else if ((change & UidRecord.CHANGE_UNCACHED) != 0) {
+                        if (DEBUG_UID_OBSERVERS) {
+                            Slog.i(TAG_UID_OBSERVERS, "UID active uid=" + item.uid);
+                        }
+                        observer.onUidCachedChanged(item.uid, false);
+                    }
+                }
+                if ((change & UidRecord.CHANGE_GONE) != 0) {
+                    if ((reg.mWhich & ActivityManager.UID_OBSERVER_GONE) != 0) {
+                        if (DEBUG_UID_OBSERVERS) {
+                            Slog.i(TAG_UID_OBSERVERS, "UID gone uid=" + item.uid);
+                        }
+                        observer.onUidGone(item.uid, item.ephemeral);
+                    }
+                    if (reg.mLastProcStates != null) {
+                        reg.mLastProcStates.delete(item.uid);
+                    }
+                } else {
+                    if ((reg.mWhich & ActivityManager.UID_OBSERVER_PROCSTATE) != 0) {
+                        if (DEBUG_UID_OBSERVERS) {
+                            Slog.i(TAG_UID_OBSERVERS, "UID CHANGED uid=" + item.uid
+                                    + ": " + item.processState + ": " + item.capability);
+                        }
+                        boolean doReport = true;
+                        if (reg.mCutpoint >= ActivityManager.MIN_PROCESS_STATE) {
+                            final int lastState = reg.mLastProcStates.get(item.uid,
+                                    ActivityManager.PROCESS_STATE_UNKNOWN);
+                            if (lastState != ActivityManager.PROCESS_STATE_UNKNOWN) {
+                                final boolean lastAboveCut = lastState <= reg.mCutpoint;
+                                final boolean newAboveCut = item.processState <= reg.mCutpoint;
+                                doReport = lastAboveCut != newAboveCut;
+                            } else {
+                                doReport = item.processState != PROCESS_STATE_NONEXISTENT;
+                            }
+                        }
+                        if (doReport) {
+                            if (reg.mLastProcStates != null) {
+                                reg.mLastProcStates.put(item.uid, item.processState);
+                            }
+                            observer.onUidStateChanged(item.uid, item.processState,
+                                    item.procStateSeq, item.capability);
+                        }
+                    }
+                }
+                final int duration = (int) (SystemClock.uptimeMillis() - start);
+                if (reg.mMaxDispatchTime < duration) {
+                    reg.mMaxDispatchTime = duration;
+                }
+                if (duration >= SLOW_UID_OBSERVER_THRESHOLD_MS) {
+                    reg.mSlowDispatchCount++;
+                }
+            }
+        } catch (RemoteException e) {
+        }
+    }
+
+    private boolean isEphemeralLocked(int uid) {
+        final String[] packages = mService.mContext.getPackageManager().getPackagesForUid(uid);
+        if (packages == null || packages.length != 1) { // Ephemeral apps cannot share uid
+            return false;
+        }
+        return mService.getPackageManagerInternalLocked().isPackageEphemeral(
+                UserHandle.getUserId(uid), packages[0]);
+    }
+
+    @GuardedBy("mService")
+    void dump(PrintWriter pw, String dumpPackage) {
+        final int count = mUidObservers.getRegisteredCallbackCount();
+        boolean printed = false;
+        for (int i = 0; i < count; i++) {
+            final UidObserverRegistration reg = (UidObserverRegistration)
+                    mUidObservers.getRegisteredCallbackCookie(i);
+            if (dumpPackage == null || dumpPackage.equals(reg.mPkg)) {
+                if (!printed) {
+                    pw.println("  mUidObservers:");
+                    printed = true;
+                }
+                pw.print("    "); UserHandle.formatUid(pw, reg.mUid);
+                pw.print(" "); pw.print(reg.mPkg);
+                final IUidObserver observer = mUidObservers.getRegisteredCallbackItem(i);
+                pw.print(" "); pw.print(observer.getClass().getTypeName()); pw.print(":");
+                if ((reg.mWhich & ActivityManager.UID_OBSERVER_IDLE) != 0) {
+                    pw.print(" IDLE");
+                }
+                if ((reg.mWhich & ActivityManager.UID_OBSERVER_ACTIVE) != 0) {
+                    pw.print(" ACT");
+                }
+                if ((reg.mWhich & ActivityManager.UID_OBSERVER_GONE) != 0) {
+                    pw.print(" GONE");
+                }
+                if ((reg.mWhich & ActivityManager.UID_OBSERVER_PROCSTATE) != 0) {
+                    pw.print(" STATE");
+                    pw.print(" (cut="); pw.print(reg.mCutpoint);
+                    pw.print(")");
+                }
+                pw.println();
+                if (reg.mLastProcStates != null) {
+                    final int size = reg.mLastProcStates.size();
+                    for (int j = 0; j < size; j++) {
+                        pw.print("      Last ");
+                        UserHandle.formatUid(pw, reg.mLastProcStates.keyAt(j));
+                        pw.print(": "); pw.println(reg.mLastProcStates.valueAt(j));
+                    }
+                }
+            }
+        }
+
+        pw.println();
+        pw.print("  mUidChangeDispatchCount=");
+        pw.print(mUidChangeDispatchCount);
+        pw.println();
+        pw.println("  Slow UID dispatches:");
+        final int size = mUidObservers.beginBroadcast();
+        for (int i = 0; i < size; i++) {
+            UidObserverRegistration r =
+                    (UidObserverRegistration) mUidObservers.getBroadcastCookie(i);
+            pw.print("    ");
+            pw.print(mUidObservers.getBroadcastItem(i).getClass().getTypeName());
+            pw.print(": ");
+            pw.print(r.mSlowDispatchCount);
+            pw.print(" / Max ");
+            pw.print(r.mMaxDispatchTime);
+            pw.println("ms");
+        }
+        mUidObservers.finishBroadcast();
+    }
+
+    @GuardedBy("mService")
+    void dumpDebug(ProtoOutputStream proto, String dumpPackage) {
+        final int count = mUidObservers.getRegisteredCallbackCount();
+        for (int i = 0; i < count; i++) {
+            final UidObserverRegistration reg = (UidObserverRegistration)
+                    mUidObservers.getRegisteredCallbackCookie(i);
+            if (dumpPackage == null || dumpPackage.equals(reg.mPkg)) {
+                reg.dumpDebug(proto, ActivityManagerServiceDumpProcessesProto.UID_OBSERVERS);
+            }
+        }
+    }
+
+    private static final class UidObserverRegistration {
+        final int mUid;
+        final String mPkg;
+        final int mWhich;
+        final int mCutpoint;
+
+        /**
+         * Total # of callback calls that took more than {@link #SLOW_UID_OBSERVER_THRESHOLD_MS}.
+         * We show it in dumpsys.
+         */
+        int mSlowDispatchCount;
+
+        /** Max time it took for each dispatch. */
+        int mMaxDispatchTime;
+
+        final SparseIntArray mLastProcStates;
+
+        // Please keep the enum lists in sync
+        private static final int[] ORIG_ENUMS = new int[]{
+                ActivityManager.UID_OBSERVER_IDLE,
+                ActivityManager.UID_OBSERVER_ACTIVE,
+                ActivityManager.UID_OBSERVER_GONE,
+                ActivityManager.UID_OBSERVER_PROCSTATE,
+        };
+        private static final int[] PROTO_ENUMS = new int[]{
+                ActivityManagerProto.UID_OBSERVER_FLAG_IDLE,
+                ActivityManagerProto.UID_OBSERVER_FLAG_ACTIVE,
+                ActivityManagerProto.UID_OBSERVER_FLAG_GONE,
+                ActivityManagerProto.UID_OBSERVER_FLAG_PROCSTATE,
+        };
+
+        UidObserverRegistration(int uid, String pkg, int which, int cutpoint) {
+            this.mUid = uid;
+            this.mPkg = pkg;
+            this.mWhich = which;
+            this.mCutpoint = cutpoint;
+            if (cutpoint >= ActivityManager.MIN_PROCESS_STATE) {
+                mLastProcStates = new SparseIntArray();
+            } else {
+                mLastProcStates = null;
+            }
+        }
+
+        void dumpDebug(ProtoOutputStream proto, long fieldId) {
+            final long token = proto.start(fieldId);
+            proto.write(UidObserverRegistrationProto.UID, mUid);
+            proto.write(UidObserverRegistrationProto.PACKAGE, mPkg);
+            ProtoUtils.writeBitWiseFlagsToProtoEnum(proto, UidObserverRegistrationProto.FLAGS,
+                    mWhich, ORIG_ENUMS, PROTO_ENUMS);
+            proto.write(UidObserverRegistrationProto.CUT_POINT, mCutpoint);
+            if (mLastProcStates != null) {
+                final int size = mLastProcStates.size();
+                for (int i = 0; i < size; i++) {
+                    final long pToken = proto.start(UidObserverRegistrationProto.LAST_PROC_STATES);
+                    proto.write(UidObserverRegistrationProto.ProcState.UID,
+                            mLastProcStates.keyAt(i));
+                    proto.write(UidObserverRegistrationProto.ProcState.STATE,
+                            mLastProcStates.valueAt(i));
+                    proto.end(pToken);
+                }
+            }
+            proto.end(token);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 7420e0a..83bf28e 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -988,6 +988,8 @@
         final ArrayList<IStopUserCallback> stopCallbacks;
         final ArrayList<KeyEvictedCallback> keyEvictedCallbacks;
         int userIdToLock = userId;
+        // Must get a reference to UserInfo before it's removed
+        final UserInfo userInfo = getUserInfo(userId);
         synchronized (mLock) {
             stopCallbacks = new ArrayList<>(uss.mStopCallbacks);
             keyEvictedCallbacks = new ArrayList<>(uss.mKeyEvictedCallbacks);
@@ -1030,8 +1032,8 @@
         if (stopped) {
             mInjector.systemServiceManagerCleanupUser(userId);
             mInjector.stackSupervisorRemoveUser(userId);
+
             // Remove the user if it is ephemeral.
-            UserInfo userInfo = getUserInfo(userId);
             if (userInfo.isEphemeral() && !userInfo.preCreated) {
                 mInjector.getUserManager().removeUserEvenWhenDisallowed(userId);
             }
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index ee441bf..dfe8af1 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -6222,7 +6222,9 @@
 
             int[] users;
             if (userId == UserHandle.USER_ALL) {
-                List<UserInfo> liveUsers = UserManager.get(mContext).getUsers(false);
+                // TODO(b/157921703): this call is returning all users, not just live ones - we
+                // need to either fix the method called, or rename the variable
+                List<UserInfo> liveUsers = UserManager.get(mContext).getUsers();
 
                 users = new int[liveUsers.size()];
                 for (int i = 0; i < liveUsers.size(); i++) {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 2bbbbf1..06ef58f 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -28,7 +28,7 @@
 import android.media.AudioRoutesInfo;
 import android.media.AudioSystem;
 import android.media.IAudioRoutesObserver;
-import android.media.IStrategyPreferredDeviceDispatcher;
+import android.media.IStrategyPreferredDevicesDispatcher;
 import android.media.MediaMetrics;
 import android.os.Binder;
 import android.os.Handler;
@@ -47,6 +47,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashSet;
+import java.util.List;
 import java.util.NoSuchElementException;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -526,23 +527,23 @@
         }
     }
 
-    /*package*/ int setPreferredDeviceForStrategySync(int strategy,
-                                                      @NonNull AudioDeviceAttributes device) {
-        return mDeviceInventory.setPreferredDeviceForStrategySync(strategy, device);
+    /*package*/ int setPreferredDevicesForStrategySync(int strategy,
+            @NonNull List<AudioDeviceAttributes> devices) {
+        return mDeviceInventory.setPreferredDevicesForStrategySync(strategy, devices);
     }
 
-    /*package*/ int removePreferredDeviceForStrategySync(int strategy) {
-        return mDeviceInventory.removePreferredDeviceForStrategySync(strategy);
+    /*package*/ int removePreferredDevicesForStrategySync(int strategy) {
+        return mDeviceInventory.removePreferredDevicesForStrategySync(strategy);
     }
 
-    /*package*/ void registerStrategyPreferredDeviceDispatcher(
-            @NonNull IStrategyPreferredDeviceDispatcher dispatcher) {
-        mDeviceInventory.registerStrategyPreferredDeviceDispatcher(dispatcher);
+    /*package*/ void registerStrategyPreferredDevicesDispatcher(
+            @NonNull IStrategyPreferredDevicesDispatcher dispatcher) {
+        mDeviceInventory.registerStrategyPreferredDevicesDispatcher(dispatcher);
     }
 
-    /*package*/ void unregisterStrategyPreferredDeviceDispatcher(
-            @NonNull IStrategyPreferredDeviceDispatcher dispatcher) {
-        mDeviceInventory.unregisterStrategyPreferredDeviceDispatcher(dispatcher);
+    /*package*/ void unregisterStrategyPreferredDevicesDispatcher(
+            @NonNull IStrategyPreferredDevicesDispatcher dispatcher) {
+        mDeviceInventory.unregisterStrategyPreferredDevicesDispatcher(dispatcher);
     }
 
     //---------------------------------------------------------------------
@@ -683,14 +684,14 @@
         sendLMsgNoDelay(MSG_L_SPEAKERPHONE_CLIENT_DIED, SENDMSG_QUEUE, obj);
     }
 
-    /*package*/ void postSaveSetPreferredDeviceForStrategy(int strategy,
-                                                           AudioDeviceAttributes device)
+    /*package*/ void postSaveSetPreferredDevicesForStrategy(int strategy,
+                                                            List<AudioDeviceAttributes> devices)
     {
-        sendILMsgNoDelay(MSG_IL_SAVE_PREF_DEVICE_FOR_STRATEGY, SENDMSG_QUEUE, strategy, device);
+        sendILMsgNoDelay(MSG_IL_SAVE_PREF_DEVICES_FOR_STRATEGY, SENDMSG_QUEUE, strategy, devices);
     }
 
-    /*package*/ void postSaveRemovePreferredDeviceForStrategy(int strategy) {
-        sendIMsgNoDelay(MSG_I_SAVE_REMOVE_PREF_DEVICE_FOR_STRATEGY, SENDMSG_QUEUE, strategy);
+    /*package*/ void postSaveRemovePreferredDevicesForStrategy(int strategy) {
+        sendIMsgNoDelay(MSG_I_SAVE_REMOVE_PREF_DEVICES_FOR_STRATEGY, SENDMSG_QUEUE, strategy);
     }
 
     //---------------------------------------------------------------------
@@ -1082,14 +1083,15 @@
                                 info.mDevice, info.mState, info.mSupprNoisy, info.mMusicDevice);
                     }
                 } break;
-                case MSG_IL_SAVE_PREF_DEVICE_FOR_STRATEGY: {
+                case MSG_IL_SAVE_PREF_DEVICES_FOR_STRATEGY: {
                     final int strategy = msg.arg1;
-                    final AudioDeviceAttributes device = (AudioDeviceAttributes) msg.obj;
-                    mDeviceInventory.onSaveSetPreferredDevice(strategy, device);
+                    final List<AudioDeviceAttributes> devices =
+                            (List<AudioDeviceAttributes>) msg.obj;
+                    mDeviceInventory.onSaveSetPreferredDevices(strategy, devices);
                 } break;
-                case MSG_I_SAVE_REMOVE_PREF_DEVICE_FOR_STRATEGY: {
+                case MSG_I_SAVE_REMOVE_PREF_DEVICES_FOR_STRATEGY: {
                     final int strategy = msg.arg1;
-                    mDeviceInventory.onSaveRemovePreferredDevice(strategy);
+                    mDeviceInventory.onSaveRemovePreferredDevices(strategy);
                 } break;
                 case MSG_CHECK_MUTE_MUSIC:
                     checkMessagesMuteMusic(0);
@@ -1163,8 +1165,8 @@
 
     // a ScoClient died in BtHelper
     private static final int MSG_L_SCOCLIENT_DIED = 32;
-    private static final int MSG_IL_SAVE_PREF_DEVICE_FOR_STRATEGY = 33;
-    private static final int MSG_I_SAVE_REMOVE_PREF_DEVICE_FOR_STRATEGY = 34;
+    private static final int MSG_IL_SAVE_PREF_DEVICES_FOR_STRATEGY = 33;
+    private static final int MSG_I_SAVE_REMOVE_PREF_DEVICES_FOR_STRATEGY = 34;
 
     private static final int MSG_L_SPEAKERPHONE_CLIENT_DIED = 35;
     private static final int MSG_CHECK_MUTE_MUSIC = 36;
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 02a846e..fbf07cc 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -16,7 +16,6 @@
 package com.android.server.audio;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothAdapter;
@@ -32,7 +31,7 @@
 import android.media.AudioRoutesInfo;
 import android.media.AudioSystem;
 import android.media.IAudioRoutesObserver;
-import android.media.IStrategyPreferredDeviceDispatcher;
+import android.media.IStrategyPreferredDevicesDispatcher;
 import android.media.MediaMetrics;
 import android.os.Binder;
 import android.os.RemoteCallbackList;
@@ -51,6 +50,7 @@
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -137,7 +137,8 @@
     private final ArrayMap<Integer, String> mApmConnectedDevices = new ArrayMap<>();
 
     // List of preferred devices for strategies
-    private final ArrayMap<Integer, AudioDeviceAttributes> mPreferredDevices = new ArrayMap<>();
+    private final ArrayMap<Integer, List<AudioDeviceAttributes>> mPreferredDevices =
+            new ArrayMap<>();
 
     // the wrapper for AudioSystem static methods, allows us to spy AudioSystem
     private final @NonNull AudioSystemAdapter mAudioSystem;
@@ -150,8 +151,8 @@
             new RemoteCallbackList<IAudioRoutesObserver>();
 
     // Monitoring of strategy-preferred device
-    final RemoteCallbackList<IStrategyPreferredDeviceDispatcher> mPrefDevDispatchers =
-            new RemoteCallbackList<IStrategyPreferredDeviceDispatcher>();
+    final RemoteCallbackList<IStrategyPreferredDevicesDispatcher> mPrefDevDispatchers =
+            new RemoteCallbackList<IStrategyPreferredDevicesDispatcher>();
 
     /*package*/ AudioDeviceInventory(@NonNull AudioDeviceBroker broker) {
         mDeviceBroker = broker;
@@ -265,8 +266,9 @@
             }
         }
         synchronized (mPreferredDevices) {
-            mPreferredDevices.forEach((strategy, device) -> {
-                mAudioSystem.setPreferredDeviceForStrategy(strategy, device); });
+            mPreferredDevices.forEach((strategy, devices) -> {
+                mAudioSystem.setDevicesRoleForStrategy(
+                        strategy, AudioSystem.DEVICE_ROLE_PREFERRED, devices); });
         }
     }
 
@@ -600,49 +602,52 @@
         mmi.record();
     }
 
-    /*package*/ void onSaveSetPreferredDevice(int strategy, @NonNull AudioDeviceAttributes device) {
-        mPreferredDevices.put(strategy, device);
-        dispatchPreferredDevice(strategy, device);
+    /*package*/ void onSaveSetPreferredDevices(int strategy,
+                                               @NonNull List<AudioDeviceAttributes> devices) {
+        mPreferredDevices.put(strategy, devices);
+        dispatchPreferredDevice(strategy, devices);
     }
 
-    /*package*/ void onSaveRemovePreferredDevice(int strategy) {
+    /*package*/ void onSaveRemovePreferredDevices(int strategy) {
         mPreferredDevices.remove(strategy);
-        dispatchPreferredDevice(strategy, null);
+        dispatchPreferredDevice(strategy, new ArrayList<AudioDeviceAttributes>());
     }
 
     //------------------------------------------------------------
     //
 
-    /*package*/ int setPreferredDeviceForStrategySync(int strategy,
-                                                      @NonNull AudioDeviceAttributes device) {
+    /*package*/ int setPreferredDevicesForStrategySync(int strategy,
+            @NonNull List<AudioDeviceAttributes> devices) {
         final long identity = Binder.clearCallingIdentity();
-        final int status = mAudioSystem.setPreferredDeviceForStrategy(strategy, device);
+        final int status = mAudioSystem.setDevicesRoleForStrategy(
+                strategy, AudioSystem.DEVICE_ROLE_PREFERRED, devices);
         Binder.restoreCallingIdentity(identity);
 
         if (status == AudioSystem.SUCCESS) {
-            mDeviceBroker.postSaveSetPreferredDeviceForStrategy(strategy, device);
+            mDeviceBroker.postSaveSetPreferredDevicesForStrategy(strategy, devices);
         }
         return status;
     }
 
-    /*package*/ int removePreferredDeviceForStrategySync(int strategy) {
+    /*package*/ int removePreferredDevicesForStrategySync(int strategy) {
         final long identity = Binder.clearCallingIdentity();
-        final int status = mAudioSystem.removePreferredDeviceForStrategy(strategy);
+        final int status = mAudioSystem.removeDevicesRoleForStrategy(
+                strategy, AudioSystem.DEVICE_ROLE_PREFERRED);
         Binder.restoreCallingIdentity(identity);
 
         if (status == AudioSystem.SUCCESS) {
-            mDeviceBroker.postSaveRemovePreferredDeviceForStrategy(strategy);
+            mDeviceBroker.postSaveRemovePreferredDevicesForStrategy(strategy);
         }
         return status;
     }
 
-    /*package*/ void registerStrategyPreferredDeviceDispatcher(
-            @NonNull IStrategyPreferredDeviceDispatcher dispatcher) {
+    /*package*/ void registerStrategyPreferredDevicesDispatcher(
+            @NonNull IStrategyPreferredDevicesDispatcher dispatcher) {
         mPrefDevDispatchers.register(dispatcher);
     }
 
-    /*package*/ void unregisterStrategyPreferredDeviceDispatcher(
-            @NonNull IStrategyPreferredDeviceDispatcher dispatcher) {
+    /*package*/ void unregisterStrategyPreferredDevicesDispatcher(
+            @NonNull IStrategyPreferredDevicesDispatcher dispatcher) {
         mPrefDevDispatchers.unregister(dispatcher);
     }
 
@@ -1288,11 +1293,13 @@
         }
     }
 
-    private void dispatchPreferredDevice(int strategy, @Nullable AudioDeviceAttributes device) {
+    private void dispatchPreferredDevice(int strategy,
+                                         @NonNull List<AudioDeviceAttributes> devices) {
         final int nbDispatchers = mPrefDevDispatchers.beginBroadcast();
         for (int i = 0; i < nbDispatchers; i++) {
             try {
-                mPrefDevDispatchers.getBroadcastItem(i).dispatchPrefDeviceChanged(strategy, device);
+                mPrefDevDispatchers.getBroadcastItem(i).dispatchPrefDevicesChanged(
+                        strategy, devices);
             } catch (RemoteException e) {
             }
         }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 23b0929..0a179e8 100755
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -83,7 +83,7 @@
 import android.media.IPlaybackConfigDispatcher;
 import android.media.IRecordingConfigDispatcher;
 import android.media.IRingtonePlayer;
-import android.media.IStrategyPreferredDeviceDispatcher;
+import android.media.IStrategyPreferredDevicesDispatcher;
 import android.media.IVolumeController;
 import android.media.MediaExtractor;
 import android.media.MediaFormat;
@@ -167,6 +167,7 @@
 import java.util.Set;
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Collectors;
 
 /**
  * The implementation of the audio service for volume, audio focus, device management...
@@ -1777,22 +1778,28 @@
     ///////////////////////////////////////////////////////////////////////////
     // IPC methods
     ///////////////////////////////////////////////////////////////////////////
-    /** @see AudioManager#setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceInfo) */
-    public int setPreferredDeviceForStrategy(int strategy, AudioDeviceAttributes device) {
-        if (device == null) {
+    /**
+     * @see AudioManager#setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAttributes)
+     * @see AudioManager#setPreferredDevicesForStrategy(AudioProductStrategy,
+     *                                                  List<AudioDeviceAttributes>)
+     */
+    public int setPreferredDevicesForStrategy(int strategy, List<AudioDeviceAttributes> devices) {
+        if (devices == null) {
             return AudioSystem.ERROR;
         }
         enforceModifyAudioRoutingPermission();
         final String logString = String.format(
                 "setPreferredDeviceForStrategy u/pid:%d/%d strat:%d dev:%s",
-                Binder.getCallingUid(), Binder.getCallingPid(), strategy, device.toString());
+                Binder.getCallingUid(), Binder.getCallingPid(), strategy,
+                devices.stream().map(e -> e.toString()).collect(Collectors.joining(",")));
         sDeviceLogger.log(new AudioEventLogger.StringEvent(logString).printLog(TAG));
-        if (device.getRole() == AudioDeviceAttributes.ROLE_INPUT) {
+        if (devices.stream().anyMatch(device ->
+                device.getRole() == AudioDeviceAttributes.ROLE_INPUT)) {
             Log.e(TAG, "Unsupported input routing in " + logString);
             return AudioSystem.ERROR;
         }
 
-        final int status = mDeviceBroker.setPreferredDeviceForStrategySync(strategy, device);
+        final int status = mDeviceBroker.setPreferredDevicesForStrategySync(strategy, devices);
         if (status != AudioSystem.SUCCESS) {
             Log.e(TAG, String.format("Error %d in %s)", status, logString));
         }
@@ -1801,53 +1808,61 @@
     }
 
     /** @see AudioManager#removePreferredDeviceForStrategy(AudioProductStrategy) */
-    public int removePreferredDeviceForStrategy(int strategy) {
+    public int removePreferredDevicesForStrategy(int strategy) {
         enforceModifyAudioRoutingPermission();
         final String logString =
                 String.format("removePreferredDeviceForStrategy strat:%d", strategy);
         sDeviceLogger.log(new AudioEventLogger.StringEvent(logString).printLog(TAG));
 
-        final int status = mDeviceBroker.removePreferredDeviceForStrategySync(strategy);
+        final int status = mDeviceBroker.removePreferredDevicesForStrategySync(strategy);
         if (status != AudioSystem.SUCCESS) {
             Log.e(TAG, String.format("Error %d in %s)", status, logString));
         }
         return status;
     }
 
-    /** @see AudioManager#getPreferredDeviceForStrategy(AudioProductStrategy) */
-    public AudioDeviceAttributes getPreferredDeviceForStrategy(int strategy) {
+    /**
+     * @see AudioManager#getPreferredDeviceForStrategy(AudioProductStrategy)
+     * @see AudioManager#getPreferredDevicesForStrategy(AudioProductStrategy)
+     */
+    public List<AudioDeviceAttributes> getPreferredDevicesForStrategy(int strategy) {
         enforceModifyAudioRoutingPermission();
-        AudioDeviceAttributes[] devices = new AudioDeviceAttributes[1];
+        List<AudioDeviceAttributes> devices = new ArrayList<>();
         final long identity = Binder.clearCallingIdentity();
-        final int status = AudioSystem.getPreferredDeviceForStrategy(strategy, devices);
+        final int status = AudioSystem.getDevicesForRoleAndStrategy(
+                strategy, AudioSystem.DEVICE_ROLE_PREFERRED, devices);
         Binder.restoreCallingIdentity(identity);
         if (status != AudioSystem.SUCCESS) {
             Log.e(TAG, String.format("Error %d in getPreferredDeviceForStrategy(%d)",
                     status, strategy));
-            return null;
+            return new ArrayList<AudioDeviceAttributes>();
         } else {
-            return devices[0];
+            return devices;
         }
     }
 
-    /** @see AudioManager#addOnPreferredDeviceForStrategyChangedListener(Executor, AudioManager.OnPreferredDeviceForStrategyChangedListener) */
-    public void registerStrategyPreferredDeviceDispatcher(
-            @Nullable IStrategyPreferredDeviceDispatcher dispatcher) {
+    /** @see AudioManager#addOnPreferredDevicesForStrategyChangedListener(
+     *               Executor, AudioManager.OnPreferredDevicesForStrategyChangedListener)
+     */
+    public void registerStrategyPreferredDevicesDispatcher(
+            @Nullable IStrategyPreferredDevicesDispatcher dispatcher) {
         if (dispatcher == null) {
             return;
         }
         enforceModifyAudioRoutingPermission();
-        mDeviceBroker.registerStrategyPreferredDeviceDispatcher(dispatcher);
+        mDeviceBroker.registerStrategyPreferredDevicesDispatcher(dispatcher);
     }
 
-    /** @see AudioManager#removeOnPreferredDeviceForStrategyChangedListener(AudioManager.OnPreferredDeviceForStrategyChangedListener) */
-    public void unregisterStrategyPreferredDeviceDispatcher(
-            @Nullable IStrategyPreferredDeviceDispatcher dispatcher) {
+    /** @see AudioManager#removeOnPreferredDevicesForStrategyChangedListener(
+     *               AudioManager.OnPreferredDevicesForStrategyChangedListener)
+     */
+    public void unregisterStrategyPreferredDevicesDispatcher(
+            @Nullable IStrategyPreferredDevicesDispatcher dispatcher) {
         if (dispatcher == null) {
             return;
         }
         enforceModifyAudioRoutingPermission();
-        mDeviceBroker.unregisterStrategyPreferredDeviceDispatcher(dispatcher);
+        mDeviceBroker.unregisterStrategyPreferredDevicesDispatcher(dispatcher);
     }
 
     /** @see AudioManager#getDevicesForAttributes(AudioAttributes) */
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index e60243f..a0e1ca7 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -20,6 +20,8 @@
 import android.media.AudioDeviceAttributes;
 import android.media.AudioSystem;
 
+import java.util.List;
+
 /**
  * Provides an adapter to access functionality of the android.media.AudioSystem class for device
  * related functionality.
@@ -77,22 +79,25 @@
     }
 
     /**
-     * Same as {@link AudioSystem#setPreferredDeviceForStrategy(int, AudioDeviceAttributes)}
+     * Same as {@link AudioSystem#setDevicesRoleForStrategy(int, int, List)}
      * @param strategy
-     * @param device
+     * @param role
+     * @param devices
      * @return
      */
-    public int setPreferredDeviceForStrategy(int strategy, @NonNull AudioDeviceAttributes device) {
-        return AudioSystem.setPreferredDeviceForStrategy(strategy, device);
+    public int setDevicesRoleForStrategy(int strategy, int role,
+                                         @NonNull List<AudioDeviceAttributes> devices) {
+        return AudioSystem.setDevicesRoleForStrategy(strategy, role, devices);
     }
 
     /**
-     * Same as {@link AudioSystem#removePreferredDeviceForStrategy(int)}
+     * Same as {@link AudioSystem#removeDevicesRoleForStrategy(int, int)}
      * @param strategy
+     * @param role
      * @return
      */
-    public int removePreferredDeviceForStrategy(int strategy) {
-        return AudioSystem.removePreferredDeviceForStrategy(strategy);
+    public int removeDevicesRoleForStrategy(int strategy, int role) {
+        return AudioSystem.removeDevicesRoleForStrategy(strategy, role);
     }
 
     /**
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index a47904c..54c1790 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -259,17 +259,6 @@
         }
 
         @Override
-        public void resetLockout(int userId, byte[] hardwareAuthToken) throws RemoteException {
-            checkInternalPermission();
-            final long identity = Binder.clearCallingIdentity();
-            try {
-                mBiometricService.resetLockout(userId, hardwareAuthToken);
-            } finally {
-                Binder.restoreCallingIdentity(identity);
-            }
-        }
-
-        @Override
         public long[] getAuthenticatorIds() throws RemoteException {
             // In this method, we're not checking whether the caller is permitted to use face
             // API because current authenticator ID is leaked (in a more contrived way) via Android
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 382bdbb..3e0a40f 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -638,19 +638,6 @@
         }
 
         @Override // Binder call
-        public void resetLockout(int userId, byte[] hardwareAuthToken) {
-            checkInternalPermission();
-
-            try {
-                for (BiometricSensor sensor : mSensors) {
-                    sensor.impl.resetLockout(userId, hardwareAuthToken);
-                }
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Remote exception", e);
-            }
-        }
-
-        @Override // Binder call
         public long[] getAuthenticatorIds(int callingUserId) {
             checkInternalPermission();
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
index ab48fdb..73fc17a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
@@ -45,7 +45,7 @@
     private final PowerManager mPowerManager;
     private final VibrationEffect mSuccessVibrationEffect;
     private final VibrationEffect mErrorVibrationEffect;
-    private boolean mShouldSendErrorToClient;
+    private boolean mShouldSendErrorToClient = true;
     private boolean mAlreadyCancelled;
 
     /**
@@ -85,11 +85,11 @@
         // case (success, failure, or error) is received from the HAL (e.g. versions of fingerprint
         // that do not handle lockout under the HAL. In these cases, ensure that the framework only
         // sends errors once per ClientMonitor.
-        if (!mShouldSendErrorToClient) {
+        if (mShouldSendErrorToClient) {
             logOnError(getContext(), errorCode, vendorCode, getTargetUserId());
             try {
                 if (getListener() != null) {
-                    mShouldSendErrorToClient = true;
+                    mShouldSendErrorToClient = false;
                     getListener().onError(getSensorId(), getCookie(), errorCode, vendorCode);
                 }
             } catch (RemoteException e) {
@@ -98,7 +98,7 @@
         }
 
         if (finish) {
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 
@@ -114,7 +114,7 @@
     }
 
     @Override
-    public void cancelWithoutStarting(@NonNull FinishCallback finishCallback) {
+    public void cancelWithoutStarting(@NonNull Callback callback) {
         final int errorCode = BiometricConstants.BIOMETRIC_ERROR_CANCELED;
         try {
             if (getListener() != null) {
@@ -123,7 +123,7 @@
         } catch (RemoteException e) {
             Slog.w(TAG, "Failed to invoke sendError", e);
         }
-        finishCallback.onClientFinished(this, true /* success */);
+        callback.onClientFinished(this, true /* success */);
     }
 
     /**
@@ -155,7 +155,7 @@
             }
         } catch (RemoteException e) {
             Slog.w(TAG, "Failed to invoke sendAcquired", e);
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index cb2321f..9128359 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -99,6 +99,14 @@
         return getCookie() != 0;
     }
 
+    public long getOperationId() {
+        return mOperationId;
+    }
+
+    public boolean isRestricted() {
+        return mIsRestricted;
+    }
+
     @Override
     protected boolean isCryptoOperation() {
         return mOperationId != 0;
@@ -191,7 +199,7 @@
             }
         } catch (RemoteException e) {
             Slog.e(TAG, "Unable to notify listener, finishing", e);
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 
@@ -211,8 +219,8 @@
      * Start authentication
      */
     @Override
-    public void start(@NonNull FinishCallback finishCallback) {
-        super.start(finishCallback);
+    public void start(@NonNull Callback callback) {
+        super.start(callback);
 
         final @LockoutTracker.LockoutMode int lockoutMode =
                 mLockoutTracker.getLockoutModeForUser(getTargetUserId());
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index 4f37dcc..588e865 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -85,7 +85,7 @@
         static final int STATE_WAITING_FOR_COOKIE = 4;
 
         /**
-         * The {@link ClientMonitor.FinishCallback} has been invoked and the client is finished.
+         * The {@link ClientMonitor.Callback} has been invoked and the client is finished.
          */
         static final int STATE_FINISHED = 5;
 
@@ -99,13 +99,13 @@
         @interface OperationState {}
 
         @NonNull final ClientMonitor<?> clientMonitor;
-        @Nullable final ClientMonitor.FinishCallback clientFinishCallback;
+        @Nullable final ClientMonitor.Callback mClientCallback;
         @OperationState int state;
 
         Operation(@NonNull ClientMonitor<?> clientMonitor,
-                @Nullable ClientMonitor.FinishCallback finishCallback) {
+                @Nullable ClientMonitor.Callback callback) {
             this.clientMonitor = clientMonitor;
-            this.clientFinishCallback = finishCallback;
+            this.mClientCallback = callback;
             state = STATE_WAITING_IN_QUEUE;
         }
 
@@ -133,7 +133,7 @@
         public void run() {
             if (operation.state != Operation.STATE_FINISHED) {
                 Slog.e(tag, "[Watchdog Triggered]: " + operation);
-                operation.clientMonitor.mFinishCallback
+                operation.clientMonitor.mCallback
                         .onClientFinished(operation.clientMonitor, false /* success */);
             }
         }
@@ -175,17 +175,25 @@
     @Nullable private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
     @NonNull private final IBiometricService mBiometricService;
     @NonNull private final Handler mHandler = new Handler(Looper.getMainLooper());
-    @NonNull private final InternalFinishCallback mInternalFinishCallback;
+    @NonNull private final InternalCallback mInternalCallback;
     @NonNull private final Queue<Operation> mPendingOperations;
     @Nullable private Operation mCurrentOperation;
     @NonNull private final ArrayDeque<CrashState> mCrashStates;
 
-    // Internal finish callback, notified when an operation is complete. Notifies the requester
+    // Internal callback, notified when an operation is complete. Notifies the requester
     // that the operation is complete, before performing internal scheduler work (such as
     // starting the next client).
-    private class InternalFinishCallback implements ClientMonitor.FinishCallback {
+    public class InternalCallback implements ClientMonitor.Callback {
         @Override
-        public void onClientFinished(ClientMonitor<?> clientMonitor, boolean success) {
+        public void onClientStarted(@NonNull ClientMonitor<?> clientMonitor) {
+            Slog.d(getTag(), "[Started] " + clientMonitor);
+            if (mCurrentOperation.mClientCallback != null) {
+                mCurrentOperation.mClientCallback.onClientStarted(clientMonitor);
+            }
+        }
+
+        @Override
+        public void onClientFinished(@NonNull ClientMonitor<?> clientMonitor, boolean success) {
             mHandler.post(() -> {
                 if (mCurrentOperation == null) {
                     Slog.e(getTag(), "[Finishing] " + clientMonitor
@@ -203,8 +211,8 @@
                 Slog.d(getTag(), "[Finishing] " + clientMonitor + ", success: " + success);
                 mCurrentOperation.state = Operation.STATE_FINISHED;
 
-                if (mCurrentOperation.clientFinishCallback != null) {
-                    mCurrentOperation.clientFinishCallback.onClientFinished(clientMonitor, success);
+                if (mCurrentOperation.mClientCallback != null) {
+                    mCurrentOperation.mClientCallback.onClientFinished(clientMonitor, success);
                 }
 
                 if (mGestureAvailabilityDispatcher != null) {
@@ -227,7 +235,7 @@
     public BiometricScheduler(@NonNull String tag,
             @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
         mBiometricTag = tag;
-        mInternalFinishCallback = new InternalFinishCallback();
+        mInternalCallback = new InternalCallback();
         mGestureAvailabilityDispatcher = gestureAvailabilityDispatcher;
         mPendingOperations = new ArrayDeque<>();
         mBiometricService = IBiometricService.Stub.asInterface(
@@ -235,6 +243,14 @@
         mCrashStates = new ArrayDeque<>();
     }
 
+    /**
+     * @return A reference to the internal callback that should be invoked whenever the scheduler
+     *         needs to (e.g. client started, client finished).
+     */
+    @NonNull protected InternalCallback getInternalCallback() {
+        return mInternalCallback;
+    }
+
     private String getTag() {
         return BASE_TAG + "/" + mBiometricTag;
     }
@@ -262,7 +278,7 @@
             }
 
             final Interruptable interruptable = (Interruptable) currentClient;
-            interruptable.cancelWithoutStarting(mInternalFinishCallback);
+            interruptable.cancelWithoutStarting(getInternalCallback());
             // Now we wait for the client to send its FinishCallback, which kicks off the next
             // operation.
             return;
@@ -280,7 +296,7 @@
         final boolean shouldStartNow = currentClient.getCookie() == 0;
         if (shouldStartNow) {
             Slog.d(getTag(), "[Starting] " + mCurrentOperation);
-            currentClient.start(mInternalFinishCallback);
+            currentClient.start(getInternalCallback());
             mCurrentOperation.state = Operation.STATE_STARTED;
         } else {
             try {
@@ -324,7 +340,7 @@
 
         Slog.d(getTag(), "[Starting] Prepared client: " + mCurrentOperation);
         mCurrentOperation.state = Operation.STATE_STARTED;
-        mCurrentOperation.clientMonitor.start(mInternalFinishCallback);
+        mCurrentOperation.clientMonitor.start(getInternalCallback());
     }
 
     /**
@@ -340,11 +356,11 @@
      * Adds a {@link ClientMonitor} to the pending queue
      *
      * @param clientMonitor        operation to be scheduled
-     * @param clientFinishCallback optional callback, invoked when the client is finished, but
+     * @param clientCallback optional callback, invoked when the client is finished, but
      *                             before it has been removed from the queue.
      */
     public void scheduleClientMonitor(@NonNull ClientMonitor<?> clientMonitor,
-            @Nullable ClientMonitor.FinishCallback clientFinishCallback) {
+            @Nullable ClientMonitor.Callback clientCallback) {
         // Mark any interruptable pending clients as canceling. Once they reach the head of the
         // queue, the scheduler will send ERROR_CANCELED and skip the operation.
         for (Operation operation : mPendingOperations) {
@@ -356,7 +372,7 @@
             }
         }
 
-        mPendingOperations.add(new Operation(clientMonitor, clientFinishCallback));
+        mPendingOperations.add(new Operation(clientMonitor, clientCallback));
         Slog.d(getTag(), "[Added] " + clientMonitor
                 + ", new queue size: " + mPendingOperations.size());
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/ClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/ClientMonitor.java
index 8b27781..dec40e3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/ClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/ClientMonitor.java
@@ -42,7 +42,15 @@
     /**
      * Interface that ClientMonitor holders should use to receive callbacks.
      */
-    public interface FinishCallback {
+    public interface Callback {
+        /**
+         * Invoked when the ClientMonitor operation has been started (e.g. reached the head of
+         * the queue and becomes the current operation).
+         *
+         * @param clientMonitor Reference of the ClientMonitor that is starting.
+         */
+        default void onClientStarted(@NonNull ClientMonitor<?> clientMonitor) {}
+
         /**
          * Invoked when the ClientMonitor operation is complete. This abstracts away asynchronous
          * (i.e. Authenticate, Enroll, Enumerate, Remove) and synchronous (i.e. generateChallenge,
@@ -52,7 +60,7 @@
          * @param clientMonitor Reference of the ClientMonitor that finished.
          * @param success True if the operation completed successfully.
          */
-        void onClientFinished(ClientMonitor<?> clientMonitor, boolean success);
+        default void onClientFinished(@NonNull ClientMonitor<?> clientMonitor, boolean success) {}
     }
 
     /**
@@ -79,7 +87,7 @@
     private final int mCookie;
     boolean mAlreadyDone;
 
-    @NonNull protected FinishCallback mFinishCallback;
+    @NonNull protected Callback mCallback;
 
     /**
      * @param context    system_server context
@@ -125,17 +133,18 @@
     /**
      * Invoked if the scheduler is unable to start the ClientMonitor (for example the HAL is null).
      * If such a problem is detected, the scheduler will not invoke
-     * {@link #start(FinishCallback)}.
+     * {@link #start(Callback)}.
      */
     public abstract void unableToStart();
 
     /**
      * Starts the ClientMonitor's lifecycle. Invokes {@link #startHalOperation()} when internal book
      * keeping is complete.
-     * @param finishCallback invoked when the operation is complete (succeeds, fails, etc)
+     * @param callback invoked when the operation is complete (succeeds, fails, etc)
      */
-    public void start(@NonNull FinishCallback finishCallback) {
-        mFinishCallback = finishCallback;
+    public void start(@NonNull Callback callback) {
+        mCallback = callback;
+        mCallback.onClientStarted(this);
     }
 
     /**
diff --git a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
index 3cef666..cb7db92 100644
--- a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
@@ -128,11 +128,11 @@
         }
     }
 
-    public void onChallengeGenerated(long challenge) throws RemoteException {
+    public void onChallengeGenerated(int sensorId, long challenge) throws RemoteException {
         if (mFaceServiceReceiver != null) {
-            mFaceServiceReceiver.onChallengeGenerated(challenge);
+            mFaceServiceReceiver.onChallengeGenerated(sensorId, challenge);
         } else if (mFingerprintServiceReceiver != null) {
-            mFingerprintServiceReceiver.onChallengeGenerated(challenge);
+            mFingerprintServiceReceiver.onChallengeGenerated(sensorId, challenge);
         }
     }
 
@@ -142,10 +142,21 @@
         }
     }
 
-    public void onFeatureGet(boolean success, int feature, boolean value)
-            throws RemoteException {
+    public void onFeatureGet(boolean success, int feature, boolean value) throws RemoteException {
         if (mFaceServiceReceiver != null) {
             mFaceServiceReceiver.onFeatureGet(success, feature, value);
         }
     }
+
+    public void onChallengeInterrupted(int sensorId) throws RemoteException {
+        if (mFaceServiceReceiver != null) {
+            mFaceServiceReceiver.onChallengeInterrupted(sensorId);
+        }
+    }
+
+    public void onChallengeInterruptFinished(int sensorId) throws RemoteException {
+        if (mFaceServiceReceiver != null) {
+            mFaceServiceReceiver.onChallengeInterruptFinished(sensorId);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
index add5829..8bf9680 100644
--- a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
@@ -77,18 +77,18 @@
             mBiometricUtils.addBiometricForUser(getContext(), getTargetUserId(), identifier);
             logOnEnrolled(getTargetUserId(), System.currentTimeMillis() - mEnrollmentStartTimeMs,
                     true /* enrollSuccessful */);
-            mFinishCallback.onClientFinished(this, true /* success */);
+            mCallback.onClientFinished(this, true /* success */);
         }
         notifyUserActivity();
     }
 
     @Override
-    public void start(@NonNull FinishCallback finishCallback) {
-        super.start(finishCallback);
+    public void start(@NonNull Callback callback) {
+        super.start(callback);
 
         if (hasReachedEnrollmentLimit()) {
             Slog.e(TAG, "Reached enrollment limit");
-            finishCallback.onClientFinished(this, false /* success */);
+            callback.onClientFinished(this, false /* success */);
             return;
         }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java
index dad5cad..92c498c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java
@@ -40,23 +40,23 @@
     @Override
     public void unableToStart() {
         try {
-            getListener().onChallengeGenerated(0L);
+            getListener().onChallengeGenerated(getSensorId(), 0L);
         } catch (RemoteException e) {
             Slog.e(TAG, "Unable to send error", e);
         }
     }
 
     @Override
-    public void start(@NonNull FinishCallback finishCallback) {
-        super.start(finishCallback);
+    public void start(@NonNull Callback callback) {
+        super.start(callback);
 
         startHalOperation();
         try {
-            getListener().onChallengeGenerated(mChallenge);
-            mFinishCallback.onClientFinished(this, true /* success */);
+            getListener().onChallengeGenerated(getSensorId(), mChallenge);
+            mCallback.onClientFinished(this, true /* success */);
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception", e);
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
index 6d7b0fd..5c08bce 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
@@ -62,29 +62,35 @@
     private final List<S> mEnrolledList;
     private ClientMonitor<T> mCurrentTask;
 
-    private final FinishCallback mEnumerateFinishCallback = (clientMonitor, success) -> {
-        final List<BiometricAuthenticator.Identifier> unknownHALTemplates =
-                ((InternalEnumerateClient<T>) mCurrentTask).getUnknownHALTemplates();
+    private final Callback mEnumerateCallback = new Callback() {
+        @Override
+        public void onClientFinished(@NonNull ClientMonitor<?> clientMonitor, boolean success) {
+            final List<BiometricAuthenticator.Identifier> unknownHALTemplates =
+                    ((InternalEnumerateClient<T>) mCurrentTask).getUnknownHALTemplates();
 
-        if (!unknownHALTemplates.isEmpty()) {
-            Slog.w(TAG, "Adding " + unknownHALTemplates.size() + " templates for deletion");
-        }
-        for (BiometricAuthenticator.Identifier unknownHALTemplate : unknownHALTemplates) {
-            mUnknownHALTemplates.add(new UserTemplate(unknownHALTemplate,
-                    mCurrentTask.getTargetUserId()));
-        }
+            if (!unknownHALTemplates.isEmpty()) {
+                Slog.w(TAG, "Adding " + unknownHALTemplates.size() + " templates for deletion");
+            }
+            for (BiometricAuthenticator.Identifier unknownHALTemplate : unknownHALTemplates) {
+                mUnknownHALTemplates.add(new UserTemplate(unknownHALTemplate,
+                        mCurrentTask.getTargetUserId()));
+            }
 
-        if (mUnknownHALTemplates.isEmpty()) {
-            // No unknown HAL templates. Unknown framework templates are already cleaned up in
-            // InternalEnumerateClient. Finish this client.
-            mFinishCallback.onClientFinished(this, success);
-        } else {
-            startCleanupUnknownHalTemplates();
+            if (mUnknownHALTemplates.isEmpty()) {
+                // No unknown HAL templates. Unknown framework templates are already cleaned up in
+                // InternalEnumerateClient. Finish this client.
+                mCallback.onClientFinished(InternalCleanupClient.this, success);
+            } else {
+                startCleanupUnknownHalTemplates();
+            }
         }
     };
 
-    private final FinishCallback mRemoveFinishCallback = (clientMonitor, success) -> {
-        mFinishCallback.onClientFinished(this, success);
+    private final Callback mRemoveCallback = new Callback() {
+        @Override
+        public void onClientFinished(@NonNull ClientMonitor<?> clientMonitor, boolean success) {
+            mCallback.onClientFinished(InternalCleanupClient.this, success);
+        }
     };
 
     protected abstract InternalEnumerateClient<T> getEnumerateClient(Context context,
@@ -116,7 +122,7 @@
         FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED,
                 mStatsModality,
                 BiometricsProtoEnums.ISSUE_UNKNOWN_TEMPLATE_ENROLLED_HAL);
-        mCurrentTask.start(mRemoveFinishCallback);
+        mCurrentTask.start(mRemoveCallback);
     }
 
     @Override
@@ -125,13 +131,13 @@
     }
 
     @Override
-    public void start(@NonNull FinishCallback finishCallback) {
-        super.start(finishCallback);
+    public void start(@NonNull Callback callback) {
+        super.start(callback);
 
         // Start enumeration. Removal will start if necessary, when enumeration is completed.
         mCurrentTask = getEnumerateClient(getContext(), mLazyDaemon, getToken(), getTargetUserId(),
                 getOwnerString(), mEnrolledList, mBiometricUtils, getSensorId());
-        mCurrentTask.start(mEnumerateFinishCallback);
+        mCurrentTask.start(mEnumerateCallback);
     }
 
     @Override
@@ -147,7 +153,7 @@
                     + mCurrentTask.getClass().getSimpleName());
             return;
         }
-        ((RemovalClient) mCurrentTask).onRemoved(identifier, remaining);
+        ((RemovalClient<T>) mCurrentTask).onRemoved(identifier, remaining);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
index 3f73cd5..e07f712 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
@@ -62,7 +62,7 @@
         handleEnumeratedTemplate(identifier);
         if (remaining == 0) {
             doTemplateCleanup();
-            mFinishCallback.onClientFinished(this, true /* success */);
+            mCallback.onClientFinished(this, true /* success */);
         }
     }
 
@@ -72,8 +72,8 @@
     }
 
     @Override
-    public void start(@NonNull FinishCallback finishCallback) {
-        super.start(finishCallback);
+    public void start(@NonNull Callback callback) {
+        super.start(callback);
 
         // The biometric template ids will be removed when we get confirmation from the HAL
         startHalOperation();
diff --git a/services/core/java/com/android/server/biometrics/sensors/Interruptable.java b/services/core/java/com/android/server/biometrics/sensors/Interruptable.java
index 35d9177..28e0117 100644
--- a/services/core/java/com/android/server/biometrics/sensors/Interruptable.java
+++ b/services/core/java/com/android/server/biometrics/sensors/Interruptable.java
@@ -37,10 +37,10 @@
 
     /**
      * Notifies the client that it needs to finish before
-     * {@link ClientMonitor#start(ClientMonitor.FinishCallback)} was invoked. This usually happens
+     * {@link ClientMonitor#start(ClientMonitor.Callback)} was invoked. This usually happens
      * if the client is still waiting in the pending queue and got notified that a subsequent
      * operation is preempting it.
-     * @param finishCallback invoked when the operation is completed.
+     * @param callback invoked when the operation is completed.
      */
-    void cancelWithoutStarting(@NonNull ClientMonitor.FinishCallback finishCallback);
+    void cancelWithoutStarting(@NonNull ClientMonitor.Callback callback);
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/LoggableMonitor.java b/services/core/java/com/android/server/biometrics/sensors/LoggableMonitor.java
index 1a4216f..d85ab25 100644
--- a/services/core/java/com/android/server/biometrics/sensors/LoggableMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/LoggableMonitor.java
@@ -58,6 +58,10 @@
         mStatsClient = statsClient;
     }
 
+    public int getStatsClient() {
+        return mStatsClient;
+    }
+
     private boolean isAnyFieldUnknown() {
         return mStatsModality == BiometricsProtoEnums.MODALITY_UNKNOWN
                 || mStatsAction == BiometricsProtoEnums.ACTION_UNKNOWN
diff --git a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
index 1c49bcdb..1348f79 100644
--- a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
@@ -55,8 +55,8 @@
     }
 
     @Override
-    public void start(@NonNull FinishCallback finishCallback) {
-        super.start(finishCallback);
+    public void start(@NonNull Callback callback) {
+        super.start(callback);
 
         // The biometric template ids will be removed when we get confirmation from the HAL
         startHalOperation();
@@ -85,7 +85,7 @@
                 // cleanup).
                 mAuthenticatorIds.put(getTargetUserId(), 0L);
             }
-            mFinishCallback.onClientFinished(this, true /* success */);
+            mCallback.onClientFinished(this, true /* success */);
         }
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java
index b78ee49..5deb8fa 100644
--- a/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java
@@ -36,10 +36,10 @@
     }
 
     @Override
-    public void start(@NonNull FinishCallback finishCallback) {
-        super.start(finishCallback);
+    public void start(@NonNull Callback callback) {
+        super.start(callback);
 
         startHalOperation();
-        mFinishCallback.onClientFinished(this, true /* success */);
+        mCallback.onClientFinished(this, true /* success */);
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/Face10.java
index 6c57208..32bb2db 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/Face10.java
@@ -29,6 +29,7 @@
 import android.hardware.biometrics.face.V1_0.IBiometricsFace;
 import android.hardware.biometrics.face.V1_0.IBiometricsFaceClientCallback;
 import android.hardware.face.Face;
+import android.hardware.face.FaceManager;
 import android.hardware.face.FaceSensorProperties;
 import android.hardware.face.IFaceServiceReceiver;
 import android.os.Build;
@@ -44,6 +45,7 @@
 import android.provider.Settings;
 import android.util.Slog;
 
+import com.android.internal.R;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.sensors.AcquisitionClient;
@@ -98,6 +100,11 @@
 
     @Nullable private IBiometricsFace mDaemon;
     private int mCurrentUserId = UserHandle.USER_NULL;
+    // If a challenge is generated, keep track of its owner. Since IBiometricsFace@1.0 only
+    // supports a single in-flight challenge, we must notify the interrupted owner that its
+    // challenge is no longer valid. The interrupted owner will be notified when the interrupter
+    // has finished.
+    @Nullable private FaceGenerateChallengeClient mCurrentChallengeOwner;
 
     private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() {
         @Override
@@ -269,7 +276,10 @@
 
     Face10(@NonNull Context context, int sensorId,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
-        mFaceSensorProperties = new FaceSensorProperties(sensorId, false /* supportsFaceDetect */);
+        final boolean supportsSelfIllumination = context.getResources()
+                .getBoolean(R.bool.config_faceAuthSupportsSelfIllumination);
+        mFaceSensorProperties = new FaceSensorProperties(sensorId, false /* supportsFaceDetect */,
+                supportsSelfIllumination);
         mContext = context;
         mSensorId = sensorId;
         mScheduler = new BiometricScheduler(TAG, null /* gestureAvailabilityTracker */);
@@ -394,9 +404,12 @@
         final FaceUpdateActiveUserClient client = new FaceUpdateActiveUserClient(mContext,
                 mLazyDaemon, targetUserId, mContext.getOpPackageName(), mSensorId, mCurrentUserId,
                 hasEnrolled, mAuthenticatorIds);
-        mScheduler.scheduleClientMonitor(client, (clientMonitor, success) -> {
-            if (success) {
-                mCurrentUserId = targetUserId;
+        mScheduler.scheduleClientMonitor(client, new ClientMonitor.Callback() {
+            @Override
+            public void onClientFinished(@NonNull ClientMonitor<?> clientMonitor, boolean success) {
+                if (success) {
+                    mCurrentUserId = targetUserId;
+                }
             }
         });
     }
@@ -456,21 +469,87 @@
         });
     }
 
+    /**
+     * {@link IBiometricsFace} only supports a single in-flight challenge. In cases where two
+     * callers both need challenges (e.g. resetLockout right before enrollment), we need to ensure
+     * that either:
+     * 1) generateChallenge/operation/revokeChallenge is complete before the next generateChallenge
+     *    is processed by the scheduler, or
+     * 2) the generateChallenge callback provides a mechanism for notifying the caller that its
+     *    challenge has been invalidated by a subsequent caller, as well as a mechanism for
+     *    notifying the previous caller that the interrupting operation is complete (e.g. the
+     *    interrupting client's challenge has been revoked, so that the interrupted client can
+     *    start retry logic if necessary). See
+     *    {@link FaceManager.GenerateChallengeCallback#onChallengeInterruptFinished(int)}
+     * The only case of conflicting challenges is currently resetLockout --> enroll. So, the second
+     * option seems better as it prioritizes the new operation, which is user-facing.
+     */
     void scheduleGenerateChallenge(@NonNull IBinder token, @NonNull IFaceServiceReceiver receiver,
             @NonNull String opPackageName) {
         mHandler.post(() -> {
+            if (mCurrentChallengeOwner != null) {
+                Slog.w(TAG, "Current challenge owner: " + mCurrentChallengeOwner
+                        + ", interrupted by: " + opPackageName);
+                try {
+                    mCurrentChallengeOwner.getListener().onChallengeInterrupted(mSensorId);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Unable to notify challenge interrupted", e);
+                }
+            }
+
             final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext,
                     mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), opPackageName,
-                    mSensorId);
-            mScheduler.scheduleClientMonitor(client);
+                    mSensorId, mCurrentChallengeOwner);
+            mScheduler.scheduleClientMonitor(client, new ClientMonitor.Callback() {
+                @Override
+                public void onClientStarted(@NonNull ClientMonitor<?> clientMonitor) {
+                    if (client != clientMonitor) {
+                        Slog.e(TAG, "scheduleGenerateChallenge, mismatched client."
+                                + " Expecting: " + client + ", received: " + clientMonitor);
+                        return;
+                    }
+                    Slog.d(TAG, "Current challenge owner: " + client);
+                    mCurrentChallengeOwner = client;
+                }
+            });
         });
     }
 
     void scheduleRevokeChallenge(@NonNull IBinder token, @NonNull String owner) {
         mHandler.post(() -> {
+            if (!mCurrentChallengeOwner.getOwnerString().contentEquals(owner)) {
+                Slog.e(TAG, "scheduleRevokeChallenge, package: " + owner
+                        + " attempting to revoke challenge owned by: "
+                        + mCurrentChallengeOwner.getOwnerString());
+                return;
+            }
+
             final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext,
                     mLazyDaemon, token, owner, mSensorId);
-            mScheduler.scheduleClientMonitor(client);
+            mScheduler.scheduleClientMonitor(client, new ClientMonitor.Callback() {
+                @Override
+                public void onClientFinished(@NonNull ClientMonitor<?> clientMonitor,
+                        boolean success) {
+                    if (client != clientMonitor) {
+                        Slog.e(TAG, "scheduleRevokeChallenge, mismatched client."
+                                + "Expecting: " + client + ", received: " + clientMonitor);
+                        return;
+                    }
+
+                    final FaceGenerateChallengeClient previousChallengeOwner =
+                            mCurrentChallengeOwner.getInterruptedClient();
+                    mCurrentChallengeOwner = null;
+                    Slog.d(TAG, "Previous challenge owner: " + previousChallengeOwner);
+                    if (previousChallengeOwner != null) {
+                        try {
+                            previousChallengeOwner.getListener()
+                                    .onChallengeInterruptFinished(mSensorId);
+                        } catch (RemoteException e) {
+                            Slog.e(TAG, "Unable to notify interrupt finished", e);
+                        }
+                    }
+                }
+            });
         });
     }
 
@@ -488,12 +567,16 @@
                     opPackageName, FaceUtils.getInstance(), disabledFeatures, ENROLL_TIMEOUT_SEC,
                     surfaceHandle, mSensorId);
 
-            mScheduler.scheduleClientMonitor(client, ((clientMonitor, success) -> {
-                if (success) {
-                    // Update authenticatorIds
-                    scheduleUpdateActiveUserWithoutHandler(client.getTargetUserId());
+            mScheduler.scheduleClientMonitor(client, new ClientMonitor.Callback() {
+                @Override
+                public void onClientFinished(@NonNull ClientMonitor<?> clientMonitor,
+                        boolean success) {
+                    if (success) {
+                        // Update authenticatorIds
+                        scheduleUpdateActiveUserWithoutHandler(client.getTargetUserId());
+                    }
                 }
-            }));
+            });
         });
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticationClient.java
index 21bda74..892d6a4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticationClient.java
@@ -92,7 +92,7 @@
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception when requesting auth", e);
             onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 
@@ -103,7 +103,7 @@
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception when requesting cancel", e);
             onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 
@@ -131,7 +131,7 @@
         // 1) Authenticated == true
         // 2) Error occurred
         // 3) Authenticated == false
-        mFinishCallback.onClientFinished(this, true /* success */);
+        mCallback.onClientFinished(this, true /* success */);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
index 33244b8..bbee6cd 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
@@ -75,11 +75,6 @@
     }
 
     @Override
-    public void resetLockout(int userId, byte[] hardwareAuthToken) throws RemoteException {
-        mFaceService.resetLockout(userId, hardwareAuthToken);
-    }
-
-    @Override
     public long getAuthenticatorId(int callingUserId) throws RemoteException {
         return mFaceService.getAuthenticatorId(callingUserId);
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceEnrollClient.java
index 52a8226..3e843ca 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceEnrollClient.java
@@ -116,12 +116,12 @@
             }
             if (status != Status.OK) {
                 onError(BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS, 0 /* vendorCode */);
-                mFinishCallback.onClientFinished(this, false /* success */);
+                mCallback.onClientFinished(this, false /* success */);
             }
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception when requesting enroll", e);
             onError(BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS, 0 /* vendorCode */);
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 
@@ -132,7 +132,7 @@
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception when requesting cancel", e);
             onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceGenerateChallengeClient.java
index 67f2712..ba401f2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceGenerateChallengeClient.java
@@ -17,12 +17,14 @@
 package com.android.server.biometrics.sensors.face;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.hardware.biometrics.face.V1_0.IBiometricsFace;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.sensors.ClientMonitor;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.GenerateChallengeClient;
 
@@ -36,10 +38,22 @@
     private static final String TAG = "FaceGenerateChallengeClient";
     private static final int CHALLENGE_TIMEOUT_SEC = 600; // 10 minutes
 
+    // If `this` FaceGenerateChallengeClient was invoked while an existing in-flight challenge
+    // was not revoked yet, store a reference to the interrupted client here. Notify the interrupted
+    // client when `this` challenge is revoked.
+    @Nullable private final FaceGenerateChallengeClient mInterruptedClient;
+
     FaceGenerateChallengeClient(@NonNull Context context,
             @NonNull LazyDaemon<IBiometricsFace> lazyDaemon, @NonNull IBinder token,
-            @NonNull ClientMonitorCallbackConverter listener, @NonNull String owner, int sensorId) {
+            @NonNull ClientMonitorCallbackConverter listener, @NonNull String owner, int sensorId,
+            @Nullable FaceGenerateChallengeClient interruptedClient) {
         super(context, lazyDaemon, token, listener, owner, sensorId);
+        mInterruptedClient = interruptedClient;
+    }
+
+    @Nullable
+    public FaceGenerateChallengeClient getInterruptedClient() {
+        return mInterruptedClient;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceGetFeatureClient.java
index ce57cb7..8c7b99d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceGetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceGetFeatureClient.java
@@ -61,8 +61,8 @@
     }
 
     @Override
-    public void start(@NonNull FinishCallback finishCallback) {
-        super.start(finishCallback);
+    public void start(@NonNull Callback callback) {
+        super.start(callback);
         startHalOperation();
     }
 
@@ -71,10 +71,10 @@
         try {
             final OptionalBool result = getFreshDaemon().getFeature(mFeature, mFaceId);
             getListener().onFeatureGet(result.status == Status.OK, mFeature, result.value);
-            mFinishCallback.onClientFinished(this, true /* success */);
+            mCallback.onClientFinished(this, true /* success */);
         } catch (RemoteException e) {
             Slog.e(TAG, "Unable to getFeature", e);
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceInternalEnumerateClient.java
index f25242e..32d4832 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceInternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceInternalEnumerateClient.java
@@ -52,7 +52,7 @@
             getFreshDaemon().enumerate();
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception when requesting enumerate", e);
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceRemovalClient.java
index 00d5f50..dde5aba 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceRemovalClient.java
@@ -51,7 +51,7 @@
             getFreshDaemon().remove(mBiometricId);
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception when requesting remove", e);
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceResetLockoutClient.java
index 69070da..f4324be 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceResetLockoutClient.java
@@ -56,8 +56,8 @@
     }
 
     @Override
-    public void start(@NonNull FinishCallback finishCallback) {
-        super.start(finishCallback);
+    public void start(@NonNull Callback callback) {
+        super.start(callback);
         startHalOperation();
     }
 
@@ -65,10 +65,10 @@
     protected void startHalOperation() {
         try {
             getFreshDaemon().resetLockout(mHardwareAuthToken);
-            mFinishCallback.onClientFinished(this, true /* success */);
+            mCallback.onClientFinished(this, true /* success */);
         } catch (RemoteException e) {
             Slog.e(TAG, "Unable to reset lockout", e);
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index b832a09..b689106 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -80,16 +80,27 @@
         }
 
         @Override // Binder call
-        public void generateChallenge(IBinder token, IFaceServiceReceiver receiver,
+        public void generateChallenge(IBinder token, int sensorId, IFaceServiceReceiver receiver,
                 String opPackageName) {
             Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
-            mFace10.scheduleGenerateChallenge(token, receiver, opPackageName);
+            if (sensorId == mFace10.getFaceSensorProperties().sensorId) {
+                mFace10.scheduleGenerateChallenge(token, receiver, opPackageName);
+                return;
+            }
+
+            Slog.w(TAG, "No matching sensor for generateChallenge, sensorId: " + sensorId);
         }
 
         @Override // Binder call
-        public void revokeChallenge(IBinder token, String owner) {
+        public void revokeChallenge(IBinder token, int sensorId, String owner) {
             Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
-            mFace10.scheduleRevokeChallenge(token, owner);
+
+            if (sensorId == mFace10.getFaceSensorProperties().sensorId) {
+                mFace10.scheduleRevokeChallenge(token, owner);
+                return;
+            }
+
+            Slog.w(TAG, "No matching sensor for revokeChallenge, sensorId: " + sensorId);
         }
 
         @Override // Binder call
@@ -267,9 +278,16 @@
         }
 
         @Override // Binder call
-        public void resetLockout(int userId, byte[] hardwareAuthToken) {
+        public void resetLockout(IBinder token, int sensorId, int userId, byte[] hardwareAuthToken,
+                String opPackageName) {
             Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
-            mFace10.scheduleResetLockout(userId, hardwareAuthToken);
+
+            if (sensorId == mFace10.getFaceSensorProperties().sensorId) {
+                mFace10.scheduleResetLockout(userId, hardwareAuthToken);
+                return;
+            }
+
+            Slog.w(TAG, "No matching sensor for resetLockout, sensorId: " + sensorId);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceSetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceSetFeatureClient.java
index e7d041a..94abb7f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceSetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceSetFeatureClient.java
@@ -70,8 +70,8 @@
     }
 
     @Override
-    public void start(@NonNull FinishCallback finishCallback) {
-        super.start(finishCallback);
+    public void start(@NonNull Callback callback) {
+        super.start(callback);
 
         startHalOperation();
     }
@@ -82,10 +82,10 @@
             final int result = getFreshDaemon()
                     .setFeature(mFeature, mEnabled, mHardwareAuthToken, mFaceId);
             getListener().onFeatureSet(result == Status.OK, mFeature);
-            mFinishCallback.onClientFinished(this, true /* success */);
+            mCallback.onClientFinished(this, true /* success */);
         } catch (RemoteException e) {
             Slog.e(TAG, "Unable to set feature: " + mFeature + " to enabled: " + mEnabled, e);
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceUpdateActiveUserClient.java
index bcf304e..05b176d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceUpdateActiveUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceUpdateActiveUserClient.java
@@ -50,8 +50,8 @@
     }
 
     @Override
-    public void start(@NonNull FinishCallback finishCallback) {
-        super.start(finishCallback);
+    public void start(@NonNull Callback callback) {
+        super.start(callback);
 
         if (mCurrentUserId == getTargetUserId()) {
             Slog.d(TAG, "Already user: " + mCurrentUserId + ", refreshing authenticatorId");
@@ -61,7 +61,7 @@
             } catch (RemoteException e) {
                 Slog.e(TAG, "Unable to refresh authenticatorId", e);
             }
-            finishCallback.onClientFinished(this, true /* success */);
+            callback.onClientFinished(this, true /* success */);
             return;
         }
 
@@ -79,16 +79,16 @@
                 FACE_DATA_DIR);
         if (!storePath.exists()) {
             Slog.e(TAG, "vold has not created the directory?");
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
             return;
         }
 
         try {
             getFreshDaemon().setActiveUser(getTargetUserId(), storePath.getAbsolutePath());
-            mFinishCallback.onClientFinished(this, true /* success */);
+            mCallback.onClientFinished(this, true /* success */);
         } catch (RemoteException e) {
             Slog.e(TAG, "Failed to setActiveUser: " + e);
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21.java
index dad0386..c5c2822 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21.java
@@ -84,7 +84,7 @@
     private static final String TAG = "Fingerprint21";
     private static final int ENROLL_TIMEOUT_SEC = 60;
 
-    private final Context mContext;
+    final Context mContext;
     private final IActivityTaskManager mActivityTaskManager;
     private final FingerprintSensorProperties mSensorProperties;
     private final BiometricScheduler mScheduler;
@@ -96,6 +96,7 @@
     private final Map<Integer, Long> mAuthenticatorIds;
 
     @Nullable private IBiometricsFingerprint mDaemon;
+    @NonNull private final HalResultController mHalResultController;
     @Nullable private IUdfpsOverlayController mUdfpsOverlayController;
     private int mCurrentUserId = UserHandle.USER_NULL;
 
@@ -146,15 +147,37 @@
         }
     };
 
-    private final IBiometricsFingerprintClientCallback mDaemonCallback =
-            new IBiometricsFingerprintClientCallback.Stub() {
+    public static class HalResultController extends IBiometricsFingerprintClientCallback.Stub {
+
+        /**
+         * Interface to sends results to the HalResultController's owner.
+         */
+        public interface Callback {
+            /**
+             * Invoked when the HAL sends ERROR_HW_UNAVAILABLE.
+             */
+            void onHardwareUnavailable();
+        }
+
+        @NonNull private final Context mContext;
+        @NonNull final Handler mHandler;
+        @NonNull final BiometricScheduler mScheduler;
+        @Nullable private Callback mCallback;
+
+        HalResultController(@NonNull Context context, @NonNull Handler handler,
+                @NonNull BiometricScheduler scheduler) {
+            mContext = context;
+            mHandler = handler;
+            mScheduler = scheduler;
+        }
+
+        public void setCallback(@Nullable Callback callback) {
+            mCallback = callback;
+        }
+
         @Override
         public void onEnrollResult(long deviceId, int fingerId, int groupId, int remaining) {
             mHandler.post(() -> {
-                final CharSequence name = FingerprintUtils.getInstance()
-                        .getUniqueName(mContext, mCurrentUserId);
-                final Fingerprint fingerprint = new Fingerprint(name, groupId, fingerId, deviceId);
-
                 final ClientMonitor<?> client = mScheduler.getCurrentClient();
                 if (!(client instanceof FingerprintEnrollClient)) {
                     Slog.e(TAG, "onEnrollResult for non-enroll client: "
@@ -162,6 +185,11 @@
                     return;
                 }
 
+                final int currentUserId = client.getTargetUserId();
+                final CharSequence name = FingerprintUtils.getInstance()
+                        .getUniqueName(mContext, currentUserId);
+                final Fingerprint fingerprint = new Fingerprint(name, groupId, fingerId, deviceId);
+
                 final FingerprintEnrollClient enrollClient = (FingerprintEnrollClient) client;
                 enrollClient.onEnrollResult(fingerprint, remaining);
             });
@@ -224,8 +252,9 @@
 
                 if (error == BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE) {
                     Slog.e(TAG, "Got ERROR_HW_UNAVAILABLE");
-                    mDaemon = null;
-                    mCurrentUserId = UserHandle.USER_NULL;
+                    if (mCallback != null) {
+                        mCallback.onHardwareUnavailable();
+                    }
                 }
             });
         }
@@ -262,20 +291,27 @@
                 enumerateConsumer.onEnumerationResult(fp, remaining);
             });
         }
-    };
+    }
 
-    Fingerprint21(@NonNull Context context, int sensorId,
+    Fingerprint21(@NonNull Context context, @NonNull BiometricScheduler scheduler,
+            @NonNull Handler handler, int sensorId,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
+            @NonNull HalResultController controller) {
         mContext = context;
+        mScheduler = scheduler;
+        mHandler = handler;
         mActivityTaskManager = ActivityTaskManager.getService();
-        mHandler = new Handler(Looper.getMainLooper());
+
         mTaskStackListener = new BiometricTaskStackListener();
         mAuthenticatorIds = Collections.synchronizedMap(new HashMap<>());
         mLazyDaemon = Fingerprint21.this::getDaemon;
         mLockoutResetDispatcher = lockoutResetDispatcher;
         mLockoutTracker = new LockoutFrameworkImpl(context, mLockoutResetCallback);
-        mScheduler = new BiometricScheduler(TAG, gestureAvailabilityDispatcher);
+        mHalResultController = controller;
+        mHalResultController.setCallback(() -> {
+            mDaemon = null;
+            mCurrentUserId = UserHandle.USER_NULL;
+        });
 
         try {
             ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG);
@@ -300,7 +336,21 @@
         final @FingerprintSensorProperties.SensorType int sensorType =
                 isUdfps ? FingerprintSensorProperties.TYPE_UDFPS
                         : FingerprintSensorProperties.TYPE_REAR;
-        mSensorProperties = new FingerprintSensorProperties(sensorId, sensorType);
+        // resetLockout is controlled by the framework, so hardwareAuthToken is not required
+        final boolean resetLockoutRequiresHardwareAuthToken = false;
+        mSensorProperties = new FingerprintSensorProperties(sensorId, sensorType,
+                resetLockoutRequiresHardwareAuthToken);
+    }
+
+    static Fingerprint21 newInstance(@NonNull Context context, int sensorId,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
+        final Handler handler = new Handler(Looper.getMainLooper());
+        final BiometricScheduler scheduler =
+                new BiometricScheduler(TAG, gestureAvailabilityDispatcher);
+        final HalResultController controller = new HalResultController(context, handler, scheduler);
+        return new Fingerprint21(context, scheduler, handler, sensorId, lockoutResetDispatcher,
+                controller);
     }
 
     @Override
@@ -355,7 +405,7 @@
         // successfully set.
         long halId = 0;
         try {
-            halId = mDaemon.setNotify(mDaemonCallback);
+            halId = mDaemon.setNotify(mHalResultController);
         } catch (RemoteException e) {
             Slog.e(TAG, "Failed to set callback for fingerprint HAL", e);
             mDaemon = null;
@@ -373,6 +423,9 @@
         return mDaemon;
     }
 
+    @Nullable IUdfpsOverlayController getUdfpsOverlayController() {
+        return mUdfpsOverlayController;
+    }
     @LockoutTracker.LockoutMode int getLockoutModeForUser(int userId) {
         return mLockoutTracker.getLockoutModeForUser(userId);
     }
@@ -409,14 +462,17 @@
                 new FingerprintUpdateActiveUserClient(mContext, mLazyDaemon, targetUserId,
                         mContext.getOpPackageName(), mSensorProperties.sensorId, mCurrentUserId,
                         hasEnrolled, mAuthenticatorIds);
-        mScheduler.scheduleClientMonitor(client, (clientMonitor, success) -> {
-            if (success) {
-                mCurrentUserId = targetUserId;
+        mScheduler.scheduleClientMonitor(client, new ClientMonitor.Callback() {
+            @Override
+            public void onClientFinished(@NonNull ClientMonitor<?> clientMonitor, boolean success) {
+                if (success) {
+                    mCurrentUserId = targetUserId;
+                }
             }
         });
     }
 
-    void scheduleResetLockout(int userId, byte[] hardwareAuthToken) {
+    void scheduleResetLockout(int userId) {
         // Fingerprint2.1 keeps track of lockout in the framework. Let's just do it on the handler
         // thread.
         mHandler.post(() -> {
@@ -453,12 +509,16 @@
                     mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
                     hardwareAuthToken, opPackageName, FingerprintUtils.getInstance(),
                     ENROLL_TIMEOUT_SEC, mSensorProperties.sensorId, mUdfpsOverlayController);
-            mScheduler.scheduleClientMonitor(client, ((clientMonitor, success) -> {
-                if (success) {
-                    // Update authenticatorIds
-                    scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId());
+            mScheduler.scheduleClientMonitor(client, new ClientMonitor.Callback() {
+                @Override
+                public void onClientFinished(@NonNull ClientMonitor<?> clientMonitor,
+                        boolean success) {
+                    if (success) {
+                        // Update authenticatorIds
+                        scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId());
+                    }
                 }
-            }));
+            });
         });
     }
 
@@ -485,7 +545,7 @@
 
     void scheduleAuthenticate(@NonNull IBinder token, long operationId, int userId, int cookie,
             @NonNull ClientMonitorCallbackConverter listener, @NonNull String opPackageName,
-            @Nullable Surface surface, boolean restricted, int statsClient) {
+            boolean restricted, int statsClient, boolean isKeyguard) {
         mHandler.post(() -> {
             scheduleUpdateActiveUserWithoutHandler(userId);
 
@@ -493,8 +553,8 @@
             final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
                     mContext, mLazyDaemon, token, listener, userId, operationId, restricted,
                     opPackageName, cookie, false /* requireConfirmation */,
-                    mSensorProperties.sensorId, isStrongBiometric, surface, statsClient,
-                    mTaskStackListener, mLockoutTracker, mUdfpsOverlayController);
+                    mSensorProperties.sensorId, isStrongBiometric, statsClient,
+                    mTaskStackListener, mLockoutTracker, mUdfpsOverlayController, isKeyguard);
             mScheduler.scheduleClientMonitor(client);
         });
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21UdfpsMock.java
new file mode 100644
index 0000000..1a6ad46
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21UdfpsMock.java
@@ -0,0 +1,557 @@
+/*
+ * 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.biometrics.sensors.fingerprint;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.trust.TrustManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
+import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
+import android.hardware.fingerprint.FingerprintSensorProperties;
+import android.hardware.fingerprint.IUdfpsOverlayController;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Slog;
+import android.util.SparseBooleanArray;
+
+import com.android.server.biometrics.sensors.AuthenticationConsumer;
+import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.ClientMonitor;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+
+import java.util.ArrayList;
+import java.util.Random;
+
+/**
+ * A mockable/testable provider of the {@link android.hardware.biometrics.fingerprint.V2_3} HIDL
+ * interface. This class is intended simulate UDFPS logic for devices that do not have an actual
+ * fingerprint@2.3 HAL (where UDFPS starts to become supported)
+ *
+ * UDFPS "accept" can only happen within a set amount of time after a sensor authentication. This is
+ * specified by {@link MockHalResultController#AUTH_VALIDITY_MS}. Touches after this duration will
+ * be treated as "reject".
+ *
+ * This class provides framework logic to emulate, for testing only, the UDFPS functionalies below:
+ *
+ * 1) IF either A) the caller is keyguard, and the device is not in a trusted state (authenticated
+ *    via biometric sensor or unlocked with a trust agent {@see android.app.trust.TrustManager}, OR
+ *    B) the caller is not keyguard, and regardless of trusted state, AND (following applies to both
+ *    (A) and (B) above) {@link FingerprintManager#onFingerDown(int, int, float, float)} is
+ *    received, this class will respond with {@link AuthenticationCallback#onAuthenticationFailed()}
+ *    after a tunable flat_time + variance_time.
+ *
+ *    In the case above (1), callers must not receive a successful authentication event here because
+ *    the sensor has not actually been authenticated.
+ *
+ * 2) IF A) the caller is keyguard and the device is not in a trusted state, OR B) the caller is not
+ *    keyguard and regardless of trusted state, AND (following applies to both (A) and (B)) the
+ *    sensor is touched and the fingerprint is accepted by the HAL, and then
+ *    {@link FingerprintManager#onFingerDown(int, int, float, float)} is received, this class will
+ *    respond with {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)}
+ *    after a tunable flat_time + variance_time. Note that the authentication callback from the
+ *    sensor is held until {@link FingerprintManager#onFingerDown(int, int, float, float)} is
+ *    invoked.
+ *
+ *    In the case above (2), callers can receive a successful authentication callback because the
+ *    real sensor was authenticated. Note that even though the real sensor was touched, keyguard
+ *    fingerprint authentication does not put keyguard into a trusted state because the
+ *    authentication callback is held until onFingerDown was invoked. This allows callers such as
+ *    keyguard to simulate a realistic path.
+ *
+ * 3) IF the caller is keyguard AND the device in a trusted state and then
+ *    {@link FingerprintManager#onFingerDown(int, int, float, float)} is received, this class will
+ *    respond with {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)}
+ *    after a tunable flat_time + variance time.
+ *
+ *    In the case above (3), since the device is already unlockable via trust agent, it's fine to
+ *    simulate the successful auth path. Non-keyguard clients will fall into either (1) or (2)
+ *    above.
+ *
+ *  This class currently does not simulate false rejection. Conversely, this class relies on the
+ *  real hardware sensor so does not affect false acceptance.
+ */
+@SuppressWarnings("deprecation")
+public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManager.TrustListener {
+
+    private static final String TAG = "Fingerprint21UdfpsMock";
+
+    // Secure setting integer. If true, the system will load this class to enable udfps testing.
+    public static final String CONFIG_ENABLE_TEST_UDFPS =
+            "com.android.server.biometrics.sensors.fingerprint.test_udfps.enable";
+    // Secure setting integer. A fixed duration intended to simulate something like the duration
+    // required for image capture.
+    private static final String CONFIG_AUTH_DELAY_PT1 =
+            "com.android.server.biometrics.sensors.fingerprint.test_udfps.auth_delay_pt1";
+    // Secure setting integer. A fixed duration intended to simulate something like the duration
+    // required for template matching to complete.
+    private static final String CONFIG_AUTH_DELAY_PT2 =
+            "com.android.server.biometrics.sensors.fingerprint.test_udfps.auth_delay_pt2";
+    // Secure setting integer. A random value between [-randomness, randomness] will be added to the
+    // capture delay above for each accept/reject.
+    private static final String CONFIG_AUTH_DELAY_RANDOMNESS =
+            "com.android.server.biometrics.sensors.fingerprint.test_udfps.auth_delay_randomness";
+
+    private static final int DEFAULT_AUTH_DELAY_PT1_MS = 300;
+    private static final int DEFAULT_AUTH_DELAY_PT2_MS = 400;
+    private static final int DEFAULT_AUTH_DELAY_RANDOMNESS_MS = 100;
+
+    @NonNull private final TestableBiometricScheduler mScheduler;
+    @NonNull private final Handler mHandler;
+    @NonNull private final FingerprintSensorProperties mSensorProperties;
+    @NonNull private final MockHalResultController mMockHalResultController;
+    @NonNull private final TrustManager mTrustManager;
+    @NonNull private final SparseBooleanArray mUserHasTrust;
+    @NonNull private final Random mRandom;
+    @NonNull private final FakeRejectRunnable mFakeRejectRunnable;
+    @NonNull private final FakeAcceptRunnable mFakeAcceptRunnable;
+    @NonNull private final RestartAuthRunnable mRestartAuthRunnable;
+
+    private static class TestableBiometricScheduler extends BiometricScheduler {
+        @NonNull private final TestableInternalCallback mInternalCallback;
+        @NonNull private Fingerprint21UdfpsMock mFingerprint21;
+
+        TestableBiometricScheduler(@NonNull String tag,
+                @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
+            super(tag, gestureAvailabilityDispatcher);
+            mInternalCallback = new TestableInternalCallback();
+        }
+
+        class TestableInternalCallback extends InternalCallback {
+            @Override
+            public void onClientStarted(ClientMonitor<?> clientMonitor) {
+                super.onClientStarted(clientMonitor);
+                Slog.d(TAG, "Client started: " + clientMonitor);
+                mFingerprint21.setDebugMessage("Started: " + clientMonitor);
+            }
+
+            @Override
+            public void onClientFinished(ClientMonitor<?> clientMonitor, boolean success) {
+                super.onClientFinished(clientMonitor, success);
+                Slog.d(TAG, "Client finished: " + clientMonitor);
+                mFingerprint21.setDebugMessage("Finished: " + clientMonitor);
+            }
+        }
+
+        void init(@NonNull Fingerprint21UdfpsMock fingerprint21) {
+            mFingerprint21 = fingerprint21;
+        }
+
+        /**
+         * Expose the internal finish callback so it can be used for testing
+         */
+        @Override
+        @NonNull protected InternalCallback getInternalCallback() {
+            return mInternalCallback;
+        }
+    }
+
+    /**
+     * All of the mocking/testing should happen in here. This way we don't need to modify the
+     * {@link com.android.server.biometrics.sensors.ClientMonitor} implementations and can run the
+     * real path there.
+     */
+    private static class MockHalResultController extends HalResultController {
+
+        // Duration for which a sensor authentication can be treated as UDFPS success.
+        private static final int AUTH_VALIDITY_MS = 10 * 1000; // 10 seconds
+
+        static class LastAuthArgs {
+            @NonNull final AuthenticationConsumer lastAuthenticatedClient;
+            final long deviceId;
+            final int fingerId;
+            final int groupId;
+            @Nullable final ArrayList<Byte> token;
+
+            LastAuthArgs(@NonNull AuthenticationConsumer authenticationConsumer, long deviceId,
+                    int fingerId, int groupId, @Nullable ArrayList<Byte> token) {
+                lastAuthenticatedClient = authenticationConsumer;
+                this.deviceId = deviceId;
+                this.fingerId = fingerId;
+                this.groupId = groupId;
+                if (token == null) {
+                    this.token = null;
+                } else {
+                    // Store a copy so the owner can be GC'd
+                    this.token = new ArrayList<>(token);
+                }
+            }
+        }
+
+        // Initialized after the constructor, but before it's ever used.
+        @NonNull private RestartAuthRunnable mRestartAuthRunnable;
+        @NonNull private Fingerprint21UdfpsMock mFingerprint21;
+        @Nullable private LastAuthArgs mLastAuthArgs;
+
+        MockHalResultController(@NonNull Context context, @NonNull Handler handler,
+                @NonNull BiometricScheduler scheduler) {
+            super(context, handler, scheduler);
+        }
+
+        void init(@NonNull RestartAuthRunnable restartAuthRunnable,
+                @NonNull Fingerprint21UdfpsMock fingerprint21) {
+            mRestartAuthRunnable = restartAuthRunnable;
+            mFingerprint21 = fingerprint21;
+        }
+
+        @Nullable AuthenticationConsumer getLastAuthenticatedClient() {
+            return mLastAuthArgs != null ? mLastAuthArgs.lastAuthenticatedClient : null;
+        }
+
+        /**
+         * Intercepts the HAL authentication and holds it until the UDFPS simulation decides
+         * that authentication finished.
+         */
+        @Override
+        public void onAuthenticated(long deviceId, int fingerId, int groupId,
+                ArrayList<Byte> token) {
+            mHandler.post(() -> {
+                final ClientMonitor<?> client = mScheduler.getCurrentClient();
+                if (!(client instanceof AuthenticationConsumer)) {
+                    Slog.e(TAG, "Non authentication consumer: " + client);
+                    return;
+                }
+
+                final boolean accepted = fingerId != 0;
+                if (accepted) {
+                    mFingerprint21.setDebugMessage("Finger accepted");
+                } else {
+                    mFingerprint21.setDebugMessage("Finger rejected");
+                }
+
+                final AuthenticationConsumer authenticationConsumer =
+                        (AuthenticationConsumer) client;
+                mLastAuthArgs = new LastAuthArgs(authenticationConsumer, deviceId, fingerId,
+                        groupId, token);
+
+                // Remove any existing restart runnbables since auth just started, and put a new
+                // one on the queue.
+                mHandler.removeCallbacks(mRestartAuthRunnable);
+                mRestartAuthRunnable.setLastAuthReference(authenticationConsumer);
+                mHandler.postDelayed(mRestartAuthRunnable, AUTH_VALIDITY_MS);
+            });
+        }
+
+        /**
+         * Calls through to the real interface and notifies clients of accept/reject.
+         */
+        void sendAuthenticated(long deviceId, int fingerId, int groupId,
+                ArrayList<Byte> token) {
+            Slog.d(TAG, "sendAuthenticated: " + (fingerId != 0));
+            mFingerprint21.setDebugMessage("Udfps match: " + (fingerId != 0));
+            super.onAuthenticated(deviceId, fingerId, groupId, token);
+        }
+    }
+
+    static Fingerprint21UdfpsMock newInstance(@NonNull Context context, int sensorId,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
+        Slog.d(TAG, "Creating Fingerprint23Mock!");
+
+        final Handler handler = new Handler(Looper.getMainLooper());
+        final TestableBiometricScheduler scheduler =
+                new TestableBiometricScheduler(TAG, gestureAvailabilityDispatcher);
+        final MockHalResultController controller =
+                new MockHalResultController(context, handler, scheduler);
+        return new Fingerprint21UdfpsMock(context, scheduler, handler, sensorId,
+                lockoutResetDispatcher, controller);
+    }
+
+    private static abstract class FakeFingerRunnable implements Runnable {
+        private long mFingerDownTime;
+        private int mCaptureDuration;
+
+        /**
+         * @param fingerDownTime System time when onFingerDown occurred
+         * @param captureDuration Duration that the finger needs to be down for
+         */
+        void setSimulationTime(long fingerDownTime, int captureDuration) {
+            mFingerDownTime = fingerDownTime;
+            mCaptureDuration = captureDuration;
+        }
+
+        @SuppressWarnings("BooleanMethodIsAlwaysInverted")
+        boolean isImageCaptureComplete() {
+            return System.currentTimeMillis() - mFingerDownTime > mCaptureDuration;
+        }
+    }
+
+    private final class FakeRejectRunnable extends FakeFingerRunnable {
+        @Override
+        public void run() {
+            mMockHalResultController.sendAuthenticated(0, 0, 0, null);
+        }
+    }
+
+    private final class FakeAcceptRunnable extends FakeFingerRunnable {
+        @Override
+        public void run() {
+            if (mMockHalResultController.mLastAuthArgs == null) {
+                // This can happen if the user has trust agents enabled, which make lockscreen
+                // dismissable. Send a fake non-zero (accept) finger.
+                Slog.d(TAG, "Sending fake finger");
+                mMockHalResultController.sendAuthenticated(1 /* deviceId */,
+                        1 /* fingerId */, 1 /* groupId */, null /* token */);
+            } else {
+                mMockHalResultController.sendAuthenticated(
+                        mMockHalResultController.mLastAuthArgs.deviceId,
+                        mMockHalResultController.mLastAuthArgs.fingerId,
+                        mMockHalResultController.mLastAuthArgs.groupId,
+                        mMockHalResultController.mLastAuthArgs.token);
+            }
+        }
+    }
+
+    /**
+     * The fingerprint HAL allows multiple (5) fingerprint attempts per HIDL invocation of the
+     * authenticate method. However, valid fingerprint authentications are invalidated after
+     * {@link MockHalResultController#AUTH_VALIDITY_MS}, meaning UDFPS touches will be reported as
+     * rejects if touched after that duration. However, since a valid fingerprint was detected, the
+     * HAL and FingerprintService will not look for subsequent fingerprints.
+     *
+     * In order to keep the FingerprintManager API consistent (that multiple fingerprint attempts
+     * are allowed per auth lifecycle), we internally cancel and restart authentication so that the
+     * sensor is responsive again.
+     */
+    private static final class RestartAuthRunnable implements Runnable {
+        @NonNull private final Fingerprint21UdfpsMock mFingerprint21;
+        @NonNull private final TestableBiometricScheduler mScheduler;
+
+        // Store a reference to the auth consumer that should be invalidated.
+        private AuthenticationConsumer mLastAuthConsumer;
+
+        RestartAuthRunnable(@NonNull Fingerprint21UdfpsMock fingerprint21,
+                @NonNull TestableBiometricScheduler scheduler) {
+            mFingerprint21 = fingerprint21;
+            mScheduler = scheduler;
+        }
+
+        void setLastAuthReference(AuthenticationConsumer lastAuthConsumer) {
+            mLastAuthConsumer = lastAuthConsumer;
+        }
+
+        @Override
+        public void run() {
+            final ClientMonitor<?> client = mScheduler.getCurrentClient();
+
+            // We don't care about FingerprintDetectClient, since accept/rejects are both OK. UDFPS
+            // rejects will just simulate the path where non-enrolled fingers are presented.
+            if (!(client instanceof FingerprintAuthenticationClient)) {
+                Slog.e(TAG, "Non-FingerprintAuthenticationClient client: " + client);
+                return;
+            }
+
+            // Perhaps the runnable is stale, or the user stopped/started auth manually. Do not
+            // restart auth in this case.
+            if (client != mLastAuthConsumer) {
+                Slog.e(TAG, "Current client: " + client
+                        + " does not match mLastAuthConsumer: " + mLastAuthConsumer);
+                return;
+            }
+
+            Slog.d(TAG, "Restarting auth, current: " + client);
+            mFingerprint21.setDebugMessage("Auth timed out");
+
+            final FingerprintAuthenticationClient authClient =
+                    (FingerprintAuthenticationClient) client;
+            // Store the authClient parameters so it can be rescheduled
+            final IBinder token = client.getToken();
+            final long operationId = authClient.getOperationId();
+            final int user = client.getTargetUserId();
+            final int cookie = client.getCookie();
+            final ClientMonitorCallbackConverter listener = client.getListener();
+            final String opPackageName = client.getOwnerString();
+            final boolean restricted = authClient.isRestricted();
+            final int statsClient = client.getStatsClient();
+            final boolean isKeyguard = authClient.isKeyguard();
+
+            // Don't actually send cancel() to the HAL, since successful auth already finishes
+            // HAL authenticate() lifecycle. Just
+            mScheduler.getInternalCallback().onClientFinished(client, true /* success */);
+
+            // Schedule this only after we invoke onClientFinished for the previous client, so that
+            // internal preemption logic is not run.
+            mFingerprint21.scheduleAuthenticate(token, operationId, user, cookie,
+                    listener, opPackageName, restricted, statsClient, isKeyguard);
+        }
+    }
+
+    private Fingerprint21UdfpsMock(@NonNull Context context,
+            @NonNull TestableBiometricScheduler scheduler,
+            @NonNull Handler handler, int sensorId,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull MockHalResultController controller) {
+        super(context, scheduler, handler, sensorId, lockoutResetDispatcher, controller);
+        mScheduler = scheduler;
+        mScheduler.init(this);
+        mHandler = handler;
+        // resetLockout is controlled by the framework, so hardwareAuthToken is not required
+        final boolean resetLockoutRequiresHardwareAuthToken = false;
+        mSensorProperties = new FingerprintSensorProperties(sensorId,
+                FingerprintSensorProperties.TYPE_UDFPS, resetLockoutRequiresHardwareAuthToken);
+        mMockHalResultController = controller;
+        mUserHasTrust = new SparseBooleanArray();
+        mTrustManager = context.getSystemService(TrustManager.class);
+        mTrustManager.registerTrustListener(this);
+        mRandom = new Random();
+        mFakeRejectRunnable = new FakeRejectRunnable();
+        mFakeAcceptRunnable = new FakeAcceptRunnable();
+        mRestartAuthRunnable = new RestartAuthRunnable(this, mScheduler);
+
+        // We can't initialize this during MockHalresultController's constructor due to a circular
+        // dependency.
+        mMockHalResultController.init(mRestartAuthRunnable, this);
+    }
+
+    @Override
+    public void onTrustChanged(boolean enabled, int userId, int flags) {
+        mUserHasTrust.put(userId, enabled);
+    }
+
+    @Override
+    public void onTrustManagedChanged(boolean enabled, int userId) {
+
+    }
+
+    @Override
+    public void onTrustError(CharSequence message) {
+
+    }
+
+    @Override
+    @NonNull
+    FingerprintSensorProperties getFingerprintSensorProperties() {
+        return mSensorProperties;
+    }
+
+    @Override
+    void onFingerDown(int x, int y, float minor, float major) {
+        mHandler.post(() -> {
+            Slog.d(TAG, "onFingerDown");
+            final AuthenticationConsumer lastAuthenticatedConsumer =
+                    mMockHalResultController.getLastAuthenticatedClient();
+            final ClientMonitor<?> currentScheduledClient = mScheduler.getCurrentClient();
+
+            if (currentScheduledClient == null) {
+                Slog.d(TAG, "Not authenticating");
+                return;
+            }
+
+            mHandler.removeCallbacks(mFakeRejectRunnable);
+            mHandler.removeCallbacks(mFakeAcceptRunnable);
+
+            // The sensor was authenticated, is still the currently scheduled client, and the
+            // user touched the UDFPS affordance. Pretend that auth succeeded.
+            final boolean authenticatedClientIsCurrent = lastAuthenticatedConsumer != null
+                    && lastAuthenticatedConsumer == currentScheduledClient;
+            // User is unlocked on keyguard via Trust Agent
+            final boolean keyguardAndTrusted;
+            if (currentScheduledClient instanceof FingerprintAuthenticationClient) {
+                keyguardAndTrusted = ((FingerprintAuthenticationClient) currentScheduledClient)
+                        .isKeyguard()
+                        && mUserHasTrust.get(currentScheduledClient.getTargetUserId(), false);
+            } else {
+                keyguardAndTrusted = false;
+            }
+
+            final int captureDuration = getNewCaptureDuration();
+            final int matchingDuration = getMatchingDuration();
+            final int totalDuration = captureDuration + matchingDuration;
+            setDebugMessage("Duration: " + totalDuration
+                    + " (" + captureDuration + " + " + matchingDuration + ")");
+            if (authenticatedClientIsCurrent || keyguardAndTrusted) {
+                mFakeAcceptRunnable.setSimulationTime(System.currentTimeMillis(), captureDuration);
+                mHandler.postDelayed(mFakeAcceptRunnable, totalDuration);
+            } else if (currentScheduledClient instanceof AuthenticationConsumer) {
+                // Something is authenticating but authentication has not succeeded yet. Pretend
+                // that auth rejected.
+                mFakeRejectRunnable.setSimulationTime(System.currentTimeMillis(), captureDuration);
+                mHandler.postDelayed(mFakeRejectRunnable, totalDuration);
+            }
+        });
+    }
+
+    @Override
+    void onFingerUp() {
+        mHandler.post(() -> {
+            Slog.d(TAG, "onFingerUp");
+
+            // Only one of these can be on the handler at any given time (see onFingerDown). If
+            // image capture is not complete, send ACQUIRED_TOO_FAST and remove the runnable from
+            // the handler. Image capture (onFingerDown) needs to happen again.
+            if (mHandler.hasCallbacks(mFakeRejectRunnable)
+                    && !mFakeRejectRunnable.isImageCaptureComplete()) {
+                mHandler.removeCallbacks(mFakeRejectRunnable);
+                mMockHalResultController.onAcquired(0 /* deviceId */,
+                        FingerprintManager.FINGERPRINT_ACQUIRED_TOO_FAST,
+                        0 /* vendorCode */);
+            } else if (mHandler.hasCallbacks(mFakeAcceptRunnable)
+                    && !mFakeAcceptRunnable.isImageCaptureComplete()) {
+                mHandler.removeCallbacks(mFakeAcceptRunnable);
+                mMockHalResultController.onAcquired(0 /* deviceId */,
+                        FingerprintManager.FINGERPRINT_ACQUIRED_TOO_FAST,
+                        0 /* vendorCode */);
+            }
+        });
+    }
+
+    private int getNewCaptureDuration() {
+        final ContentResolver contentResolver = mContext.getContentResolver();
+        final int captureTime = Settings.Secure.getIntForUser(contentResolver,
+                CONFIG_AUTH_DELAY_PT1,
+                DEFAULT_AUTH_DELAY_PT1_MS,
+                UserHandle.USER_CURRENT);
+        final int randomDelayRange = Settings.Secure.getIntForUser(contentResolver,
+                CONFIG_AUTH_DELAY_RANDOMNESS,
+                DEFAULT_AUTH_DELAY_RANDOMNESS_MS,
+                UserHandle.USER_CURRENT);
+        final int randomDelay = mRandom.nextInt(randomDelayRange * 2) - randomDelayRange;
+
+        // Must be at least 0
+        return Math.max(captureTime + randomDelay, 0);
+    }
+
+    private int getMatchingDuration() {
+        final int matchingTime = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                CONFIG_AUTH_DELAY_PT2,
+                DEFAULT_AUTH_DELAY_PT2_MS,
+                UserHandle.USER_CURRENT);
+
+        // Must be at least 0
+        return Math.max(matchingTime, 0);
+    }
+
+    private void setDebugMessage(String message) {
+        try {
+            final IUdfpsOverlayController controller = getUdfpsOverlayController();
+            // Things can happen before SysUI loads and sets the controller.
+            if (controller != null) {
+                Slog.d(TAG, "setDebugMessage: " + message);
+                controller.setDebugMessage(message);
+            }
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Remote exception when sending message: " + message, e);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticationClient.java
index 1564056..99d348a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticationClient.java
@@ -29,7 +29,6 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
-import android.view.Surface;
 
 import com.android.server.biometrics.sensors.AuthenticationClient;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
@@ -47,6 +46,7 @@
 
     private static final String TAG = "Biometrics/FingerprintAuthClient";
 
+    private final boolean mIsKeyguard;
     private final LockoutFrameworkImpl mLockoutFrameworkImpl;
     @Nullable private final IUdfpsOverlayController mUdfpsOverlayController;
 
@@ -54,16 +54,17 @@
             @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
             @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
             boolean restricted, @NonNull String owner, int cookie, boolean requireConfirmation,
-            int sensorId, boolean isStrongBiometric, @Nullable Surface surface, int statsClient,
+            int sensorId, boolean isStrongBiometric, int statsClient,
             @NonNull TaskStackListener taskStackListener,
             @NonNull LockoutFrameworkImpl lockoutTracker,
-            @Nullable IUdfpsOverlayController udfpsOverlayController) {
+            @Nullable IUdfpsOverlayController udfpsOverlayController, boolean isKeyguard) {
         super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
                 owner, cookie, requireConfirmation, sensorId, isStrongBiometric,
                 BiometricsProtoEnums.MODALITY_FINGERPRINT, statsClient, taskStackListener,
                 lockoutTracker);
         mLockoutFrameworkImpl = lockoutTracker;
         mUdfpsOverlayController = udfpsOverlayController;
+        mIsKeyguard = isKeyguard;
     }
 
     @Override
@@ -79,7 +80,7 @@
         if (authenticated) {
             resetFailedAttempts(getTargetUserId());
             UdfpsHelper.hideUdfpsOverlay(mUdfpsOverlayController);
-            mFinishCallback.onClientFinished(this, true /* success */);
+            mCallback.onClientFinished(this, true /* success */);
         } else {
             final @LockoutTracker.LockoutMode int lockoutMode =
                     mLockoutFrameworkImpl.getLockoutModeForUser(getTargetUserId());
@@ -119,7 +120,7 @@
             onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
                     0 /* vendorCode */);
             UdfpsHelper.hideUdfpsOverlay(mUdfpsOverlayController);
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 
@@ -132,7 +133,7 @@
             Slog.e(TAG, "Remote exception when requesting cancel", e);
             onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
                     0 /* vendorCode */);
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 
@@ -145,4 +146,8 @@
     public void onFingerUp() {
         UdfpsHelper.onFingerUp(getFreshDaemon());
     }
+
+    public boolean isKeyguard() {
+        return mIsKeyguard;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
index 3418c46..21a46d5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
@@ -75,11 +75,6 @@
     }
 
     @Override
-    public void resetLockout(int userId, byte[] hardwareAuthToken) throws RemoteException {
-        mFingerprintService.resetLockout(userId, hardwareAuthToken);
-    }
-
-    @Override
     public long getAuthenticatorId(int callingUserId) throws RemoteException {
         return mFingerprintService.getAuthenticatorId(callingUserId);
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintDetectClient.java
index 8b295f8..8652ee4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintDetectClient.java
@@ -68,13 +68,13 @@
             Slog.e(TAG, "Remote exception when requesting cancel", e);
             onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
                     0 /* vendorCode */);
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 
     @Override
-    public void start(@NonNull FinishCallback finishCallback) {
-        super.start(finishCallback);
+    public void start(@NonNull Callback callback) {
+        super.start(callback);
         startHalOperation();
     }
 
@@ -88,7 +88,7 @@
             onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
                     0 /* vendorCode */);
             UdfpsHelper.hideUdfpsOverlay(mUdfpsOverlayController);
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintEnrollClient.java
index 32f8b8f..d5db6e4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintEnrollClient.java
@@ -79,7 +79,7 @@
             onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
                     0 /* vendorCode */);
             UdfpsHelper.hideUdfpsOverlay(mUdfpsOverlayController);
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 
@@ -92,7 +92,7 @@
             Slog.e(TAG, "Remote exception when requesting cancel", e);
             onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
                     0 /* vendorCode */);
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintInternalEnumerateClient.java
index 240c3c5..834bf42 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintInternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintInternalEnumerateClient.java
@@ -52,7 +52,7 @@
             getFreshDaemon().enumerate();
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception when requesting enumerate", e);
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintRemovalClient.java
index a9336ef..9f54563 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintRemovalClient.java
@@ -54,7 +54,7 @@
             getFreshDaemon().remove(getTargetUserId(), mBiometricId);
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception when requesting remove", e);
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index e4387c9..7c7da11 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -25,6 +25,7 @@
 import static android.Manifest.permission.USE_FINGERPRINT;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.AppOpsManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -38,14 +39,17 @@
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.Binder;
+import android.os.Build;
 import android.os.IBinder;
 import android.os.NativeHandle;
 import android.os.Process;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.util.EventLog;
 import android.util.Slog;
 import android.view.Surface;
 
+import com.android.internal.R;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.SystemService;
@@ -94,10 +98,16 @@
         }
 
         @Override // Binder call
-        public void generateChallenge(IBinder token, IFingerprintServiceReceiver receiver,
-                String opPackageName) {
+        public void generateChallenge(IBinder token, int sensorId,
+                IFingerprintServiceReceiver receiver, String opPackageName) {
             Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
-            mFingerprint21.scheduleGenerateChallenge(token, receiver, opPackageName);
+
+            if (sensorId == mFingerprint21.getFingerprintSensorProperties().sensorId) {
+                mFingerprint21.scheduleGenerateChallenge(token, receiver, opPackageName);
+                return;
+            }
+
+            Slog.w(TAG, "No matching sensor for generateChallenge, sensorId: " + sensorId);
         }
 
         @Override // Binder call
@@ -158,8 +168,8 @@
             final int statsClient = isKeyguard ? BiometricsProtoEnums.CLIENT_KEYGUARD
                     : BiometricsProtoEnums.CLIENT_FINGERPRINT_MANAGER;
             mFingerprint21.scheduleAuthenticate(token, operationId, userId, 0 /* cookie */,
-                    new ClientMonitorCallbackConverter(receiver), opPackageName, surface,
-                    restricted, statsClient);
+                    new ClientMonitorCallbackConverter(receiver), opPackageName,
+                    restricted, statsClient, isKeyguard);
         }
 
         @Override
@@ -193,8 +203,8 @@
 
             final boolean restricted = true; // BiometricPrompt is always restricted
             mFingerprint21.scheduleAuthenticate(token, operationId, userId, cookie,
-                    new ClientMonitorCallbackConverter(sensorReceiver), opPackageName, surface,
-                    restricted, BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT);
+                    new ClientMonitorCallbackConverter(sensorReceiver), opPackageName, restricted,
+                    BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT, false /* isKeyguard */);
         }
 
         @Override // Binder call
@@ -343,9 +353,16 @@
         }
 
         @Override // Binder call
-        public void resetLockout(int userId, byte [] hardwareAuthToken) {
+        public void resetLockout(IBinder token, int sensorId, int userId,
+                @Nullable byte [] hardwareAuthToken, String opPackageName) {
             Utils.checkPermission(getContext(), RESET_FINGERPRINT_LOCKOUT);
-            mFingerprint21.scheduleResetLockout(userId, hardwareAuthToken);
+
+            if (sensorId == mFingerprint21.getFingerprintSensorProperties().sensorId) {
+                mFingerprint21.scheduleResetLockout(userId);
+                return;
+            }
+
+            Slog.w(TAG, "No matching sensor for resetLockout, sensorId: " + sensorId);
         }
 
         @Override
@@ -369,8 +386,18 @@
         @Override // Binder call
         public void initializeConfiguration(int sensorId) {
             Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
-            mFingerprint21 = new Fingerprint21(getContext(), sensorId, mLockoutResetDispatcher,
-                    mGestureAvailabilityDispatcher);
+
+            if ((Build.IS_USERDEBUG || Build.IS_ENG)
+                    && getContext().getResources().getBoolean(R.bool.allow_test_udfps)
+                    && Settings.Secure.getIntForUser(getContext().getContentResolver(),
+                    Fingerprint21UdfpsMock.CONFIG_ENABLE_TEST_UDFPS, 0 /* default */,
+                    UserHandle.USER_CURRENT) != 0) {
+                mFingerprint21 = Fingerprint21UdfpsMock.newInstance(getContext(), sensorId,
+                        mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
+            } else {
+                mFingerprint21 = Fingerprint21.newInstance(getContext(), sensorId,
+                        mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
+            }
         }
 
         @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUpdateActiveUserClient.java
index e1082ae..c1c3593 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUpdateActiveUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUpdateActiveUserClient.java
@@ -22,7 +22,6 @@
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
 import android.os.Build;
 import android.os.Environment;
-import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.SELinux;
 import android.util.Slog;
@@ -58,8 +57,8 @@
     }
 
     @Override
-    public void start(@NonNull FinishCallback finishCallback) {
-        super.start(finishCallback);
+    public void start(@NonNull Callback callback) {
+        super.start(callback);
 
         if (mCurrentUserId == getTargetUserId()) {
             Slog.d(TAG, "Already user: " + mCurrentUserId + ", refreshing authenticatorId");
@@ -69,7 +68,7 @@
             } catch (RemoteException e) {
                 Slog.e(TAG, "Unable to refresh authenticatorId", e);
             }
-            finishCallback.onClientFinished(this, true /* success */);
+            callback.onClientFinished(this, true /* success */);
             return;
         }
 
@@ -89,7 +88,7 @@
         if (!mDirectory.exists()) {
             if (!mDirectory.mkdir()) {
                 Slog.e(TAG, "Cannot make directory: " + mDirectory.getAbsolutePath());
-                finishCallback.onClientFinished(this, false /* success */);
+                callback.onClientFinished(this, false /* success */);
                 return;
             }
             // Calling mkdir() from this process will create a directory with our
@@ -97,7 +96,7 @@
             // the label.
             if (!SELinux.restorecon(mDirectory)) {
                 Slog.e(TAG, "Restorecons failed. Directory will have wrong label.");
-                finishCallback.onClientFinished(this, false /* success */);
+                callback.onClientFinished(this, false /* success */);
                 return;
             }
         }
@@ -116,10 +115,10 @@
             getFreshDaemon().setActiveGroup(getTargetUserId(), mDirectory.getAbsolutePath());
             mAuthenticatorIds.put(getTargetUserId(), mHasEnrolledBiometrics
                     ? getFreshDaemon().getAuthenticatorId() : 0L);
-            mFinishCallback.onClientFinished(this, true /* success */);
+            mCallback.onClientFinished(this, true /* success */);
         } catch (RemoteException e) {
             Slog.e(TAG, "Failed to setActiveGroup: " + e);
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java
index 9e04057..0400ef5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java
@@ -70,10 +70,6 @@
     }
 
     @Override
-    public void resetLockout(int userId, byte[] hardwareAuthToken) throws RemoteException {
-    }
-
-    @Override
     public long getAuthenticatorId(int callingUserId) throws RemoteException {
         return 0;
     }
diff --git a/services/core/java/com/android/server/connectivity/PermissionMonitor.java b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
index 7202f0f..7f9b3c9 100644
--- a/services/core/java/com/android/server/connectivity/PermissionMonitor.java
+++ b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
@@ -56,7 +56,6 @@
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.SparseArray;
-import android.util.SparseIntArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -130,7 +129,42 @@
         }
     }
 
-    public PermissionMonitor(@NonNull final Context context, @NonNull final INetd netd) {
+    /**
+     * A data class to store each uid Netd permission information. Netd permissions includes
+     * PERMISSION_NETWORK, PERMISSION_SYSTEM, PERMISSION_INTERNET, PERMISSION_UPDATE_DEVICE_STATS
+     * and OR'd with the others. Default permission is PERMISSION_NONE and PERMISSION_UNINSTALLED
+     * will be set if all packages are removed from the uid.
+     */
+    public static class UidNetdPermissionInfo {
+        private final int mNetdPermissions;
+
+        UidNetdPermissionInfo() {
+            this(PERMISSION_NONE);
+        }
+
+        UidNetdPermissionInfo(int permissions) {
+            mNetdPermissions = permissions;
+        }
+
+        /** Plus given permissions and return new UidNetdPermissionInfo instance. */
+        public UidNetdPermissionInfo plusNetdPermissions(int permissions) {
+            return new UidNetdPermissionInfo(mNetdPermissions | permissions);
+        }
+
+        /** Return whether package is uninstalled. */
+        public boolean isPackageUninstalled() {
+            return mNetdPermissions == PERMISSION_UNINSTALLED;
+        }
+
+        /** Check that uid has given permissions */
+        public boolean hasNetdPermissions(final int permissions) {
+            if (isPackageUninstalled()) return false;
+            if (permissions == PERMISSION_NONE) return true;
+            return (mNetdPermissions & permissions) == permissions;
+        }
+    }
+
+    public PermissionMonitor(Context context, INetd netd) {
         this(context, netd, new Dependencies());
     }
 
@@ -161,7 +195,7 @@
             return;
         }
 
-        SparseIntArray netdPermsUids = new SparseIntArray();
+        final SparseArray<UidNetdPermissionInfo> netdPermsUids = new SparseArray<>();
 
         for (PackageInfo app : apps) {
             int uid = app.applicationInfo != null ? app.applicationInfo.uid : INVALID_UID;
@@ -183,9 +217,13 @@
                 }
             }
 
+            // Skip already checked uid.
+            if (netdPermsUids.get(uid) != null) continue;
+
             //TODO: unify the management of the permissions into one codepath.
-            final int otherNetdPerms = getNetdPermissionMask(uid);
-            netdPermsUids.put(uid, netdPermsUids.get(uid) | otherNetdPerms);
+            final UidNetdPermissionInfo permInfo =
+                    new UidNetdPermissionInfo(getNetdPermissionMask(uid));
+            netdPermsUids.put(uid, permInfo);
         }
 
         List<UserInfo> users = mUserManager.getUsers(true);  // exclude dying users
@@ -207,7 +245,10 @@
                         ? PERMISSION_UPDATE_DEVICE_STATS : 0;
                 netdPermission |= perms.contains(INTERNET) ? PERMISSION_INTERNET : 0;
             }
-            netdPermsUids.put(uid, netdPermsUids.get(uid) | netdPermission);
+            final UidNetdPermissionInfo permInfo = netdPermsUids.get(uid);
+            netdPermsUids.put(uid, permInfo != null
+                    ? permInfo.plusNetdPermissions(netdPermission)
+                    : new UidNetdPermissionInfo(netdPermission));
         }
         log("Users: " + mUsers.size() + ", Apps: " + mApps.size());
         update(mUsers, mApps, true);
@@ -341,15 +382,15 @@
         return currentPermission;
     }
 
-    private int getPermissionForUid(final int uid) {
+    private UidNetdPermissionInfo getPermissionForUid(final int uid) {
         // Check all the packages for this UID. The UID has the permission if any of the
         // packages in it has the permission.
         final String[] packages = mPackageManager.getPackagesForUid(uid);
         if (packages == null || packages.length <= 0) {
             // The last package of this uid is removed from device. Clean the package up.
-            return PERMISSION_UNINSTALLED;
+            return new UidNetdPermissionInfo(PERMISSION_UNINSTALLED);
         }
-        return getNetdPermissionMask(uid);
+        return new UidNetdPermissionInfo(getNetdPermissionMask(uid));
     }
 
     /**
@@ -599,28 +640,28 @@
      * permission information to netd.
      *
      * @param uid the app uid of the package installed
-     * @param permissions the permissions the app requested and netd cares about.
+     * @param permissionInfo the permission info of given uid.
      *
      * @hide
      */
     @VisibleForTesting
-    void sendPackagePermissionsForUid(int uid, int permissions) {
-        SparseIntArray netdPermissionsAppIds = new SparseIntArray();
-        netdPermissionsAppIds.put(uid, permissions);
-        sendPackagePermissionsToNetd(netdPermissionsAppIds);
+    void sendPackagePermissionsForUid(int uid, UidNetdPermissionInfo permissionInfo) {
+        final SparseArray<UidNetdPermissionInfo> uidsPermInfo = new SparseArray<>();
+        uidsPermInfo.put(uid, permissionInfo);
+        sendPackagePermissionsToNetd(uidsPermInfo);
     }
 
     /**
      * Called by packageManagerService to send IPC to netd. Grant or revoke the INTERNET
      * and/or UPDATE_DEVICE_STATS permission of the uids in array.
      *
-     * @param netdPermissionsAppIds integer pairs of uids and the permission granted to it. If the
-     * permission is 0, revoke all permissions of that uid.
-     *
+     * @param uidsPermInfo permission info array generated from each uid. If the uid permission is
+     *                     PERMISSION_NONE or PERMISSION_UNINSTALLED, revoke all permissions of that
+     *                     uid.
      * @hide
      */
     @VisibleForTesting
-    void sendPackagePermissionsToNetd(SparseIntArray netdPermissionsAppIds) {
+    void sendPackagePermissionsToNetd(final SparseArray<UidNetdPermissionInfo> uidsPermInfo) {
         if (mNetd == null) {
             Log.e(TAG, "Failed to get the netd service");
             return;
@@ -630,26 +671,20 @@
         ArrayList<Integer> updateStatsPermissionAppIds = new ArrayList<>();
         ArrayList<Integer> noPermissionAppIds = new ArrayList<>();
         ArrayList<Integer> uninstalledAppIds = new ArrayList<>();
-        for (int i = 0; i < netdPermissionsAppIds.size(); i++) {
-            int permissions = netdPermissionsAppIds.valueAt(i);
-            switch(permissions) {
-                case (PERMISSION_INTERNET | PERMISSION_UPDATE_DEVICE_STATS):
-                    allPermissionAppIds.add(netdPermissionsAppIds.keyAt(i));
-                    break;
-                case PERMISSION_INTERNET:
-                    internetPermissionAppIds.add(netdPermissionsAppIds.keyAt(i));
-                    break;
-                case PERMISSION_UPDATE_DEVICE_STATS:
-                    updateStatsPermissionAppIds.add(netdPermissionsAppIds.keyAt(i));
-                    break;
-                case PERMISSION_NONE:
-                    noPermissionAppIds.add(netdPermissionsAppIds.keyAt(i));
-                    break;
-                case PERMISSION_UNINSTALLED:
-                    uninstalledAppIds.add(netdPermissionsAppIds.keyAt(i));
-                default:
-                    Log.e(TAG, "unknown permission type: " + permissions + "for uid: "
-                            + netdPermissionsAppIds.keyAt(i));
+        for (int i = 0; i < uidsPermInfo.size(); i++) {
+            final int uid = uidsPermInfo.keyAt(i);
+            final UidNetdPermissionInfo permInfo = uidsPermInfo.valueAt(i);
+            if (permInfo.hasNetdPermissions(
+                    PERMISSION_INTERNET | PERMISSION_UPDATE_DEVICE_STATS)) {
+                allPermissionAppIds.add(uid);
+            } else if (permInfo.hasNetdPermissions(PERMISSION_INTERNET)) {
+                internetPermissionAppIds.add(uid);
+            } else if (permInfo.hasNetdPermissions(PERMISSION_UPDATE_DEVICE_STATS)) {
+                updateStatsPermissionAppIds.add(uid);
+            } else if (permInfo.isPackageUninstalled()) {
+                uninstalledAppIds.add(uid);
+            } else {
+                noPermissionAppIds.add(uid);
             }
         }
         try {
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 1c93d4e..5484bfc 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -153,8 +153,8 @@
     private static final boolean LOGD = true;
 
     // Length of time (in milliseconds) that an app hosting an always-on VPN is placed on
-    // the device idle whitelist during service launch and VPN bootstrap.
-    private static final long VPN_LAUNCH_IDLE_WHITELIST_DURATION_MS = 60 * 1000;
+    // the device idle allowlist during service launch and VPN bootstrap.
+    private static final long VPN_LAUNCH_IDLE_ALLOWLIST_DURATION_MS = 60 * 1000;
 
     // Settings for how much of the address space should be routed so that Vpn considers
     // "most" of the address space is routed. This is used to determine whether this Vpn
@@ -180,7 +180,8 @@
     // This is taken as a total of IPv4 + IPV6 routes for simplicity, but the algorithm
     // is actually O(n²)+O(n²).
     private static final int MAX_ROUTES_TO_EVALUATE = 150;
-
+    private static final String LOCKDOWN_ALLOWLIST_SETTING_NAME =
+            Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST;
     /**
      * Largest profile size allowable for Platform VPNs.
      *
@@ -236,7 +237,7 @@
      * Set of packages in addition to the VPN app itself that can access the network directly when
      * VPN is not connected even if {@code mLockdown} is set.
      */
-    private @NonNull List<String> mLockdownWhitelist = Collections.emptyList();
+    private @NonNull List<String> mLockdownAllowlist = Collections.emptyList();
 
      /**
      * A memory of what UIDs this class told netd to block for the lockdown feature.
@@ -520,7 +521,7 @@
             }
         }
         if (!hadUnderlyingNetworks) {
-            // No idea what the underlying networks are; assume sane defaults
+            // No idea what the underlying networks are; assume the safer defaults
             metered = true;
             roaming = false;
             congested = false;
@@ -653,18 +654,18 @@
      *
      * @param packageName the package to designate as always-on VPN supplier.
      * @param lockdown whether to prevent traffic outside of a VPN, for example while connecting.
-     * @param lockdownWhitelist packages to be whitelisted from lockdown.
+     * @param lockdownAllowlist packages to be allowed from lockdown.
      * @param keyStore the Keystore instance to use for checking of PlatformVpnProfile(s)
      * @return {@code true} if the package has been set as always-on, {@code false} otherwise.
      */
     public synchronized boolean setAlwaysOnPackage(
             @Nullable String packageName,
             boolean lockdown,
-            @Nullable List<String> lockdownWhitelist,
+            @Nullable List<String> lockdownAllowlist,
             @NonNull KeyStore keyStore) {
         enforceControlPermissionOrInternalCaller();
 
-        if (setAlwaysOnPackageInternal(packageName, lockdown, lockdownWhitelist, keyStore)) {
+        if (setAlwaysOnPackageInternal(packageName, lockdown, lockdownAllowlist, keyStore)) {
             saveAlwaysOnPackage();
             return true;
         }
@@ -679,7 +680,7 @@
      *
      * @param packageName the package to designate as always-on VPN supplier.
      * @param lockdown whether to prevent traffic outside of a VPN, for example while connecting.
-     * @param lockdownWhitelist packages to be whitelisted from lockdown. This is only used if
+     * @param lockdownAllowlist packages to be allowed to bypass lockdown. This is only used if
      *     {@code lockdown} is {@code true}. Packages must not contain commas.
      * @param keyStore the system keystore instance to check for profiles
      * @return {@code true} if the package has been set as always-on, {@code false} otherwise.
@@ -687,16 +688,16 @@
     @GuardedBy("this")
     private boolean setAlwaysOnPackageInternal(
             @Nullable String packageName, boolean lockdown,
-            @Nullable List<String> lockdownWhitelist, @NonNull KeyStore keyStore) {
+            @Nullable List<String> lockdownAllowlist, @NonNull KeyStore keyStore) {
         if (VpnConfig.LEGACY_VPN.equals(packageName)) {
             Log.w(TAG, "Not setting legacy VPN \"" + packageName + "\" as always-on.");
             return false;
         }
 
-        if (lockdownWhitelist != null) {
-            for (String pkg : lockdownWhitelist) {
+        if (lockdownAllowlist != null) {
+            for (String pkg : lockdownAllowlist) {
                 if (pkg.contains(",")) {
-                    Log.w(TAG, "Not setting always-on vpn, invalid whitelisted package: " + pkg);
+                    Log.w(TAG, "Not setting always-on vpn, invalid allowed package: " + pkg);
                     return false;
                 }
             }
@@ -724,8 +725,8 @@
         }
 
         mLockdown = (mAlwaysOn && lockdown);
-        mLockdownWhitelist = (mLockdown && lockdownWhitelist != null)
-                ? Collections.unmodifiableList(new ArrayList<>(lockdownWhitelist))
+        mLockdownAllowlist = (mLockdown && lockdownAllowlist != null)
+                ? Collections.unmodifiableList(new ArrayList<>(lockdownAllowlist))
                 : Collections.emptyList();
 
         if (isCurrentPreparedPackage(packageName)) {
@@ -754,10 +755,10 @@
     }
 
     /**
-     * @return an immutable list of packages whitelisted from always-on VPN lockdown.
+     * @return an immutable list of packages allowed to bypass always-on VPN lockdown.
      */
-    public synchronized List<String> getLockdownWhitelist() {
-        return mLockdown ? mLockdownWhitelist : null;
+    public synchronized List<String> getLockdownAllowlist() {
+        return mLockdown ? mLockdownAllowlist : null;
     }
 
     /**
@@ -772,8 +773,8 @@
             mSystemServices.settingsSecurePutIntForUser(Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN,
                     (mAlwaysOn && mLockdown ? 1 : 0), mUserHandle);
             mSystemServices.settingsSecurePutStringForUser(
-                    Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST,
-                    String.join(",", mLockdownWhitelist), mUserHandle);
+                    LOCKDOWN_ALLOWLIST_SETTING_NAME,
+                    String.join(",", mLockdownAllowlist), mUserHandle);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -788,12 +789,12 @@
                     Settings.Secure.ALWAYS_ON_VPN_APP, mUserHandle);
             final boolean alwaysOnLockdown = mSystemServices.settingsSecureGetIntForUser(
                     Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN, 0 /*default*/, mUserHandle) != 0;
-            final String whitelistString = mSystemServices.settingsSecureGetStringForUser(
-                    Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST, mUserHandle);
-            final List<String> whitelistedPackages = TextUtils.isEmpty(whitelistString)
-                    ? Collections.emptyList() : Arrays.asList(whitelistString.split(","));
+            final String allowlistString = mSystemServices.settingsSecureGetStringForUser(
+                    LOCKDOWN_ALLOWLIST_SETTING_NAME, mUserHandle);
+            final List<String> allowedPackages = TextUtils.isEmpty(allowlistString)
+                    ? Collections.emptyList() : Arrays.asList(allowlistString.split(","));
             setAlwaysOnPackageInternal(
-                    alwaysOnPackage, alwaysOnLockdown, whitelistedPackages, keyStore);
+                    alwaysOnPackage, alwaysOnLockdown, allowedPackages, keyStore);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -849,7 +850,7 @@
             DeviceIdleInternal idleController =
                     LocalServices.getService(DeviceIdleInternal.class);
             idleController.addPowerSaveTempWhitelistApp(Process.myUid(), alwaysOnPackage,
-                    VPN_LAUNCH_IDLE_WHITELIST_DURATION_MS, mUserHandle, false, "vpn");
+                    VPN_LAUNCH_IDLE_ALLOWLIST_DURATION_MS, mUserHandle, false, "vpn");
 
             // Start the VPN service declared in the app's manifest.
             Intent serviceIntent = new Intent(VpnConfig.SERVICE_INTERFACE);
@@ -1212,7 +1213,7 @@
         // applications have changed. Consider diffing UID ranges and only applying the delta.
         if (!Objects.equals(oldConfig.allowedApplications, mConfig.allowedApplications) ||
                 !Objects.equals(oldConfig.disallowedApplications, mConfig.disallowedApplications)) {
-            Log.i(TAG, "Handover not possible due to changes to whitelisted/blacklisted apps");
+            Log.i(TAG, "Handover not possible due to changes to allowed/denied apps");
             return false;
         }
 
@@ -1440,13 +1441,13 @@
      * associated with one user, and any restricted profiles attached to that user.
      *
      * <p>If one of {@param allowedApplications} or {@param disallowedApplications} is provided,
-     * the UID ranges will match the app whitelist or blacklist specified there. Otherwise, all UIDs
+     * the UID ranges will match the app list specified there. Otherwise, all UIDs
      * in each user and profile will be included.
      *
      * @param userHandle The userId to create UID ranges for along with any of its restricted
      *                   profiles.
-     * @param allowedApplications (optional) whitelist of applications to include.
-     * @param disallowedApplications (optional) blacklist of applications to exclude.
+     * @param allowedApplications (optional) List of applications to allow.
+     * @param disallowedApplications (optional) List of applications to deny.
      */
     @VisibleForTesting
     Set<UidRange> createUserAndRestrictedProfilesRanges(@UserIdInt int userHandle,
@@ -1480,13 +1481,13 @@
      * associated with one user.
      *
      * <p>If one of {@param allowedApplications} or {@param disallowedApplications} is provided,
-     * the UID ranges will match the app whitelist or blacklist specified there. Otherwise, all UIDs
+     * the UID ranges will match the app allowlist or denylist specified there. Otherwise, all UIDs
      * in the user will be included.
      *
      * @param ranges {@link Set} of {@link UidRange}s to which to add.
      * @param userHandle The userId to add to {@param ranges}.
-     * @param allowedApplications (optional) whitelist of applications to include.
-     * @param disallowedApplications (optional) blacklist of applications to exclude.
+     * @param allowedApplications (optional) allowlist of applications to include.
+     * @param disallowedApplications (optional) denylist of applications to exclude.
      */
     @VisibleForTesting
     void addUserToRanges(@NonNull Set<UidRange> ranges, @UserIdInt int userHandle,
@@ -1608,7 +1609,7 @@
 
     /**
      * Restricts network access from all UIDs affected by this {@link Vpn}, apart from the VPN
-     * service app itself and whitelisted packages, to only sockets that have had {@code protect()}
+     * service app itself and allowed packages, to only sockets that have had {@code protect()}
      * called on them. All non-VPN traffic is blocked via a {@code PROHIBIT} response from the
      * kernel.
      *
@@ -1630,7 +1631,7 @@
         if (isNullOrLegacyVpn(mPackage)) {
             exemptedPackages = null;
         } else {
-            exemptedPackages = new ArrayList<>(mLockdownWhitelist);
+            exemptedPackages = new ArrayList<>(mLockdownAllowlist);
             exemptedPackages.add(mPackage);
         }
         final Set<UidRange> rangesToTellNetdToRemove = new ArraySet<>(mBlockedUidsAsToldToNetd);
@@ -1675,7 +1676,7 @@
      * Tell netd to add or remove a list of {@link UidRange}s to the list of UIDs that are only
      * allowed to make connections through sockets that have had {@code protect()} called on them.
      *
-     * @param enforce {@code true} to add to the blacklist, {@code false} to remove.
+     * @param enforce {@code true} to add to the denylist, {@code false} to remove.
      * @param ranges {@link Collection} of {@link UidRange}s to add (if {@param enforce} is
      *               {@code true}) or to remove.
      * @return {@code true} if all of the UIDs were added/removed. {@code false} otherwise,
diff --git a/services/core/java/com/android/server/gpu/GpuService.java b/services/core/java/com/android/server/gpu/GpuService.java
index c0617ca..54794fe 100644
--- a/services/core/java/com/android/server/gpu/GpuService.java
+++ b/services/core/java/com/android/server/gpu/GpuService.java
@@ -65,7 +65,7 @@
 
     private static final String PROD_DRIVER_PROPERTY = "ro.gfx.driver.0";
     private static final String DEV_DRIVER_PROPERTY = "ro.gfx.driver.1";
-    private static final String GAME_DRIVER_ALLOWLIST_FILENAME = "allowlist.txt";
+    private static final String UPDATABLE_DRIVER_PRODUCTION_ALLOWLIST_FILENAME = "allowlist.txt";
     private static final int BASE64_FLAGS = Base64.NO_PADDING | Base64.NO_WRAP;
 
     private final Context mContext;
@@ -77,7 +77,7 @@
     private final boolean mHasProdDriver;
     private final boolean mHasDevDriver;
     private ContentResolver mContentResolver;
-    private long mGameDriverVersionCode;
+    private long mProdDriverVersionCode;
     private SettingsObserver mSettingsObserver;
     private DeviceConfigListener mDeviceConfigListener;
     @GuardedBy("mLock")
@@ -88,7 +88,7 @@
 
         mContext = context;
         mProdDriverPackageName = SystemProperties.get(PROD_DRIVER_PROPERTY);
-        mGameDriverVersionCode = -1;
+        mProdDriverVersionCode = -1;
         mDevDriverPackageName = SystemProperties.get(DEV_DRIVER_PROPERTY);
         mPackageManager = context.getPackageManager();
         mHasProdDriver = !TextUtils.isEmpty(mProdDriverPackageName);
@@ -117,20 +117,20 @@
             }
             mSettingsObserver = new SettingsObserver();
             mDeviceConfigListener = new DeviceConfigListener();
-            fetchGameDriverPackageProperties();
+            fetchProductionDriverPackageProperties();
             processDenylists();
             setDenylist();
-            fetchDeveloperDriverPackageProperties();
+            fetchPrereleaseDriverPackageProperties();
         }
     }
 
     private final class SettingsObserver extends ContentObserver {
-        private final Uri mGameDriverDenylistsUri =
-                Settings.Global.getUriFor(Settings.Global.GAME_DRIVER_DENYLISTS);
+        private final Uri mProdDriverDenylistsUri =
+                Settings.Global.getUriFor(Settings.Global.UPDATABLE_DRIVER_PRODUCTION_DENYLISTS);
 
         SettingsObserver() {
             super(new Handler());
-            mContentResolver.registerContentObserver(mGameDriverDenylistsUri, false, this,
+            mContentResolver.registerContentObserver(mProdDriverDenylistsUri, false, this,
                     UserHandle.USER_ALL);
         }
 
@@ -140,7 +140,7 @@
                 return;
             }
 
-            if (mGameDriverDenylistsUri.equals(uri)) {
+            if (mProdDriverDenylistsUri.equals(uri)) {
                 processDenylists();
                 setDenylist();
             }
@@ -157,9 +157,11 @@
         @Override
         public void onPropertiesChanged(Properties properties) {
             synchronized (mDeviceConfigLock) {
-                if (properties.getKeyset().contains(Settings.Global.GAME_DRIVER_DENYLISTS)) {
+                if (properties.getKeyset().contains(
+                            Settings.Global.UPDATABLE_DRIVER_PRODUCTION_DENYLISTS)) {
                     parseDenylists(
-                            properties.getString(Settings.Global.GAME_DRIVER_DENYLISTS, ""));
+                            properties.getString(
+                                    Settings.Global.UPDATABLE_DRIVER_PRODUCTION_DENYLISTS, ""));
                     setDenylist();
                 }
             }
@@ -186,10 +188,10 @@
                 case ACTION_PACKAGE_CHANGED:
                 case ACTION_PACKAGE_REMOVED:
                     if (isProdDriver) {
-                        fetchGameDriverPackageProperties();
+                        fetchProductionDriverPackageProperties();
                         setDenylist();
                     } else if (isDevDriver) {
-                        fetchDeveloperDriverPackageProperties();
+                        fetchPrereleaseDriverPackageProperties();
                     }
                     break;
                 default:
@@ -218,7 +220,7 @@
         }
     }
 
-    private void fetchGameDriverPackageProperties() {
+    private void fetchProductionDriverPackageProperties() {
         final ApplicationInfo driverInfo;
         try {
             driverInfo = mPackageManager.getApplicationInfo(mProdDriverPackageName,
@@ -241,15 +243,16 @@
 
         // Reset the allowlist.
         Settings.Global.putString(mContentResolver,
-                                  Settings.Global.GAME_DRIVER_ALLOWLIST, "");
-        mGameDriverVersionCode = driverInfo.longVersionCode;
+                                  Settings.Global.UPDATABLE_DRIVER_PRODUCTION_ALLOWLIST, "");
+        mProdDriverVersionCode = driverInfo.longVersionCode;
 
         try {
             final Context driverContext = mContext.createPackageContext(mProdDriverPackageName,
                                                                         Context.CONTEXT_RESTRICTED);
 
-            assetToSettingsGlobal(mContext, driverContext, GAME_DRIVER_ALLOWLIST_FILENAME,
-                    Settings.Global.GAME_DRIVER_ALLOWLIST, ",");
+            assetToSettingsGlobal(mContext, driverContext,
+                    UPDATABLE_DRIVER_PRODUCTION_ALLOWLIST_FILENAME,
+                    Settings.Global.UPDATABLE_DRIVER_PRODUCTION_ALLOWLIST, ",");
         } catch (PackageManager.NameNotFoundException e) {
             if (DEBUG) {
                 Slog.w(TAG, "driver package '" + mProdDriverPackageName + "' not installed");
@@ -259,11 +262,11 @@
 
     private void processDenylists() {
         String base64String = DeviceConfig.getProperty(DeviceConfig.NAMESPACE_GAME_DRIVER,
-                Settings.Global.GAME_DRIVER_DENYLISTS);
+                Settings.Global.UPDATABLE_DRIVER_PRODUCTION_DENYLISTS);
         if (base64String == null) {
             base64String =
                     Settings.Global.getString(mContentResolver,
-                                              Settings.Global.GAME_DRIVER_DENYLISTS);
+                            Settings.Global.UPDATABLE_DRIVER_PRODUCTION_DENYLISTS);
         }
         parseDenylists(base64String != null ? base64String : "");
     }
@@ -288,16 +291,16 @@
 
     private void setDenylist() {
         Settings.Global.putString(mContentResolver,
-                                  Settings.Global.GAME_DRIVER_DENYLIST, "");
+                                  Settings.Global.UPDATABLE_DRIVER_PRODUCTION_DENYLIST, "");
         synchronized (mLock) {
             if (mDenylists == null) {
                 return;
             }
             List<Denylist> denylists = mDenylists.getDenylistsList();
             for (Denylist denylist : denylists) {
-                if (denylist.getVersionCode() == mGameDriverVersionCode) {
+                if (denylist.getVersionCode() == mProdDriverVersionCode) {
                     Settings.Global.putString(mContentResolver,
-                            Settings.Global.GAME_DRIVER_DENYLIST,
+                            Settings.Global.UPDATABLE_DRIVER_PRODUCTION_DENYLIST,
                             String.join(",", denylist.getPackageNamesList()));
                     return;
                 }
@@ -305,7 +308,7 @@
         }
     }
 
-    private void fetchDeveloperDriverPackageProperties() {
+    private void fetchPrereleaseDriverPackageProperties() {
         final ApplicationInfo driverInfo;
         try {
             driverInfo = mPackageManager.getApplicationInfo(mDevDriverPackageName,
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index 05cf40a..3ac95d7 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -178,7 +178,7 @@
             };
 
     /**
-     * @return Global instance if exists.  Otherwise, a dummy no-op instance.
+     * @return Global instance if exists.  Otherwise, a fallback no-op instance.
      */
     @NonNull
     public static InputMethodManagerInternal get() {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 3cd70fe..9ab410d 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2213,8 +2213,7 @@
      * @param client {@link android.os.Binder} proxy that is associated with the singleton instance
      *               of {@link android.view.inputmethod.InputMethodManager} that runs on the client
      *               process
-     * @param inputContext communication channel for the dummy
-     *                     {@link android.view.inputmethod.InputConnection}
+     * @param inputContext communication channel for the fallback {@link InputConnection}
      * @param selfReportedDisplayId self-reported display ID to which the client is associated.
      *                              Whether the client is still allowed to access to this display
      *                              or not needs to be evaluated every time the client interacts
@@ -3451,9 +3450,9 @@
             return InputBindResult.USER_SWITCHING;
         }
 
-        // Master feature flag that overrides other conditions and forces IME preRendering.
+        // Main feature flag that overrides other conditions and forces IME preRendering.
         if (DEBUG) {
-            Slog.v(TAG, "IME PreRendering MASTER flag: "
+            Slog.v(TAG, "IME PreRendering main flag: "
                     + DebugFlags.FLAG_PRE_RENDER_IME_VIEWS.value() + ", LowRam: " + mIsLowRam);
         }
         // pre-rendering not supported on low-ram devices.
diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
index 937514c..b518eb1 100644
--- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
@@ -1663,7 +1663,7 @@
                 }
 
                 if (editorInfo == null) {
-                    // So-called dummy InputConnection scenario.  For app compatibility, we still
+                    // So-called fallback InputConnection scenario.  For app compatibility, we still
                     // notify this to the IME.
                     switch (clientInfo.mState) {
                         case InputMethodClientState.READY_TO_SEND_FIRST_BIND_RESULT:
diff --git a/services/core/java/com/android/server/location/LocationProviderManager.java b/services/core/java/com/android/server/location/LocationProviderManager.java
index c3532a8..05aa315 100644
--- a/services/core/java/com/android/server/location/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/LocationProviderManager.java
@@ -229,7 +229,7 @@
         // we cache these values because checking/calculating on the fly is more expensive
         private boolean mPermitted;
         private boolean mForeground;
-        @Nullable private LocationRequest mProviderLocationRequest;
+        private LocationRequest mProviderLocationRequest;
         private boolean mIsUsingHighPower;
 
         protected Registration(LocationRequest request, CallerIdentity identity,
@@ -244,6 +244,8 @@
             } else {
                 mWorkSource = identity.addToWorkSource(null);
             }
+
+            mProviderLocationRequest = super.getRequest();
         }
 
         @GuardedBy("mLock")
@@ -313,7 +315,7 @@
 
         @Override
         public final LocationRequest getRequest() {
-            return Objects.requireNonNull(mProviderLocationRequest);
+            return mProviderLocationRequest;
         }
 
         public final boolean isForeground() {
diff --git a/services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java b/services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java
new file mode 100644
index 0000000..e3074db
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java
@@ -0,0 +1,253 @@
+/*
+ * 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.locksettings;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.face.FaceManager;
+import android.hardware.face.FaceSensorProperties;
+import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintSensorProperties;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.ServiceManager;
+import android.service.gatekeeper.IGateKeeperService;
+import android.util.ArraySet;
+import android.util.Slog;
+
+import com.android.internal.widget.VerifyCredentialResponse;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Class that handles biometric-related work in the {@link LockSettingsService} area, for example
+ * resetLockout.
+ */
+@SuppressWarnings("deprecation")
+public class BiometricDeferredQueue {
+    private static final String TAG = "BiometricDeferredQueue";
+
+    @NonNull private final Context mContext;
+    @NonNull private final SyntheticPasswordManager mSpManager;
+    @NonNull private final Handler mHandler;
+    @Nullable private FingerprintManager mFingerprintManager;
+    @Nullable private FaceManager mFaceManager;
+
+    // Entries added by LockSettingsService once a user's synthetic password is known. At this point
+    // things are still keyed by userId.
+    @NonNull private final ArrayList<UserAuthInfo> mPendingResetLockouts;
+
+    /**
+     * Authentication info for a successful user unlock via Synthetic Password. This can be used to
+     * perform multiple operations (e.g. resetLockout for multiple HALs/Sensors) by sending the
+     * Gatekeeper Password to Gatekeer multiple times, each with a sensor-specific challenge.
+     */
+    private static class UserAuthInfo {
+        final int userId;
+        @NonNull final byte[] gatekeeperPassword;
+
+        UserAuthInfo(int userId, @NonNull byte[] gatekeeperPassword) {
+            this.userId = userId;
+            this.gatekeeperPassword = gatekeeperPassword;
+        }
+    }
+
+    /**
+     * Per-authentication callback.
+     */
+    private static class FaceResetLockoutTask implements FaceManager.GenerateChallengeCallback {
+        interface FinishCallback {
+            void onFinished();
+        }
+
+        @NonNull FinishCallback finishCallback;
+        @NonNull FaceManager faceManager;
+        @NonNull SyntheticPasswordManager spManager;
+        @NonNull Set<Integer> sensorIds; // IDs of sensors waiting for challenge
+        @NonNull List<UserAuthInfo> pendingResetLockuts;
+
+        FaceResetLockoutTask(
+                @NonNull FinishCallback finishCallback,
+                @NonNull FaceManager faceManager,
+                @NonNull SyntheticPasswordManager spManager,
+                @NonNull Set<Integer> sensorIds,
+                @NonNull List<UserAuthInfo> pendingResetLockouts) {
+            this.finishCallback = finishCallback;
+            this.faceManager = faceManager;
+            this.spManager = spManager;
+            this.sensorIds = sensorIds;
+            this.pendingResetLockuts = pendingResetLockouts;
+        }
+
+        @Override
+        public void onChallengeInterrupted(int sensorId) {
+            Slog.w(TAG, "Challenge interrupted, sensor: " + sensorId);
+            // Consider re-attempting generateChallenge/resetLockout/revokeChallenge
+            // when onChallengeInterruptFinished is invoked
+        }
+
+        @Override
+        public void onChallengeInterruptFinished(int sensorId) {
+            Slog.w(TAG, "Challenge interrupt finished, sensor: " + sensorId);
+        }
+
+        @Override
+        public void onGenerateChallengeResult(int sensorId, long challenge) {
+            if (!sensorIds.contains(sensorId)) {
+                Slog.e(TAG, "Unknown sensorId received: " + sensorId);
+                return;
+            }
+
+            // Challenge received for a sensor. For each sensor, reset lockout for all users.
+            for (UserAuthInfo userAuthInfo : pendingResetLockuts) {
+                Slog.d(TAG, "Resetting face lockout for sensor: " + sensorId
+                        + ", user: " + userAuthInfo.userId);
+                final VerifyCredentialResponse response = spManager.verifyChallengeInternal(
+                        getGatekeeperService(), userAuthInfo.gatekeeperPassword, challenge,
+                        userAuthInfo.userId);
+                if (response.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) {
+                    Slog.wtf(TAG, "VerifyChallenge failed, response: "
+                            + response.getResponseCode());
+                }
+                faceManager.resetLockout(sensorId, userAuthInfo.userId,
+                        response.getGatekeeperHAT());
+            }
+
+            sensorIds.remove(sensorId);
+            faceManager.revokeChallenge(sensorId);
+
+            if (sensorIds.isEmpty()) {
+                Slog.d(TAG, "Done requesting resetLockout for all face sensors");
+                finishCallback.onFinished();
+            }
+        }
+
+        synchronized IGateKeeperService getGatekeeperService() {
+            final IBinder service = ServiceManager.getService(Context.GATEKEEPER_SERVICE);
+            if (service == null) {
+                Slog.e(TAG, "Unable to acquire GateKeeperService");
+                return null;
+            }
+            return IGateKeeperService.Stub.asInterface(service);
+        }
+    }
+
+    @Nullable private FaceResetLockoutTask mFaceResetLockoutTask;
+
+    private final FaceResetLockoutTask.FinishCallback mFaceFinishCallback = () -> {
+        mFaceResetLockoutTask = null;
+    };
+
+    BiometricDeferredQueue(@NonNull Context context, @NonNull SyntheticPasswordManager spManager,
+            @NonNull Handler handler) {
+        mContext = context;
+        mSpManager = spManager;
+        mHandler = handler;
+        mPendingResetLockouts = new ArrayList<>();
+    }
+
+    public void systemReady(@Nullable FingerprintManager fingerprintManager,
+            @Nullable FaceManager faceManager) {
+        mFingerprintManager = fingerprintManager;
+        mFaceManager = faceManager;
+    }
+
+    /**
+     * Adds a request for resetLockout on all biometric sensors for the user specified. The queue
+     * owner must invoke {@link #processPendingLockoutResets()} at some point to kick off the
+     * operations.
+     *
+     * Note that this should only ever be invoked for successful authentications, otherwise it will
+     * consume a Gatekeeper authentication attempt and potentially wipe the user/device.
+     *
+     * @param userId The user that the operation will apply for.
+     * @param gatekeeperPassword The Gatekeeper Password
+     */
+    void addPendingLockoutResetForUser(int userId, @NonNull byte[] gatekeeperPassword) {
+        mHandler.post(() -> {
+            Slog.d(TAG, "addPendingLockoutResetForUser: " + userId);
+            mPendingResetLockouts.add(new UserAuthInfo(userId, gatekeeperPassword));
+        });
+    }
+
+    void processPendingLockoutResets() {
+        mHandler.post(() -> {
+            Slog.d(TAG, "processPendingLockoutResets: " + mPendingResetLockouts.size());
+            processPendingLockoutsForFingerprint(new ArrayList<>(mPendingResetLockouts));
+            processPendingLockoutsForFace(new ArrayList<>(mPendingResetLockouts));
+            mPendingResetLockouts.clear();
+        });
+    }
+
+    private void processPendingLockoutsForFingerprint(List<UserAuthInfo> pendingResetLockouts) {
+        if (mFingerprintManager != null) {
+            final List<FingerprintSensorProperties> fingerprintSensorProperties =
+                    mFingerprintManager.getSensorProperties();
+            for (FingerprintSensorProperties prop : fingerprintSensorProperties) {
+                if (!prop.resetLockoutRequiresHardwareAuthToken) {
+                    for (UserAuthInfo user : pendingResetLockouts) {
+                        mFingerprintManager.resetLockout(prop.sensorId, user.userId,
+                                null /* hardwareAuthToken */);
+                    }
+                } else {
+                    Slog.e(TAG, "Fingerprint resetLockout with HAT not supported yet");
+                    // TODO(b/152414803): Implement this when resetLockout is implemented below
+                    //  the framework.
+                }
+            }
+        }
+    }
+
+    /**
+     * For devices on {@link android.hardware.biometrics.face.V1_0} which only support a single
+     * in-flight challenge, we generate a single challenge to reset lockout for all profiles. This
+     * hopefully reduces/eliminates issues such as overwritten challenge, incorrectly revoked
+     * challenge, or other race conditions.
+     *
+     * TODO(b/162965646) This logic can be avoided if multiple in-flight challenges are supported.
+     *  Though it will need to continue to exist to support existing HIDLs, each profile that
+     *  requires resetLockout could have its own challenge, and the `mPendingResetLockouts` queue
+     *  can be avoided.
+     */
+    private void processPendingLockoutsForFace(List<UserAuthInfo> pendingResetLockouts) {
+        if (mFaceManager != null) {
+            if (mFaceResetLockoutTask != null) {
+                // This code will need to be updated if this problem ever occurs.
+                Slog.w(TAG, "mFaceGenerateChallengeCallback not null, previous operation may be"
+                        + " stuck");
+            }
+            final List<FaceSensorProperties> faceSensorProperties =
+                    mFaceManager.getSensorProperties();
+            final Set<Integer> sensorIds = new ArraySet<>();
+            for (FaceSensorProperties prop : faceSensorProperties) {
+                sensorIds.add(prop.sensorId);
+            }
+
+            mFaceResetLockoutTask = new FaceResetLockoutTask(mFaceFinishCallback, mFaceManager,
+                    mSpManager, sensorIds, pendingResetLockouts);
+            for (final FaceSensorProperties prop : faceSensorProperties) {
+                // Generate a challenge for each sensor. The challenge does not need to be
+                // per-user, since the HAT returned by gatekeeper contains userId.
+                mFaceManager.generateChallenge(prop.sensorId, mFaceResetLockoutTask);
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index d6e37bac..0044d89 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -37,7 +37,6 @@
 import static com.android.internal.widget.LockPatternUtils.frpCredentialEnabled;
 import static com.android.internal.widget.LockPatternUtils.userOwnsFrpCredential;
 
-import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -187,23 +186,6 @@
     private static final String SYNTHETIC_PASSWORD_UPDATE_TIME_KEY = "sp-handle-ts";
     private static final String USER_SERIAL_NUMBER_KEY = "serial-number";
 
-    // TODO (b/145978626) LockSettingsService no longer accepts challenges in the verifyCredential
-    //  paths. These are temporarily left around to ensure that resetLockout works. It will be
-    //  removed once resetLockout is compartmentalized.
-    // No challenge provided
-    private static final int CHALLENGE_NONE = 0;
-    // Challenge was provided from the external caller (non-LockSettingsService)
-    private static final int CHALLENGE_FROM_CALLER = 1;
-    // Challenge was generated from within LockSettingsService, for resetLockout. When challenge
-    // type is set to internal, LSS will revokeChallenge after all profiles for that user are
-    // unlocked.
-    private static final int CHALLENGE_INTERNAL = 2;
-
-    @IntDef({CHALLENGE_NONE,
-            CHALLENGE_FROM_CALLER,
-            CHALLENGE_INTERNAL})
-    @interface ChallengeType {}
-
     // Order of holding lock: mSeparateChallengeLock -> mSpManager -> this
     // Do not call into ActivityManager while holding mSpManager lock.
     private final Object mSeparateChallengeLock = new Object();
@@ -219,6 +201,7 @@
     protected final LockSettingsStorage mStorage;
     private final LockSettingsStrongAuth mStrongAuth;
     private final SynchronizedStrongAuthTracker mStrongAuthTracker;
+    private final BiometricDeferredQueue mBiometricDeferredQueue;
 
     private final NotificationManager mNotificationManager;
     private final UserManager mUserManager;
@@ -321,15 +304,6 @@
         }
     }
 
-    private class PendingResetLockout {
-        final int mUserId;
-        final byte[] mHAT;
-        PendingResetLockout(int userId, byte[] hat) {
-            mUserId = userId;
-            mHAT = hat;
-        }
-    }
-
     private LockscreenCredential generateRandomProfilePassword() {
         byte[] randomLockSeed = new byte[] {};
         try {
@@ -588,6 +562,7 @@
 
         mSpManager = injector.getSyntheticPasswordManager(mStorage);
         mManagedProfilePasswordCache = injector.getManagedProfilePasswordCache();
+        mBiometricDeferredQueue = new BiometricDeferredQueue(mContext, mSpManager, mHandler);
 
         mRebootEscrowManager = injector.getRebootEscrowManager(new RebootEscrowCallbacks(),
                 mStorage);
@@ -740,8 +715,7 @@
             // If boot took too long and the password in vold got expired, parent keystore will
             // be still locked, we ignore this case since the user will be prompted to unlock
             // the device after boot.
-            unlockChildProfile(userId, true /* ignoreUserNotAuthenticated */,
-                    CHALLENGE_NONE, 0 /* challenge */, null /* resetLockouts */);
+            unlockChildProfile(userId, true /* ignoreUserNotAuthenticated */);
         }
     }
 
@@ -830,6 +804,8 @@
         mRebootEscrowManager.loadRebootEscrowDataIfAvailable();
         // TODO: maybe skip this for split system user mode.
         mStorage.prefetchUser(UserHandle.USER_SYSTEM);
+        mBiometricDeferredQueue.systemReady(mInjector.getFingerprintManager(),
+                mInjector.getFaceManager());
     }
 
     private void getAuthSecretHal() {
@@ -1306,13 +1282,10 @@
         return credential;
     }
 
-    private void unlockChildProfile(int profileHandle, boolean ignoreUserNotAuthenticated,
-            @ChallengeType int challengeType, long challenge,
-            @Nullable ArrayList<PendingResetLockout> resetLockouts) {
+    private void unlockChildProfile(int profileHandle, boolean ignoreUserNotAuthenticated) {
         try {
             doVerifyCredential(getDecryptedPasswordForTiedProfile(profileHandle),
-                    challengeType, challenge, profileHandle, null /* progressCallback */,
-                    resetLockouts, 0 /* flags */);
+                    profileHandle, null /* progressCallback */, 0 /* flags */);
         } catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException
                 | NoSuchAlgorithmException | NoSuchPaddingException
                 | InvalidAlgorithmParameterException | IllegalBlockSizeException
@@ -1327,10 +1300,6 @@
         }
     }
 
-    private void unlockUser(int userId, byte[] token, byte[] secret) {
-        unlockUser(userId, token, secret, CHALLENGE_NONE, 0 /* challenge */, null);
-    }
-
     /**
      * Unlock the user (both storage and user state) and its associated managed profiles
      * synchronously.
@@ -1339,9 +1308,7 @@
      * can end up calling into other system services to process user unlock request (via
      * {@link com.android.server.SystemServiceManager#unlockUser} </em>
      */
-    private void unlockUser(int userId, byte[] token, byte[] secret,
-            @ChallengeType int challengeType, long challenge,
-            @Nullable ArrayList<PendingResetLockout> resetLockouts) {
+    private void unlockUser(int userId, byte[] token, byte[] secret) {
         Slog.i(TAG, "Unlocking user " + userId + " with secret only, length "
                 + (secret != null ? secret.length : 0));
         // TODO: make this method fully async so we can update UI with progress strings
@@ -1378,6 +1345,9 @@
         }
 
         if (mUserManager.getUserInfo(userId).isManagedProfile()) {
+            if (!hasUnifiedChallenge(userId)) {
+                mBiometricDeferredQueue.processPendingLockoutResets();
+            }
             return;
         }
 
@@ -1388,10 +1358,7 @@
             if (hasUnifiedChallenge(profile.id)) {
                 if (mUserManager.isUserRunning(profile.id)) {
                     // Unlock managed profile with unified lock
-                    // Must pass the challenge on for resetLockout, so it's not over-written, which
-                    // causes LockSettingsService to revokeChallenge inappropriately.
-                    unlockChildProfile(profile.id, false /* ignoreUserNotAuthenticated */,
-                            challengeType, challenge, resetLockouts);
+                    unlockChildProfile(profile.id, false /* ignoreUserNotAuthenticated */);
                 } else {
                     try {
                         // Profile not ready for unlock yet, but decrypt the unified challenge now
@@ -1412,22 +1379,9 @@
                     restoreCallingIdentity(ident);
                 }
             }
-
         }
 
-        if (resetLockouts != null && !resetLockouts.isEmpty()) {
-            mHandler.post(() -> {
-                final BiometricManager bm = mContext.getSystemService(BiometricManager.class);
-                final PackageManager pm = mContext.getPackageManager();
-                for (int i = 0; i < resetLockouts.size(); i++) {
-                    bm.resetLockout(resetLockouts.get(i).mUserId, resetLockouts.get(i).mHAT);
-                }
-                if (challengeType == CHALLENGE_INTERNAL
-                        && pm.hasSystemFeature(PackageManager.FEATURE_FACE)) {
-                    mContext.getSystemService(FaceManager.class).revokeChallenge();
-                }
-            });
-        }
+        mBiometricDeferredQueue.processPendingLockoutResets();
     }
 
     private boolean hasUnifiedChallenge(int userId) {
@@ -1727,8 +1681,7 @@
         setUserKeyProtection(userId, credential, convertResponse(gkResponse));
         fixateNewestUserKeyAuth(userId);
         // Refresh the auth token
-        doVerifyCredential(credential, CHALLENGE_FROM_CALLER, 0, userId,
-                null /* progressCallback */, 0 /* flags */);
+        doVerifyCredential(credential, userId, null /* progressCallback */, 0 /* flags */);
         synchronizeUnifiedWorkChallengeForProfiles(userId, null);
         sendCredentialsOnChangeIfRequired(credential, userId, isLockTiedToParent);
         return true;
@@ -1972,8 +1925,7 @@
             ICheckCredentialProgressCallback progressCallback) {
         checkPasswordReadPermission(userId);
         try {
-            return doVerifyCredential(credential, CHALLENGE_NONE, 0L, userId, progressCallback,
-                    0 /* flags */);
+            return doVerifyCredential(credential, userId, progressCallback, 0 /* flags */);
         } finally {
             scheduleGc();
         }
@@ -1984,10 +1936,8 @@
     public VerifyCredentialResponse verifyCredential(LockscreenCredential credential,
             int userId, int flags) {
         checkPasswordReadPermission(userId);
-
         try {
-            return doVerifyCredential(credential, CHALLENGE_NONE, 0L, userId,
-                    null /* progressCallback */, flags);
+            return doVerifyCredential(credential, userId, null /* progressCallback */, flags);
         } finally {
             scheduleGc();
         }
@@ -2006,32 +1956,17 @@
         return response;
     }
 
-    /**
+    /*
+     * Verify user credential and unlock the user. Fix pattern bug by deprecating the old base zero
+     * format.
      * @param credential User's lockscreen credential
-     * @param challengeType Owner of the challenge
-     * @param challenge Challenge to be wrapped within Gatekeeper's HAT, if the credential is
-     *                  verified
      * @param userId User to verify the credential for
      * @param progressCallback Receive progress callbacks
      * @param flags See {@link LockPatternUtils.VerifyFlag}
      * @return See {@link VerifyCredentialResponse}
      */
     private VerifyCredentialResponse doVerifyCredential(LockscreenCredential credential,
-            @ChallengeType int challengeType, long challenge, int userId,
-            ICheckCredentialProgressCallback progressCallback,
-            @LockPatternUtils.VerifyFlag int flags) {
-        return doVerifyCredential(credential, challengeType, challenge, userId,
-                progressCallback, null /* resetLockouts */, flags);
-    }
-
-    /**
-     * Verify user credential and unlock the user. Fix pattern bug by deprecating the old base zero
-     * format.
-     */
-    private VerifyCredentialResponse doVerifyCredential(LockscreenCredential credential,
-            @ChallengeType int challengeType, long challenge, int userId,
-            ICheckCredentialProgressCallback progressCallback,
-            @Nullable ArrayList<PendingResetLockout> resetLockouts,
+            int userId, ICheckCredentialProgressCallback progressCallback,
             @LockPatternUtils.VerifyFlag int flags) {
         if (credential == null || credential.isNone()) {
             throw new IllegalArgumentException("Credential can't be null or empty");
@@ -2041,9 +1976,10 @@
             Slog.e(TAG, "FRP credential can only be verified prior to provisioning.");
             return VerifyCredentialResponse.ERROR;
         }
-        VerifyCredentialResponse response = null;
-        response = spBasedDoVerifyCredential(credential, challengeType, challenge,
-                userId, progressCallback, resetLockouts, flags);
+
+        VerifyCredentialResponse response = spBasedDoVerifyCredential(credential, userId,
+                progressCallback, flags);
+
         // The user employs synthetic password based credential.
         if (response != null) {
             if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
@@ -2064,8 +2000,7 @@
             return VerifyCredentialResponse.ERROR;
         }
 
-        response = verifyCredential(userId, storedHash, credential,
-                challengeType, challenge, progressCallback);
+        response = verifyCredential(userId, storedHash, credential, progressCallback);
 
         if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
             mStrongAuth.reportSuccessfulStrongAuthUnlock(userId);
@@ -2085,8 +2020,6 @@
         // Unlock parent by using parent's challenge
         final VerifyCredentialResponse parentResponse = doVerifyCredential(
                 credential,
-                CHALLENGE_NONE,
-                0L,
                 parentProfileId,
                 null /* progressCallback */,
                 flags);
@@ -2098,10 +2031,7 @@
         try {
             // Unlock work profile, and work profile with unified lock must use password only
             return doVerifyCredential(getDecryptedPasswordForTiedProfile(userId),
-                    CHALLENGE_NONE,
-                    0L,
-                    userId, null /* progressCallback */,
-                    flags);
+                    userId, null /* progressCallback */, flags);
         } catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException
                 | NoSuchAlgorithmException | NoSuchPaddingException
                 | InvalidAlgorithmParameterException | IllegalBlockSizeException
@@ -2119,8 +2049,7 @@
      * hash to GK.
      */
     private VerifyCredentialResponse verifyCredential(int userId, CredentialHash storedHash,
-            LockscreenCredential credential, @ChallengeType int challengeType, long challenge,
-            ICheckCredentialProgressCallback progressCallback) {
+            LockscreenCredential credential, ICheckCredentialProgressCallback progressCallback) {
         if ((storedHash == null || storedHash.hash.length == 0) && credential.isNone()) {
             // don't need to pass empty credentials to GateKeeper
             return VerifyCredentialResponse.OK;
@@ -2137,7 +2066,7 @@
         GateKeeperResponse gateKeeperResponse;
         try {
             gateKeeperResponse = getGateKeeperService().verifyChallenge(
-                    userId, challenge, storedHash.hash, credential.getCredential());
+                    userId, 0L /* challenge */, storedHash.hash, credential.getCredential());
         } catch (RemoteException e) {
             Slog.e(TAG, "gatekeeper verify failed", e);
             gateKeeperResponse = GateKeeperResponse.ERROR;
@@ -2710,28 +2639,13 @@
     }
 
     private VerifyCredentialResponse spBasedDoVerifyCredential(LockscreenCredential userCredential,
-            @ChallengeType int challengeType, long challenge,
             int userId, ICheckCredentialProgressCallback progressCallback,
-            @Nullable ArrayList<PendingResetLockout> resetLockouts,
             @LockPatternUtils.VerifyFlag int flags) {
-
         final boolean hasEnrolledBiometrics = mInjector.hasEnrolledBiometrics(userId);
 
-        Slog.d(TAG, "spBasedDoVerifyCredential: user=" + userId + " challengeType=" + challengeType
+        Slog.d(TAG, "spBasedDoVerifyCredential: user=" + userId
                 + " hasEnrolledBiometrics=" + hasEnrolledBiometrics);
 
-        final PackageManager pm = mContext.getPackageManager();
-        // TODO: When lockout is handled under the HAL for all biometrics (fingerprint),
-        // we need to generate challenge for each one, have it signed by GK and reset lockout
-        // for each modality.
-        if (challengeType == CHALLENGE_NONE && pm.hasSystemFeature(PackageManager.FEATURE_FACE)
-                && hasEnrolledBiometrics) {
-            // If there are multiple profiles in the same account, ensure we only generate the
-            // challenge once.
-            challengeType = CHALLENGE_INTERNAL;
-            challenge = mContext.getSystemService(FaceManager.class).generateChallengeBlocking();
-        }
-
         final AuthenticationResult authResult;
         VerifyCredentialResponse response;
         final boolean returnGkPw = (flags & VERIFY_FLAG_RETURN_GK_PW) != 0;
@@ -2752,10 +2666,13 @@
 
             // credential has matched
             if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
+                mBiometricDeferredQueue.addPendingLockoutResetForUser(userId,
+                        authResult.authToken.deriveGkPassword());
+
                 // perform verifyChallenge with synthetic password which generates the real GK auth
                 // token and response for the current user
                 response = mSpManager.verifyChallenge(getGateKeeperService(), authResult.authToken,
-                        challenge, userId);
+                        0L /* challenge */, userId);
                 if (response.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) {
                     // This shouldn't really happen: the unwrapping of SP succeeds, but SP doesn't
                     // match the recorded GK password handle.
@@ -2765,15 +2682,7 @@
             }
         }
         if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
-            // Do resetLockout / revokeChallenge when all profiles are unlocked
-            if (hasEnrolledBiometrics) {
-                if (resetLockouts == null) {
-                    resetLockouts = new ArrayList<>();
-                }
-                resetLockouts.add(new PendingResetLockout(userId, response.getGatekeeperHAT()));
-            }
-
-            onCredentialVerified(authResult.authToken, challengeType, challenge, resetLockouts,
+            onCredentialVerified(authResult.authToken,
                     PasswordMetrics.computeForCredential(userCredential), userId);
         } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) {
             if (response.getTimeout() > 0) {
@@ -2789,9 +2698,7 @@
         }
     }
 
-    private void onCredentialVerified(AuthenticationToken authToken,
-            @ChallengeType int challengeType, long challenge,
-            @Nullable ArrayList<PendingResetLockout> resetLockouts, PasswordMetrics metrics,
+    private void onCredentialVerified(AuthenticationToken authToken, PasswordMetrics metrics,
             int userId) {
 
         if (metrics != null) {
@@ -2806,7 +2713,7 @@
 
         {
             final byte[] secret = authToken.deriveDiskEncryptionKey();
-            unlockUser(userId, null, secret, challengeType, challenge, resetLockouts);
+            unlockUser(userId, null, secret);
             Arrays.fill(secret, (byte) 0);
         }
         activateEscrowTokens(authToken, userId);
@@ -3193,11 +3100,8 @@
                 return false;
             }
         }
-        // TODO: Reset biometrics lockout here. Ideally that should be self-contained inside
-        // onCredentialVerified(), which will require some refactoring on the current lockout
-        // reset logic.
 
-        onCredentialVerified(authResult.authToken, CHALLENGE_NONE, 0, null,
+        onCredentialVerified(authResult.authToken,
                 loadPasswordMetrics(authResult.authToken, userId), userId);
         return true;
     }
@@ -3208,8 +3112,7 @@
             if (cred == null) {
                 return false;
             }
-            return doVerifyCredential(cred, CHALLENGE_NONE, 0, userId,
-                    null /* progressCallback */, 0 /* flags */)
+            return doVerifyCredential(cred, userId, null /* progressCallback */, 0 /* flags */)
                     .getResponseCode() == VerifyCredentialResponse.RESPONSE_OK;
         }
     }
@@ -3532,8 +3435,7 @@
             SyntheticPasswordManager.AuthenticationToken
                     authToken = new SyntheticPasswordManager.AuthenticationToken(spVersion);
             authToken.recreateDirectly(syntheticPassword);
-            onCredentialVerified(authToken, CHALLENGE_NONE, 0, null,
-                    loadPasswordMetrics(authToken, userId), userId);
+            onCredentialVerified(authToken, loadPasswordMetrics(authToken, userId), userId);
         }
     }
 }
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index 81d07cc..e9a05a8 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -484,7 +484,7 @@
     public Map<Integer, List<Long>> listSyntheticPasswordHandlesForAllUsers(String stateName) {
         Map<Integer, List<Long>> result = new ArrayMap<>();
         final UserManager um = UserManager.get(mContext);
-        for (UserInfo user : um.getUsers(false)) {
+        for (UserInfo user : um.getUsers()) {
             result.put(user.id, listSyntheticPasswordHandlesForUser(stateName, user.id));
         }
         return result;
diff --git a/services/core/java/com/android/server/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
index 3a4dfaf..0b3cdae 100644
--- a/services/core/java/com/android/server/media/BluetoothRouteProvider.java
+++ b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
@@ -34,6 +34,7 @@
 import android.media.AudioManager;
 import android.media.AudioSystem;
 import android.media.MediaRoute2Info;
+import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Slog;
@@ -55,7 +56,6 @@
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     private static final String HEARING_AID_ROUTE_ID_PREFIX = "HEARING_AID_";
-    private static BluetoothRouteProvider sInstance;
 
     @SuppressWarnings("WeakerAccess") /* synthetic access */
     // Maps hardware address to BluetoothRouteInfo
@@ -79,19 +79,21 @@
     private final BroadcastReceiver mBroadcastReceiver = new BluetoothBroadcastReceiver();
     private final BluetoothProfileListener mProfileListener = new BluetoothProfileListener();
 
-    static synchronized BluetoothRouteProvider getInstance(@NonNull Context context,
+    /**
+     * Create an instance of {@link BluetoothRouteProvider}.
+     * It may return {@code null} if Bluetooth is not supported on this hardware platform.
+     */
+    @Nullable
+    static BluetoothRouteProvider createInstance(@NonNull Context context,
             @NonNull BluetoothRoutesUpdatedListener listener) {
         Objects.requireNonNull(context);
         Objects.requireNonNull(listener);
 
-        if (sInstance == null) {
-            BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
-            if (btAdapter == null) {
-                return null;
-            }
-            sInstance = new BluetoothRouteProvider(context, btAdapter, listener);
+        BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
+        if (btAdapter == null) {
+            return null;
         }
-        return sInstance;
+        return new BluetoothRouteProvider(context, btAdapter, listener);
     }
 
     private BluetoothRouteProvider(Context context, BluetoothAdapter btAdapter,
@@ -103,7 +105,7 @@
         buildBluetoothRoutes();
     }
 
-    public void start() {
+    public void start(UserHandle user) {
         mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.A2DP);
         mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.HEARING_AID);
 
@@ -118,7 +120,8 @@
         addEventReceiver(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED,
                 deviceStateChangedReceiver);
 
-        mContext.registerReceiver(mBroadcastReceiver, mIntentFilter, null, null);
+        mContext.registerReceiverAsUser(mBroadcastReceiver, user,
+                mIntentFilter, null, null);
     }
 
     /**
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 875bfdf..1114fe0 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -1176,7 +1176,8 @@
             super(Looper.getMainLooper(), null, true);
             mServiceRef = new WeakReference<>(service);
             mUserRecord = userRecord;
-            mSystemProvider = new SystemMediaRoute2Provider(service.mContext);
+            mSystemProvider = new SystemMediaRoute2Provider(service.mContext,
+                    UserHandle.of(userRecord.mUserId));
             mRouteProviders.add(mSystemProvider);
             mWatcher = new MediaRoute2ProviderWatcher(service.mContext, this,
                     this, mUserRecord.mUserId);
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 0f67483..8777cea 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -589,11 +589,19 @@
             if (mDestroyed) {
                 return;
             }
+            ParceledListSlice<QueueItem> parcelableQueue;
+            if (mQueue == null) {
+                parcelableQueue = null;
+            } else {
+                parcelableQueue = new ParceledListSlice<>(mQueue);
+                // Limit the size of initial Parcel to prevent binder buffer overflow
+                // as onQueueChanged is an async binder call.
+                parcelableQueue.setInlineCountLimit(1);
+            }
             for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
                 ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
                 try {
-                    holder.mCallback.onQueueChanged(mQueue == null ? null :
-                            new ParceledListSlice<>(mQueue));
+                    holder.mCallback.onQueueChanged(parcelableQueue);
                 } catch (DeadObjectException e) {
                     mControllerCallbackHolders.remove(i);
                     logCallbackException("Removing dead callback in pushQueueUpdate", holder, e);
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index eb4ab1c..9521611 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -32,14 +32,15 @@
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.UserInfo;
-import android.database.ContentObserver;
 import android.media.AudioManager;
 import android.media.AudioManagerInternal;
 import android.media.AudioPlaybackConfiguration;
@@ -58,7 +59,6 @@
 import android.media.session.MediaController;
 import android.media.session.MediaSession;
 import android.media.session.MediaSessionManager;
-import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
@@ -138,7 +138,6 @@
     private KeyguardManager mKeyguardManager;
     private AudioManagerInternal mAudioManagerInternal;
     private ContentResolver mContentResolver;
-    private SettingsObserver mSettingsObserver;
     private boolean mHasFeatureLeanback;
 
     // The FullUserRecord of the current users. (i.e. The foreground user that isn't a profile)
@@ -192,8 +191,6 @@
                     }
                 }, null /* handler */);
         mContentResolver = mContext.getContentResolver();
-        mSettingsObserver = new SettingsObserver();
-        mSettingsObserver.observe();
         mHasFeatureLeanback = mContext.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_LEANBACK);
 
@@ -202,8 +199,20 @@
         instantiateCustomProvider(null);
         instantiateCustomDispatcher(null);
         mRecordThread.start();
+
+        final IntentFilter filter = new IntentFilter(
+                NotificationManager.ACTION_NOTIFICATION_LISTENER_ENABLED_CHANGED);
+        mContext.registerReceiver(mNotificationListenerEnabledChangedReceiver, filter);
     }
 
+    private final BroadcastReceiver mNotificationListenerEnabledChangedReceiver =
+            new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    updateActiveSessionListeners();
+                }
+    };
+
     private boolean isGlobalPriorityActiveLocked() {
         return mGlobalPrioritySession != null && mGlobalPrioritySession.isActive();
     }
@@ -1082,25 +1091,6 @@
         }
     }
 
-    final class SettingsObserver extends ContentObserver {
-        private final Uri mSecureSettingsUri = Settings.Secure.getUriFor(
-                Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
-
-        private SettingsObserver() {
-            super(null);
-        }
-
-        private void observe() {
-            mContentResolver.registerContentObserver(mSecureSettingsUri,
-                    false, this, ALL.getIdentifier());
-        }
-
-        @Override
-        public void onChange(boolean selfChange, Uri uri) {
-            updateActiveSessionListeners();
-        }
-    }
-
     class SessionManagerImpl extends ISessionManager.Stub {
         private static final String EXTRA_WAKELOCK_ACQUIRED =
                 "android.media.AudioService.WAKELOCK_ACQUIRED";
@@ -1928,7 +1918,7 @@
             final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
             final long token = Binder.clearCallingIdentity();
             try {
-                // Don't perform sanity check between controllerPackageName and controllerUid.
+                // Don't perform check between controllerPackageName and controllerUid.
                 // When an (activity|service) runs on the another apps process by specifying
                 // android:process in the AndroidManifest.xml, then PID and UID would have the
                 // running process' information instead of the (activity|service) that has created
@@ -2710,5 +2700,4 @@
             obtainMessage(msg, userIdInteger).sendToTarget();
         }
     }
-
 }
diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java
index 953aae4..d9b5b6d 100644
--- a/services/core/java/com/android/server/media/MediaSessionStack.java
+++ b/services/core/java/com/android/server/media/MediaSessionStack.java
@@ -20,7 +20,6 @@
 
 import android.media.Session2Token;
 import android.media.session.MediaSession;
-import android.os.Debug;
 import android.os.UserHandle;
 import android.util.Log;
 import android.util.SparseArray;
@@ -187,7 +186,7 @@
      */
     public void updateMediaButtonSessionIfNeeded() {
         if (DEBUG) {
-            Log.d(TAG, "updateMediaButtonSessionIfNeeded, callers=" + Debug.getCallers(2));
+            Log.d(TAG, "updateMediaButtonSessionIfNeeded, callers=" + getCallers(2));
         }
         List<Integer> audioPlaybackUids =
                 mAudioPlayerStateMonitor.getSortedAudioPlaybackClientUids();
@@ -413,4 +412,24 @@
         // so they also need to be cleared.
         mCachedActiveLists.remove(UserHandle.USER_ALL);
     }
+
+    // Code copied from android.os.Debug#getCallers(int)
+    private static String getCallers(final int depth) {
+        final StackTraceElement[] callStack = Thread.currentThread().getStackTrace();
+        StringBuffer sb = new StringBuffer();
+        for (int i = 0; i < depth; i++) {
+            sb.append(getCaller(callStack, i)).append(" ");
+        }
+        return sb.toString();
+    }
+
+    // Code copied from android.os.Debug#getCaller(StackTraceElement[], int)
+    private static String getCaller(StackTraceElement[] callStack, int depth) {
+        // callStack[4] is the caller of the method that called getCallers()
+        if (4 + depth >= callStack.length) {
+            return "<bottom of call stack>";
+        }
+        StackTraceElement caller = callStack[4 + depth];
+        return caller.getClassName() + "." + caller.getMethodName() + ":" + caller.getLineNumber();
+    }
 }
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 2c089ca..4f7af94 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -45,6 +45,7 @@
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Slog;
@@ -99,7 +100,7 @@
         }
     };
 
-    SystemMediaRoute2Provider(Context context) {
+    SystemMediaRoute2Provider(Context context, UserHandle user) {
         super(sComponentName);
 
         mIsSystemRouteProvider = true;
@@ -117,7 +118,7 @@
         updateDeviceRoute(newAudioRoutes);
 
         // .getInstance returns null if there is no bt adapter available
-        mBtRouteProvider = BluetoothRouteProvider.getInstance(context, (routes) -> {
+        mBtRouteProvider = BluetoothRouteProvider.createInstance(context, (routes) -> {
             publishProviderState();
 
             boolean sessionInfoChanged;
@@ -130,11 +131,12 @@
 
         IntentFilter intentFilter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION);
         intentFilter.addAction(AudioManager.STREAM_DEVICES_CHANGED_ACTION);
-        mContext.registerReceiver(new AudioManagerBroadcastReceiver(), intentFilter);
+        mContext.registerReceiverAsUser(new AudioManagerBroadcastReceiver(), user,
+                intentFilter, null, null);
 
         if (mBtRouteProvider != null) {
             mHandler.post(() -> {
-                mBtRouteProvider.start();
+                mBtRouteProvider.start(user);
                 notifyProviderState();
             });
         }
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 295143e..29ee8eb 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -4367,7 +4367,7 @@
                 // Flip state because app was explicitly added or removed to denylist.
                 setMeteredNetworkDenylist(uid, (isDenylisted || isRestrictedByAdmin));
                 if (hasRule(oldRule, RULE_REJECT_METERED) && isAllowlisted) {
-                    // Since dneylist prevails over allowlist, we need to handle the special case
+                    // Since denylist prevails over allowlist, we need to handle the special case
                     // where app is allowlisted and denylisted at the same time (although such
                     // scenario should be blocked by the UI), then denylist is removed.
                     setMeteredNetworkAllowlist(uid, isAllowlisted);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 2d052da..9f9235d 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -31,6 +31,7 @@
 import static android.app.NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED_INTERNAL;
 import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED;
 import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED;
+import static android.app.NotificationManager.ACTION_NOTIFICATION_LISTENER_ENABLED_CHANGED;
 import static android.app.NotificationManager.ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED;
 import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
 import static android.app.NotificationManager.EXTRA_AUTOMATIC_ZEN_RULE_ID;
@@ -7758,6 +7759,13 @@
     @VisibleForTesting
     void updateUriPermissions(@Nullable NotificationRecord newRecord,
             @Nullable NotificationRecord oldRecord, String targetPkg, int targetUserId) {
+        updateUriPermissions(newRecord, oldRecord, targetPkg, targetUserId, false);
+    }
+
+    @VisibleForTesting
+    void updateUriPermissions(@Nullable NotificationRecord newRecord,
+            @Nullable NotificationRecord oldRecord, String targetPkg, int targetUserId,
+            boolean onlyRevokeCurrentTarget) {
         final String key = (newRecord != null) ? newRecord.getKey() : oldRecord.getKey();
         if (DBG) Slog.d(TAG, key + ": updating permissions");
 
@@ -7785,7 +7793,9 @@
         }
 
         // If we have no Uris to grant, but an existing owner, go destroy it
-        if (newUris == null && permissionOwner != null) {
+        // When revoking permissions of a single listener, destroying the owner will revoke
+        // permissions of other listeners who need to keep access.
+        if (newUris == null && permissionOwner != null && !onlyRevokeCurrentTarget) {
             destroyPermissionOwner(permissionOwner, UserHandle.getUserId(oldRecord.getUid()), key);
             permissionOwner = null;
         }
@@ -7808,9 +7818,20 @@
                 final Uri uri = oldUris.valueAt(i);
                 if (newUris == null || !newUris.contains(uri)) {
                     if (DBG) Slog.d(TAG, key + ": revoking " + uri);
-                    int userId = ContentProvider.getUserIdFromUri(
-                            uri, UserHandle.getUserId(oldRecord.getUid()));
-                    revokeUriPermission(permissionOwner, uri, userId);
+                    if (onlyRevokeCurrentTarget) {
+                        // We're revoking permission from one listener only; other listeners may
+                        // still need access because the notification may still exist
+                        revokeUriPermission(permissionOwner, uri,
+                                UserHandle.getUserId(oldRecord.getUid()), targetPkg, targetUserId);
+                    } else {
+                        // This is broad to unilaterally revoke permissions to this Uri as granted
+                        // by this notification.  But this code-path can only be used when the
+                        // reason for revoking is that the notification posted again without this
+                        // Uri, not when removing an individual listener.
+                        revokeUriPermission(permissionOwner, uri,
+                                UserHandle.getUserId(oldRecord.getUid()),
+                                null, UserHandle.USER_ALL);
+                    }
                 }
             }
         }
@@ -7839,8 +7860,10 @@
         }
     }
 
-    private void revokeUriPermission(IBinder owner, Uri uri, int userId) {
+    private void revokeUriPermission(IBinder owner, Uri uri, int sourceUserId, String targetPkg,
+            int targetUserId) {
         if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return;
+        int userId = ContentProvider.getUserIdFromUri(uri, sourceUserId);
 
         final long ident = Binder.clearCallingIdentity();
         try {
@@ -7848,7 +7871,7 @@
                     owner,
                     ContentProvider.getUriWithoutUserId(uri),
                     Intent.FLAG_GRANT_READ_URI_PERMISSION,
-                    userId);
+                    userId, targetPkg, targetUserId);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -9158,6 +9181,17 @@
         }
 
         @Override
+        protected void setPackageOrComponentEnabled(String pkgOrComponent, int userId,
+                boolean isPrimary, boolean enabled) {
+            super.setPackageOrComponentEnabled(pkgOrComponent, userId, isPrimary, enabled);
+
+            getContext().sendBroadcastAsUser(
+                    new Intent(ACTION_NOTIFICATION_LISTENER_ENABLED_CHANGED)
+                            .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY),
+                    UserHandle.ALL, null);
+        }
+
+        @Override
         protected void loadDefaultsFromConfig() {
             String defaultListenerAccess = mContext.getResources().getString(
                     R.string.config_defaultListenerAccessPackages);
@@ -9219,6 +9253,7 @@
             final NotificationRankingUpdate update;
             synchronized (mNotificationLock) {
                 update = makeRankingUpdateLocked(info);
+                updateUriPermissionsForActiveNotificationsLocked(info, true);
             }
             try {
                 listener.onListenerConnected(update);
@@ -9230,6 +9265,7 @@
         @Override
         @GuardedBy("mNotificationLock")
         protected void onServiceRemovedLocked(ManagedServiceInfo removed) {
+            updateUriPermissionsForActiveNotificationsLocked(removed, false);
             if (removeDisabledHints(removed)) {
                 updateListenerHintsLocked();
                 updateEffectsSuppressorLocked();
@@ -9296,8 +9332,7 @@
 
                 for (final ManagedServiceInfo info : getServices()) {
                     boolean sbnVisible = isVisibleToListener(sbn, info);
-                    boolean oldSbnVisible = oldSbn != null ? isVisibleToListener(oldSbn, info)
-                            : false;
+                    boolean oldSbnVisible = (oldSbn != null) && isVisibleToListener(oldSbn, info);
                     // This notification hasn't been and still isn't visible -> ignore.
                     if (!oldSbnVisible && !sbnVisible) {
                         continue;
@@ -9321,13 +9356,8 @@
                     // This notification became invisible -> remove the old one.
                     if (oldSbnVisible && !sbnVisible) {
                         final StatusBarNotification oldSbnLightClone = oldSbn.cloneLight();
-                        mHandler.post(new Runnable() {
-                            @Override
-                            public void run() {
-                                notifyRemoved(
-                                        info, oldSbnLightClone, update, null, REASON_USER_STOPPED);
-                            }
-                        });
+                        mHandler.post(() -> notifyRemoved(
+                                info, oldSbnLightClone, update, null, REASON_USER_STOPPED));
                         continue;
                     }
 
@@ -9337,12 +9367,7 @@
                     updateUriPermissions(r, old, info.component.getPackageName(), targetUserId);
 
                     final StatusBarNotification sbnToPost = trimCache.ForListener(info);
-                    mHandler.post(new Runnable() {
-                        @Override
-                        public void run() {
-                            notifyPosted(info, sbnToPost, update);
-                        }
-                    });
+                    mHandler.post(() -> notifyPosted(info, sbnToPost, update));
                 }
             } catch (Exception e) {
                 Slog.e(TAG, "Could not notify listeners for " + r.getKey(), e);
@@ -9350,6 +9375,46 @@
         }
 
         /**
+         * Synchronously grant or revoke permissions to Uris for all active and visible
+         * notifications to just the NotificationListenerService provided.
+         */
+        @GuardedBy("mNotificationLock")
+        private void updateUriPermissionsForActiveNotificationsLocked(
+                ManagedServiceInfo info, boolean grant) {
+            try {
+                for (final NotificationRecord r : mNotificationList) {
+                    // When granting permissions, ignore notifications which are invisible.
+                    // When revoking permissions, all notifications are invisible, so process all.
+                    if (grant && !isVisibleToListener(r.getSbn(), info)) {
+                        continue;
+                    }
+                    // If the notification is hidden, permissions are not required by the listener.
+                    if (r.isHidden() && info.targetSdkVersion < Build.VERSION_CODES.P) {
+                        continue;
+                    }
+                    // Grant or revoke access synchronously
+                    final int targetUserId = (info.userid == UserHandle.USER_ALL)
+                            ? UserHandle.USER_SYSTEM : info.userid;
+                    if (grant) {
+                        // Grant permissions by passing arguments as if the notification is new.
+                        updateUriPermissions(/* newRecord */ r, /* oldRecord */ null,
+                                info.component.getPackageName(), targetUserId);
+                    } else {
+                        // Revoke permissions by passing arguments as if the notification was
+                        // removed, but set `onlyRevokeCurrentTarget` to avoid revoking permissions
+                        // granted to *other* targets by this notification's URIs.
+                        updateUriPermissions(/* newRecord */ null, /* oldRecord */ r,
+                                info.component.getPackageName(), targetUserId,
+                                /* onlyRevokeCurrentTarget */ true);
+                    }
+                }
+            } catch (Exception e) {
+                Slog.e(TAG, "Could not " + (grant ? "grant" : "revoke") + " Uri permissions to "
+                        + info.component, e);
+            }
+        }
+
+        /**
          * asynchronously notify all listeners about a removed notification
          */
         @GuardedBy("mNotificationLock")
@@ -9384,18 +9449,11 @@
                 final NotificationStats stats = mAssistants.isServiceTokenValidLocked(info.service)
                         ? notificationStats : null;
                 final NotificationRankingUpdate update = makeRankingUpdateLocked(info);
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        notifyRemoved(info, sbnLight, update, stats, reason);
-                    }
-                });
+                mHandler.post(() -> notifyRemoved(info, sbnLight, update, stats, reason));
             }
 
             // Revoke access after all listeners have been updated
-            mHandler.post(() -> {
-                updateUriPermissions(null, r, null, UserHandle.USER_SYSTEM);
-            });
+            mHandler.post(() -> updateUriPermissions(null, r, null, UserHandle.USER_SYSTEM));
         }
 
         /**
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index def9c78..3d7c978 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -35,9 +35,6 @@
 import android.content.pm.parsing.component.ParsedIntentInfo;
 import android.content.pm.parsing.component.ParsedMainComponent;
 import android.content.pm.parsing.component.ParsedProvider;
-import android.os.Handler;
-import android.os.HandlerExecutor;
-import android.os.HandlerThread;
 import android.os.Process;
 import android.os.Trace;
 import android.os.UserHandle;
@@ -353,13 +350,9 @@
                         injector.getUserManagerInternal().getUserInfos());
             }
         };
-        HandlerThread appsFilterThread = new HandlerThread("appsFilter");
-        appsFilterThread.start();
-        Handler appsFilterHandler = new Handler(appsFilterThread.getLooper());
-        Executor executor = new HandlerExecutor(appsFilterHandler);
-
         AppsFilter appsFilter = new AppsFilter(stateProvider, featureConfig,
-                forcedQueryablePackageNames, forceSystemAppsQueryable, null, executor);
+                forcedQueryablePackageNames, forceSystemAppsQueryable, null,
+                injector.getBackgroundExecutor());
         featureConfig.setAppsFilter(appsFilter);
         return appsFilter;
     }
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 672ad5e..cd383b9 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -23,6 +23,8 @@
 import android.content.Context;
 import android.content.pm.PackageStats;
 import android.os.Build;
+import android.os.CreateAppDataArgs;
+import android.os.CreateAppDataResult;
 import android.os.IBinder;
 import android.os.IBinder.DeathRecipient;
 import android.os.IInstalld;
@@ -39,7 +41,10 @@
 import dalvik.system.VMRuntime;
 
 import java.io.FileDescriptor;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
 
 public class Installer extends SystemService {
     private static final String TAG = "Installer";
@@ -176,37 +181,140 @@
         }
     }
 
+    private static CreateAppDataArgs buildCreateAppDataArgs(String uuid, String packageName,
+            int userId, int flags, int appId, String seInfo, int targetSdkVersion) {
+        final CreateAppDataArgs args = new CreateAppDataArgs();
+        args.uuid = uuid;
+        args.packageName = packageName;
+        args.userId = userId;
+        args.flags = flags;
+        args.appId = appId;
+        args.seInfo = seInfo;
+        args.targetSdkVersion = targetSdkVersion;
+        return args;
+    }
+
+    private static CreateAppDataResult buildPlaceholderCreateAppDataResult() {
+        final CreateAppDataResult result = new CreateAppDataResult();
+        result.ceDataInode = -1;
+        result.exceptionCode = 0;
+        result.exceptionMessage = null;
+        return result;
+    }
+
+    /**
+     * @deprecated callers are encouraged to migrate to using {@link Batch} to
+     *             more efficiently handle operations in bulk.
+     */
+    @Deprecated
     public long createAppData(String uuid, String packageName, int userId, int flags, int appId,
             String seInfo, int targetSdkVersion) throws InstallerException {
-        if (!checkBeforeRemote()) return -1;
+        final CreateAppDataArgs args = buildCreateAppDataArgs(uuid, packageName, userId, flags,
+                appId, seInfo, targetSdkVersion);
+        final CreateAppDataResult result = createAppData(args);
+        if (result.exceptionCode == 0) {
+            return result.ceDataInode;
+        } else {
+            throw new InstallerException(result.exceptionMessage);
+        }
+    }
+
+    public @NonNull CreateAppDataResult createAppData(@NonNull CreateAppDataArgs args)
+            throws InstallerException {
+        if (!checkBeforeRemote()) {
+            return buildPlaceholderCreateAppDataResult();
+        }
         try {
-            return mInstalld.createAppData(uuid, packageName, userId, flags, appId, seInfo,
-                    targetSdkVersion);
+            return mInstalld.createAppData(args);
+        } catch (Exception e) {
+            throw InstallerException.from(e);
+        }
+    }
+
+    public @NonNull CreateAppDataResult[] createAppDataBatched(@NonNull CreateAppDataArgs[] args)
+            throws InstallerException {
+        if (!checkBeforeRemote()) {
+            final CreateAppDataResult[] results = new CreateAppDataResult[args.length];
+            Arrays.fill(results, buildPlaceholderCreateAppDataResult());
+            return results;
+        }
+        try {
+            return mInstalld.createAppDataBatched(args);
         } catch (Exception e) {
             throw InstallerException.from(e);
         }
     }
 
     /**
-     * Batched version of createAppData for use with multiple packages.
+     * Class that collects multiple {@code installd} operations together in an
+     * attempt to more efficiently execute them in bulk.
+     * <p>
+     * Instead of returning results immediately, {@link CompletableFuture}
+     * instances are returned which can be used to chain follow-up work for each
+     * request.
+     * <p>
+     * The creator of this object <em>must</em> invoke {@link #execute()}
+     * exactly once to begin execution of all pending operations. Once execution
+     * has been kicked off, no additional events can be enqueued into this
+     * instance, but multiple instances can safely exist in parallel.
      */
-    public void createAppDataBatched(String[] uuids, String[] packageNames, int userId, int flags,
-            int[] appIds, String[] seInfos, int[] targetSdkVersions) throws InstallerException {
-        if (!checkBeforeRemote()) return;
-        final int batchSize = 256;
-        for (int i = 0; i < uuids.length; i += batchSize) {
-            int to = i + batchSize;
-            if (to > uuids.length) {
-                to = uuids.length;
-            }
+    public static class Batch {
+        private static final int CREATE_APP_DATA_BATCH_SIZE = 256;
 
-            try {
-                mInstalld.createAppDataBatched(Arrays.copyOfRange(uuids, i, to),
-                        Arrays.copyOfRange(packageNames, i, to), userId, flags,
-                        Arrays.copyOfRange(appIds, i, to), Arrays.copyOfRange(seInfos, i, to),
-                        Arrays.copyOfRange(targetSdkVersions, i, to));
-            } catch (Exception e) {
-                throw InstallerException.from(e);
+        private boolean mExecuted;
+
+        private final List<CreateAppDataArgs> mArgs = new ArrayList<>();
+        private final List<CompletableFuture<Long>> mFutures = new ArrayList<>();
+
+        /**
+         * Enqueue the given {@code installd} operation to be executed in the
+         * future when {@link #execute(Installer)} is invoked.
+         * <p>
+         * Callers of this method are not required to hold a monitor lock on an
+         * {@link Installer} object.
+         */
+        public synchronized @NonNull CompletableFuture<Long> createAppData(String uuid,
+                String packageName, int userId, int flags, int appId, String seInfo,
+                int targetSdkVersion) {
+            if (mExecuted) throw new IllegalStateException();
+
+            final CreateAppDataArgs args = buildCreateAppDataArgs(uuid, packageName, userId, flags,
+                    appId, seInfo, targetSdkVersion);
+            final CompletableFuture<Long> future = new CompletableFuture<>();
+            mArgs.add(args);
+            mFutures.add(future);
+            return future;
+        }
+
+        /**
+         * Execute all pending {@code installd} operations that have been
+         * collected by this batch in a blocking fashion.
+         * <p>
+         * Callers of this method <em>must</em> hold a monitor lock on the given
+         * {@link Installer} object.
+         */
+        public synchronized void execute(@NonNull Installer installer) throws InstallerException {
+            if (mExecuted) throw new IllegalStateException();
+            mExecuted = true;
+
+            final int size = mArgs.size();
+            for (int i = 0; i < size; i += CREATE_APP_DATA_BATCH_SIZE) {
+                final CreateAppDataArgs[] args = new CreateAppDataArgs[Math.min(size - i,
+                        CREATE_APP_DATA_BATCH_SIZE)];
+                for (int j = 0; j < args.length; j++) {
+                    args[j] = mArgs.get(i + j);
+                }
+                final CreateAppDataResult[] results = installer.createAppDataBatched(args);
+                for (int j = 0; j < args.length; j++) {
+                    final CreateAppDataResult result = results[j];
+                    final CompletableFuture<Long> future = mFutures.get(i + j);
+                    if (result.exceptionCode == 0) {
+                        future.complete(result.ceDataInode);
+                    } else {
+                        future.completeExceptionally(
+                                new InstallerException(result.exceptionMessage));
+                    }
+                }
             }
         }
     }
diff --git a/services/core/java/com/android/server/pm/InstantAppResolver.java b/services/core/java/com/android/server/pm/InstantAppResolver.java
index 0b0f139..79f8dc1 100644
--- a/services/core/java/com/android/server/pm/InstantAppResolver.java
+++ b/services/core/java/com/android/server/pm/InstantAppResolver.java
@@ -380,7 +380,7 @@
                 sanitizeIntent(request.origIntent),
                 // This must only expose the secured version of the host
                 request.hostDigestPrefixSecure,
-                UserHandle.getUserHandleForUid(request.userId),
+                UserHandle.of(request.userId),
                 request.isRequesterInstantApp,
                 request.token
         );
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 840645e..4b246c3 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -613,6 +613,9 @@
                 throw new IllegalArgumentException(
                     "APEX files can only be installed as part of a staged session.");
             }
+            if (params.isMultiPackage) {
+                throw new IllegalArgumentException("A multi-session can't be set as APEX.");
+            }
         }
 
         if (params.isStaged && !isCalledBySystemOrShell(callingUid)) {
@@ -1281,14 +1284,21 @@
             pw.increaseIndent();
 
             List<PackageInstallerSession> finalizedSessions = new ArrayList<>();
+            List<PackageInstallerSession> orphanedChildSessions = new ArrayList<>();
             int N = mSessions.size();
             for (int i = 0; i < N; i++) {
                 final PackageInstallerSession session = mSessions.valueAt(i);
 
-                // Do not print finalized staged session as active install sessions
                 final PackageInstallerSession rootSession = session.hasParentSessionId()
                         ? getSession(session.getParentSessionId())
                         : session;
+                // Do not print orphaned child sessions as active install sessions
+                if (rootSession == null) {
+                    orphanedChildSessions.add(session);
+                    continue;
+                }
+
+                // Do not print finalized staged session as active install sessions
                 if (rootSession.isStagedAndInTerminalState()) {
                     finalizedSessions.add(session);
                     continue;
@@ -1300,6 +1310,21 @@
             pw.println();
             pw.decreaseIndent();
 
+            if (!orphanedChildSessions.isEmpty()) {
+                // Presence of orphaned sessions indicate leak in cleanup for multi-package and
+                // should be cleaned up.
+                pw.println("Orphaned install sessions:");
+                pw.increaseIndent();
+                N = orphanedChildSessions.size();
+                for (int i = 0; i < N; i++) {
+                    final PackageInstallerSession session = orphanedChildSessions.get(i);
+                    session.dump(pw);
+                    pw.println();
+                }
+                pw.println();
+                pw.decreaseIndent();
+            }
+
             pw.println("Finalized install sessions:");
             pw.increaseIndent();
             N = finalizedSessions.size();
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index ff9edd5..28c5e96 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -1328,12 +1328,9 @@
                 if (PackageInstaller.STATUS_SUCCESS == status) {
                     mChildSessionsRemaining.removeAt(sessionIndex);
                     if (mChildSessionsRemaining.size() == 0) {
-                        try {
-                            intent.putExtra(PackageInstaller.EXTRA_SESSION_ID,
-                                    PackageInstallerSession.this.sessionId);
-                            mStatusReceiver.sendIntent(mContext, 0, intent, null, null);
-                        } catch (IntentSender.SendIntentException ignore) {
-                        }
+                        destroyInternal();
+                        dispatchSessionFinished(PackageManager.INSTALL_SUCCEEDED,
+                                "Session installed", null);
                     }
                 } else if (PackageInstaller.STATUS_PENDING_USER_ACTION == status) {
                     try {
@@ -1492,53 +1489,6 @@
     }
 
     /**
-     * Assert multipackage install has consistent sessions.
-     *
-     * @throws PackageManagerException if child sessions don't match parent session
-     *                                  in respect to staged and enable rollback parameters.
-     */
-    @GuardedBy("mLock")
-    private void assertMultiPackageConsistencyLocked(
-            @NonNull List<PackageInstallerSession> childSessions) throws PackageManagerException {
-        for (PackageInstallerSession childSession : childSessions) {
-            // It might be that the parent session is loaded before all of it's child sessions are,
-            // e.g. when reading sessions from XML. Those sessions will be null here, and their
-            // conformance with the multipackage params will be checked when they're loaded.
-            if (childSession == null) {
-                continue;
-            }
-            assertConsistencyWithLocked(childSession);
-        }
-    }
-
-    /**
-     * Assert consistency with the given session.
-     *
-     * @throws PackageManagerException if other sessions doesn't match this session
-     *                                  in respect to staged and enable rollback parameters.
-     */
-    @GuardedBy("mLock")
-    private void assertConsistencyWithLocked(PackageInstallerSession other)
-            throws PackageManagerException {
-        // Session groups must be consistent wrt to isStaged parameter. Non-staging session
-        // cannot be grouped with staging sessions.
-        if (this.params.isStaged != other.params.isStaged) {
-            throw new PackageManagerException(
-                PackageManager.INSTALL_FAILED_MULTIPACKAGE_INCONSISTENCY,
-                "Multipackage Inconsistency: session " + other.sessionId
-                    + " and session " + sessionId
-                    + " have inconsistent staged settings");
-        }
-        if (this.params.getEnableRollback() != other.params.getEnableRollback()) {
-            throw new PackageManagerException(
-                PackageManager.INSTALL_FAILED_MULTIPACKAGE_INCONSISTENCY,
-                "Multipackage Inconsistency: session " + other.sessionId
-                    + " and session " + sessionId
-                    + " have inconsistent rollback settings");
-        }
-    }
-
-    /**
      * Seal the session to prevent further modification.
      *
      * <p>The session will be sealed after calling this method even if it failed.
@@ -1552,14 +1502,7 @@
         try {
             assertNoWriteFileTransfersOpenLocked();
             assertPreparedAndNotDestroyedLocked("sealing of session");
-
             mSealed = true;
-            List<PackageInstallerSession> childSessions = getChildSessionsLocked();
-            if (childSessions != null) {
-                assertMultiPackageConsistencyLocked(childSessions);
-            }
-        } catch (PackageManagerException e) {
-            throw onSessionValidationFailure(e);
         } catch (Throwable e) {
             // Convert all exceptions into package manager exceptions as only those are handled
             // in the code above.
@@ -1637,7 +1580,8 @@
      * If session should be sealed, then it's sealed to prevent further modification.
      * If the session can't be sealed then it's destroyed.
      *
-     * Additionally for staged APEX sessions read+validate the package and populate req'd fields.
+     * Additionally for staged APEX/APK sessions read+validate the package and populate req'd
+     * fields.
      *
      * <p> This is meant to be called after all of the sessions are loaded and added to
      * PackageInstallerService
@@ -1670,6 +1614,13 @@
                     // APEX installations rely on certain fields to be populated after reboot.
                     // E.g. mPackageName.
                     validateApexInstallLocked();
+                } else {
+                    // Populate mPackageName for this APK session which is required by the staging
+                    // manager to check duplicate apk-in-apex.
+                    PackageInstallerSession parent = allSessions.get(mParentSessionId);
+                    if (parent != null && parent.isStagedSessionReady()) {
+                        validateApkInstallLocked();
+                    }
                 }
             } catch (PackageManagerException e) {
                 Slog.e(TAG, "Package not valid", e);
@@ -2783,23 +2734,27 @@
         }
     }
 
-    @Override
-    public void abandon() {
-        if (hasParentSessionId()) {
-            throw new IllegalStateException(
-                    "Session " + sessionId + " is a child of multi-package session "
-                            + getParentSessionId() +  " and may not be abandoned directly.");
-        }
-
+    private void abandonNonStaged() {
         synchronized (mLock) {
-            if (params.isStaged && mDestroyed) {
+            assertCallerIsOwnerOrRootLocked();
+            if (mRelinquished) {
+                if (LOGD) Slog.d(TAG, "Ignoring abandon after commit relinquished control");
+                return;
+            }
+            destroyInternal();
+        }
+        dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null);
+    }
+
+    private void abandonStaged() {
+        synchronized (mLock) {
+            if (mDestroyed) {
                 // If a user abandons staged session in an unsafe state, then system will try to
                 // abandon the destroyed staged session when it is safe on behalf of the user.
                 assertCallerIsOwnerOrRootOrSystemLocked();
             } else {
                 assertCallerIsOwnerOrRootLocked();
             }
-
             if (isStagedAndInTerminalState()) {
                 // We keep the session in the database if it's in a finalized state. It will be
                 // removed by PackageInstallerService when the last update time is old enough.
@@ -2807,26 +2762,35 @@
                 // do it now.
                 return;
             }
-            if (mCommitted && params.isStaged) {
-                mDestroyed = true;
+            mDestroyed = true;
+            if (mCommitted) {
                 if (!mStagingManager.abortCommittedSessionLocked(this)) {
                     // Do not clean up the staged session from system. It is not safe yet.
                     mCallback.onStagedSessionChanged(this);
                     return;
                 }
-                cleanStageDir(getChildSessionsLocked());
             }
-
-            if (mRelinquished) {
-                Slog.d(TAG, "Ignoring abandon after commit relinquished control");
-                return;
-            }
+            cleanStageDir(getChildSessionsLocked());
             destroyInternal();
         }
         dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null);
     }
 
     @Override
+    public void abandon() {
+        if (hasParentSessionId()) {
+            throw new IllegalStateException(
+                    "Session " + sessionId + " is a child of multi-package session "
+                            + getParentSessionId() +  " and may not be abandoned directly.");
+        }
+        if (params.isStaged) {
+            abandonStaged();
+        } else {
+            abandonNonStaged();
+        }
+    }
+
+    @Override
     public boolean isMultiPackage() {
         return params.isMultiPackage;
     }
@@ -3185,6 +3149,16 @@
             throw new IllegalStateException("Multi-session " + childSessionId
                     + " can't be a child.");
         }
+        if (params.isStaged != childSession.params.isStaged) {
+            throw new IllegalStateException("Multipackage Inconsistency: session "
+                    + childSession.sessionId + " and session " + sessionId
+                    + " have inconsistent staged settings");
+        }
+        if (params.getEnableRollback() != childSession.params.getEnableRollback()) {
+            throw new IllegalStateException("Multipackage Inconsistency: session "
+                    + childSession.sessionId + " and session " + sessionId
+                    + " have inconsistent rollback settings");
+        }
 
         try {
             acquireTransactionLock();
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 0d1c00d..9c8b972 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -248,6 +248,8 @@
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
@@ -418,7 +420,9 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
@@ -923,6 +927,7 @@
         private final Object mLock;
         private final Installer mInstaller;
         private final Object mInstallLock;
+        private final Executor mBackgroundExecutor;
 
         // ----- producers -----
         private final Singleton<ComponentResolver> mComponentResolverProducer;
@@ -944,6 +949,7 @@
 
         Injector(Context context, Object lock, Installer installer,
                 Object installLock, PackageAbiHelper abiHelper,
+                Executor backgroundExecutor,
                 Producer<ComponentResolver> componentResolverProducer,
                 Producer<PermissionManagerServiceInternal> permissionManagerProducer,
                 Producer<UserManagerService> userManagerProducer,
@@ -965,6 +971,7 @@
             mInstaller = installer;
             mAbiHelper = abiHelper;
             mInstallLock = installLock;
+            mBackgroundExecutor = backgroundExecutor;
             mComponentResolverProducer = new Singleton<>(componentResolverProducer);
             mPermissionManagerProducer = new Singleton<>(permissionManagerProducer);
             mUserManagerProducer = new Singleton<>(userManagerProducer);
@@ -1078,6 +1085,10 @@
         public PlatformCompat getCompatibility() {
             return mPlatformCompatProducer.get(this, mPackageManager);
         }
+
+        public Executor getBackgroundExecutor() {
+            return mBackgroundExecutor;
+        }
     }
 
     @VisibleForTesting(visibility = Visibility.PRIVATE)
@@ -2580,17 +2591,21 @@
         t.traceBegin("create package manager");
         final Object lock = new Object();
         final Object installLock = new Object();
+        HandlerThread backgroundThread = new HandlerThread("PackageManagerBg");
+        backgroundThread.start();
+        Handler backgroundHandler = new Handler(backgroundThread.getLooper());
 
         Injector injector = new Injector(
                 context, lock, installer, installLock, new PackageAbiHelperImpl(),
+                new HandlerExecutor(backgroundHandler),
                 (i, pm) ->
                         new ComponentResolver(i.getUserManagerService(), pm.mPmInternal, lock),
                 (i, pm) ->
-                        PermissionManagerService.create(context, lock),
+                PermissionManagerService.create(context, lock),
                 (i, pm) ->
-                        new UserManagerService(context, pm,
-                                new UserDataPreparer(installer, installLock, context, onlyCore),
-                                lock),
+                new UserManagerService(context, pm,
+                        new UserDataPreparer(installer, installLock, context, onlyCore),
+                        lock),
                 (i, pm) ->
                         new Settings(Environment.getDataDirectory(),
                                 i.getPermissionManagerServiceInternal().getPermissionSettings(),
@@ -3481,6 +3496,7 @@
                     return;
                 }
                 int count = 0;
+                final Installer.Batch batch = new Installer.Batch();
                 for (String pkgName : deferPackages) {
                     AndroidPackage pkg = null;
                     synchronized (mLock) {
@@ -3490,13 +3506,14 @@
                         }
                     }
                     if (pkg != null) {
-                        synchronized (mInstallLock) {
-                            prepareAppDataAndMigrateLIF(pkg, UserHandle.USER_SYSTEM, storageFlags,
-                                    true /* maybeMigrateAppData */);
-                        }
+                        prepareAppDataAndMigrate(batch, pkg, UserHandle.USER_SYSTEM, storageFlags,
+                                true /* maybeMigrateAppData */);
                         count++;
                     }
                 }
+                synchronized (mInstallLock) {
+                    executeBatchLI(batch);
+                }
                 traceLog.traceEnd();
                 Slog.i(TAG, "Deferred reconcileAppsData finished " + count + " packages");
             }, "prepareAppData");
@@ -7819,7 +7836,7 @@
     // low 'int'-sized word: relative priority among 'always' results.
     private long getDomainVerificationStatusLPr(PackageSetting ps, int userId) {
         long result = ps.getDomainVerificationStatusForUser(userId);
-        // if none available, get the master status
+        // if none available, get the status
         if (result >> 32 == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED) {
             if (ps.getIntentFilterVerificationInfo() != null) {
                 result = ((long)ps.getIntentFilterVerificationInfo().getStatus()) << 32;
@@ -17279,7 +17296,7 @@
 
         if (DEBUG_INSTALL) Slog.d(TAG, "installPackageLI: path=" + tmpPackageFile);
 
-        // Sanity check
+        // Validity check
         if (instantApp && onExternal) {
             Slog.i(TAG, "Incompatible ephemeral install; external=" + onExternal);
             throw new PrepareFailure(PackageManager.INSTALL_FAILED_SESSION_INVALID);
@@ -17423,7 +17440,7 @@
                     }
                 }
 
-                // Quick sanity check that we're signed correctly if updating;
+                // Quick validity check that we're signed correctly if updating;
                 // we'll check this again later when scanning, but we want to
                 // bail early here before tripping over redefined permissions.
                 final KeySetManagerService ksms = mSettings.mKeySetManagerService;
@@ -17591,9 +17608,6 @@
                 synchronized (mLock) {
                     pkgSetting = mSettings.getPackageLPr(pkgName);
                 }
-                String abiOverride =
-                        (pkgSetting == null || TextUtils.isEmpty(pkgSetting.cpuAbiOverrideString)
-                        ? args.abiOverride : pkgSetting.cpuAbiOverrideString);
                 boolean isUpdatedSystemAppFromExistingSetting = pkgSetting != null
                         && pkgSetting.getPkgState().isUpdatedSystemApp();
                 AndroidPackage oldPackage = mPackages.get(pkgName);
@@ -17601,7 +17615,7 @@
                 final Pair<PackageAbiHelper.Abis, PackageAbiHelper.NativeLibraryPaths>
                         derivedAbi = mInjector.getAbiHelper().derivePackageAbi(parsedPackage,
                         isUpdatedSystemAppFromExistingSetting || isUpdatedSystemAppInferred,
-                        abiOverride);
+                        args.abiOverride);
                 derivedAbi.first.applyTo(parsedPackage);
                 derivedAbi.second.applyTo(parsedPackage);
             } catch (PackageManagerException pme) {
@@ -21561,7 +21575,7 @@
             // had been set as a preferred activity.  We try to clean this up
             // the next time we encounter that preferred activity, but it is
             // possible for the user flow to never be able to return to that
-            // situation so here we do a sanity check to make sure we haven't
+            // situation so here we do a validity check to make sure we haven't
             // left any junk around.
             ArrayList<PreferredActivity> removed = new ArrayList<>();
             for (int i=0; i<mSettings.mPreferredActivities.size(); i++) {
@@ -22746,6 +22760,14 @@
         }
     }
 
+    private void executeBatchLI(@NonNull Installer.Batch batch) {
+        try {
+            batch.execute(mInstaller);
+        } catch (InstallerException e) {
+            Slog.w(TAG, "Failed to execute pending operations", e);
+        }
+    }
+
     /**
      * Examine all apps present on given mounted volume, and destroy apps that
      * aren't expected, either due to uninstallation or reinstallation on
@@ -22885,6 +22907,8 @@
 
         // Ensure that data directories are ready to roll for all packages
         // installed for this volume and user
+        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "prepareAppDataAndMigrate");
+        Installer.Batch batch = new Installer.Batch();
         final List<PackageSetting> packages;
         synchronized (mLock) {
             packages = mSettings.getVolumePackagesLPr(volumeUuid);
@@ -22905,10 +22929,12 @@
             }
 
             if (ps.getInstalled(userId)) {
-                prepareAppDataAndMigrateLIF(ps.pkg, userId, flags, migrateAppData);
+                prepareAppDataAndMigrate(batch, ps.pkg, userId, flags, migrateAppData);
                 preparedCount++;
             }
         }
+        executeBatchLI(batch);
+        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
 
         Slog.v(TAG, "reconcileAppsData finished " + preparedCount + " packages");
         return result;
@@ -22933,6 +22959,7 @@
             mSettings.writeKernelMappingLPr(ps);
         }
 
+        Installer.Batch batch = new Installer.Batch();
         UserManagerInternal umInternal = mInjector.getUserManagerInternal();
         StorageManagerInternal smInternal = mInjector.getStorageManagerInternal();
         for (UserInfo user : mUserManager.getUsers(false /*excludeDying*/)) {
@@ -22947,16 +22974,20 @@
 
             if (ps.getInstalled(user.id)) {
                 // TODO: when user data is locked, mark that we're still dirty
-                prepareAppDataLIF(pkg, user.id, flags);
-
-                if (umInternal.isUserUnlockingOrUnlocked(user.id)) {
-                    // Prepare app data on external storage; currently this is used to
-                    // setup any OBB dirs that were created by the installer correctly.
-                    int uid = UserHandle.getUid(user.id, UserHandle.getAppId(pkg.getUid()));
-                    smInternal.prepareAppDataAfterInstall(pkg.getPackageName(), uid);
-                }
+                prepareAppData(batch, pkg, user.id, flags).thenRun(() -> {
+                    // Note: this code block is executed with the Installer lock
+                    // already held, since it's invoked as a side-effect of
+                    // executeBatchLI()
+                    if (umInternal.isUserUnlockingOrUnlocked(user.id)) {
+                        // Prepare app data on external storage; currently this is used to
+                        // setup any OBB dirs that were created by the installer correctly.
+                        int uid = UserHandle.getUid(user.id, UserHandle.getAppId(pkg.getUid()));
+                        smInternal.prepareAppDataAfterInstall(pkg.getPackageName(), uid);
+                    }
+                });
             }
         }
+        executeBatchLI(batch);
     }
 
     /**
@@ -22967,26 +22998,33 @@
      * will try recovering system apps by wiping data; third-party app data is
      * left intact.
      */
-    private void prepareAppDataLIF(AndroidPackage pkg, int userId, int flags) {
+    private @NonNull CompletableFuture<?> prepareAppData(@NonNull Installer.Batch batch,
+            @Nullable AndroidPackage pkg, int userId, int flags) {
         if (pkg == null) {
             Slog.wtf(TAG, "Package was null!", new Throwable());
-            return;
+            return CompletableFuture.completedFuture(null);
         }
-        prepareAppDataLeafLIF(pkg, userId, flags);
+        return prepareAppDataLeaf(batch, pkg, userId, flags);
     }
 
-    private void prepareAppDataAndMigrateLIF(AndroidPackage pkg, int userId, int flags,
-            boolean maybeMigrateAppData) {
-        prepareAppDataLIF(pkg, userId, flags);
-
-        if (maybeMigrateAppData && maybeMigrateAppDataLIF(pkg, userId)) {
-            // We may have just shuffled around app data directories, so
-            // prepare them one more time
-            prepareAppDataLIF(pkg, userId, flags);
-        }
+    private @NonNull CompletableFuture<?> prepareAppDataAndMigrate(@NonNull Installer.Batch batch,
+            @NonNull AndroidPackage pkg, int userId, int flags, boolean maybeMigrateAppData) {
+        return prepareAppData(batch, pkg, userId, flags).thenRun(() -> {
+            // Note: this code block is executed with the Installer lock
+            // already held, since it's invoked as a side-effect of
+            // executeBatchLI()
+            if (maybeMigrateAppData && maybeMigrateAppDataLIF(pkg, userId)) {
+                // We may have just shuffled around app data directories, so
+                // prepare them one more time
+                final Installer.Batch batchInner = new Installer.Batch();
+                prepareAppData(batchInner, pkg, userId, flags);
+                executeBatchLI(batchInner);
+            }
+        });
     }
 
-    private void prepareAppDataLeafLIF(AndroidPackage pkg, int userId, int flags) {
+    private @NonNull CompletableFuture<?> prepareAppDataLeaf(@NonNull Installer.Batch batch,
+            @NonNull AndroidPackage pkg, int userId, int flags) {
         if (DEBUG_APP_DATA) {
             Slog.v(TAG, "prepareAppData for " + pkg.getPackageName() + " u" + userId + " 0x"
                     + Integer.toHexString(flags));
@@ -23006,57 +23044,67 @@
         Preconditions.checkNotNull(pkgSeInfo);
 
         final String seInfo = pkgSeInfo + (pkg.getSeInfoUser() != null ? pkg.getSeInfoUser() : "");
-        long ceDataInode = -1;
-        try {
-            ceDataInode = mInstaller.createAppData(volumeUuid, packageName, userId, flags,
-                    appId, seInfo, pkg.getTargetSdkVersion());
-        } catch (InstallerException e) {
-            if (pkg.isSystem()) {
-                logCriticalInfo(Log.ERROR, "Failed to create app data for " + packageName
-                        + ", but trying to recover: " + e);
-                destroyAppDataLeafLIF(pkg, userId, flags);
-                try {
-                    ceDataInode = mInstaller.createAppData(volumeUuid, packageName, userId, flags,
-                            appId, seInfo, pkg.getTargetSdkVersion());
-                    logCriticalInfo(Log.DEBUG, "Recovery succeeded!");
-                } catch (InstallerException e2) {
-                    logCriticalInfo(Log.DEBUG, "Recovery failed!");
-                }
-            } else {
-                Slog.e(TAG, "Failed to create app data for " + packageName + ": " + e);
-            }
-        }
-        // Prepare the application profiles only for upgrades and first boot (so that we don't
-        // repeat the same operation at each boot).
-        // We only have to cover the upgrade and first boot here because for app installs we
-        // prepare the profiles before invoking dexopt (in installPackageLI).
-        //
-        // We also have to cover non system users because we do not call the usual install package
-        // methods for them.
-        //
-        // NOTE: in order to speed up first boot time we only create the current profile and do not
-        // update the content of the reference profile. A system image should already be configured
-        // with the right profile keys and the profiles for the speed-profile prebuilds should
-        // already be copied. That's done in #performDexOptUpgrade.
-        //
-        // TODO(calin, mathieuc): We should use .dm files for prebuilds profiles instead of
-        // manually copying them in #performDexOptUpgrade. When we do that we should have a more
-        // granular check here and only update the existing profiles.
-        if (mIsUpgrade || mFirstBoot || (userId != UserHandle.USER_SYSTEM)) {
-            mArtManagerService.prepareAppProfiles(pkg, userId,
-                /* updateReferenceProfileContent= */ false);
-        }
+        final int targetSdkVersion = pkg.getTargetSdkVersion();
 
-        if ((flags & StorageManager.FLAG_STORAGE_CE) != 0 && ceDataInode != -1) {
-            // TODO: mark this structure as dirty so we persist it!
-            synchronized (mLock) {
-                if (ps != null) {
-                    ps.setCeDataInode(ceDataInode, userId);
-                }
-            }
-        }
+        return batch.createAppData(volumeUuid, packageName, userId, flags, appId, seInfo,
+                targetSdkVersion).whenComplete((ceDataInode, e) -> {
+                    // Note: this code block is executed with the Installer lock
+                    // already held, since it's invoked as a side-effect of
+                    // executeBatchLI()
+                    if ((e != null) && pkg.isSystem()) {
+                        logCriticalInfo(Log.ERROR, "Failed to create app data for " + packageName
+                                + ", but trying to recover: " + e);
+                        destroyAppDataLeafLIF(pkg, userId, flags);
+                        try {
+                            ceDataInode = mInstaller.createAppData(volumeUuid, packageName, userId,
+                                    flags, appId, seInfo, pkg.getTargetSdkVersion());
+                            logCriticalInfo(Log.DEBUG, "Recovery succeeded!");
+                        } catch (InstallerException e2) {
+                            logCriticalInfo(Log.DEBUG, "Recovery failed!");
+                        }
+                    } else if (e != null) {
+                        Slog.e(TAG, "Failed to create app data for " + packageName + ": " + e);
+                    }
 
-        prepareAppDataContentsLeafLIF(pkg, ps, userId, flags);
+                    // Prepare the application profiles only for upgrades and
+                    // first boot (so that we don't repeat the same operation at
+                    // each boot).
+                    //
+                    // We only have to cover the upgrade and first boot here
+                    // because for app installs we prepare the profiles before
+                    // invoking dexopt (in installPackageLI).
+                    //
+                    // We also have to cover non system users because we do not
+                    // call the usual install package methods for them.
+                    //
+                    // NOTE: in order to speed up first boot time we only create
+                    // the current profile and do not update the content of the
+                    // reference profile. A system image should already be
+                    // configured with the right profile keys and the profiles
+                    // for the speed-profile prebuilds should already be copied.
+                    // That's done in #performDexOptUpgrade.
+                    //
+                    // TODO(calin, mathieuc): We should use .dm files for
+                    // prebuilds profiles instead of manually copying them in
+                    // #performDexOptUpgrade. When we do that we should have a
+                    // more granular check here and only update the existing
+                    // profiles.
+                    if (mIsUpgrade || mFirstBoot || (userId != UserHandle.USER_SYSTEM)) {
+                        mArtManagerService.prepareAppProfiles(pkg, userId,
+                            /* updateReferenceProfileContent= */ false);
+                    }
+
+                    if ((flags & StorageManager.FLAG_STORAGE_CE) != 0 && ceDataInode != -1) {
+                        // TODO: mark this structure as dirty so we persist it!
+                        synchronized (mLock) {
+                            if (ps != null) {
+                                ps.setCeDataInode(ceDataInode, userId);
+                            }
+                        }
+                    }
+
+                    prepareAppDataContentsLeafLIF(pkg, ps, userId, flags);
+                });
     }
 
     private void prepareAppDataContentsLIF(AndroidPackage pkg, @Nullable PackageSetting pkgSetting,
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 7aeec6d..32ec052 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -1043,7 +1043,9 @@
                 + "; isStaged = " + session.isStaged()
                 + "; isReady = " + session.isStagedSessionReady()
                 + "; isApplied = " + session.isStagedSessionApplied()
-                + "; isFailed = " + session.isStagedSessionFailed() + ";");
+                + "; isFailed = " + session.isStagedSessionFailed()
+                + "; errorMsg = " + session.getStagedSessionErrorMessage()
+                + ";");
     }
 
     private Intent parseIntentAndUser() throws URISyntaxException {
@@ -3338,7 +3340,7 @@
             session = new PackageInstaller.Session(
                     mInterface.getPackageInstaller().openSession(sessionId));
             if (!session.isMultiPackage() && !session.isStaged()) {
-                // Sanity check that all .dm files match an apk.
+                // Validity check that all .dm files match an apk.
                 // (The installer does not support standalone .dm files and will not process them.)
                 try {
                     DexMetadataHelper.validateDexPaths(session.getNames());
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 3e3e3c5..acb149b 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -102,6 +102,7 @@
 import com.android.permission.persistence.RuntimePermissionsPersistence;
 import com.android.permission.persistence.RuntimePermissionsState;
 import com.android.server.LocalServices;
+import com.android.server.pm.Installer.Batch;
 import com.android.server.pm.Installer.InstallerException;
 import com.android.server.pm.parsing.PackageInfoUtils;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
@@ -4148,24 +4149,12 @@
         final TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG + "Timing",
                 Trace.TRACE_TAG_PACKAGE_MANAGER);
         t.traceBegin("createNewUser-" + userHandle);
-        String[] volumeUuids;
-        String[] names;
-        int[] appIds;
-        String[] seinfos;
-        int[] targetSdkVersions;
-        int packagesCount;
+        Installer.Batch batch = new Installer.Batch();
         final boolean skipPackageWhitelist = userTypeInstallablePackages == null;
         synchronized (mLock) {
-            Collection<PackageSetting> packages = mPackages.values();
-            packagesCount = packages.size();
-            volumeUuids = new String[packagesCount];
-            names = new String[packagesCount];
-            appIds = new int[packagesCount];
-            seinfos = new String[packagesCount];
-            targetSdkVersions = new int[packagesCount];
-            Iterator<PackageSetting> packagesIterator = packages.iterator();
-            for (int i = 0; i < packagesCount; i++) {
-                PackageSetting ps = packagesIterator.next();
+            final int size = mPackages.size();
+            for (int i = 0; i < size; i++) {
+                final PackageSetting ps = mPackages.valueAt(i);
                 if (ps.pkg == null) {
                     continue;
                 }
@@ -4187,18 +4176,15 @@
                 }
                 // Need to create a data directory for all apps under this user. Accumulate all
                 // required args and call the installer after mPackages lock has been released
-                volumeUuids[i] = ps.volumeUuid;
-                names[i] = ps.name;
-                appIds[i] = ps.appId;
-                seinfos[i] = AndroidPackageUtils.getSeInfo(ps.pkg, ps);
-                targetSdkVersions[i] = ps.pkg.getTargetSdkVersion();
+                final String seInfo = AndroidPackageUtils.getSeInfo(ps.pkg, ps);
+                batch.createAppData(ps.volumeUuid, ps.name, userHandle,
+                        StorageManager.FLAG_STORAGE_CE | StorageManager.FLAG_STORAGE_DE, ps.appId,
+                        seInfo, ps.pkg.getTargetSdkVersion());
             }
         }
         t.traceBegin("createAppData");
-        final int flags = StorageManager.FLAG_STORAGE_CE | StorageManager.FLAG_STORAGE_DE;
         try {
-            installer.createAppDataBatched(volumeUuids, names, userHandle, flags, appIds, seinfos,
-                    targetSdkVersions);
+            batch.execute(installer);
         } catch (InstallerException e) {
             Slog.w(TAG, "Failed to prepare app data", e);
         }
@@ -4647,6 +4633,9 @@
             pw.print(prefix); pw.print("  resourcePath="); pw.println(ps.getCodePathString());
             pw.print(prefix); pw.print("  legacyNativeLibraryDir=");
             pw.println(ps.legacyNativeLibraryPathString);
+            pw.print(prefix); pw.print("  extractNativeLibs=");
+            pw.println((ps.pkgFlags & ApplicationInfo.FLAG_EXTRACT_NATIVE_LIBS) != 0
+                    ? "true" : "false");
             pw.print(prefix); pw.print("  primaryCpuAbi="); pw.println(ps.primaryCpuAbiString);
             pw.print(prefix); pw.print("  secondaryCpuAbi="); pw.println(ps.secondaryCpuAbiString);
         }
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index f9bf54a..6d80f08 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -56,6 +56,7 @@
 import android.os.storage.IStorageManager;
 import android.os.storage.StorageManager;
 import android.text.TextUtils;
+import android.util.ArraySet;
 import android.util.IntArray;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -84,6 +85,7 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
@@ -589,13 +591,14 @@
             // If checkpoint is supported, then we only resume sessions if we are in checkpointing
             // mode. If not, we fail all sessions.
             if (supportsCheckpoint() && !needsCheckpoint()) {
-                String errorMsg = "Reverting back to safe state. Marking " + session.sessionId
-                        + " as failed";
-                if (!TextUtils.isEmpty(mFailureReason)) {
-                    errorMsg = errorMsg + ": " + mFailureReason;
+                String revertMsg = "Reverting back to safe state. Marking "
+                        + session.sessionId + " as failed.";
+                final String reasonForRevert = getReasonForRevert();
+                if (!TextUtils.isEmpty(reasonForRevert)) {
+                    revertMsg += " Reason for revert: " + reasonForRevert;
                 }
-                Slog.d(TAG, errorMsg);
-                session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN, errorMsg);
+                Slog.d(TAG, revertMsg);
+                session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN, revertMsg);
                 return;
             }
         } catch (RemoteException e) {
@@ -650,6 +653,7 @@
         try {
             if (hasApex) {
                 checkInstallationOfApkInApexSuccessful(session);
+                checkDuplicateApkInApex(session);
                 snapshotAndRestoreForApexSession(session);
                 Slog.i(TAG, "APEX packages in session " + session.sessionId
                         + " were successfully activated. Proceeding with APK packages, if any");
@@ -699,6 +703,16 @@
         }
     }
 
+    private String getReasonForRevert() {
+        if (!TextUtils.isEmpty(mFailureReason)) {
+            return mFailureReason;
+        }
+        if (!TextUtils.isEmpty(mNativeFailureReason)) {
+            return "Session reverted due to crashing native process: " + mNativeFailureReason;
+        }
+        return "";
+    }
+
     private List<String> findAPKsInDir(File stageDir) {
         List<String> ret = new ArrayList<>();
         if (stageDir != null && stageDir.exists()) {
@@ -829,6 +843,40 @@
         return null;
     }
 
+    /**
+     * Throws a PackageManagerException if there are duplicate packages in apk and apk-in-apex.
+     */
+    private void checkDuplicateApkInApex(@NonNull PackageInstallerSession session)
+            throws PackageManagerException {
+        if (!session.isMultiPackage()) {
+            return;
+        }
+        final int[] childSessionIds = session.getChildSessionIds();
+        final Set<String> apkNames = new ArraySet<>();
+        synchronized (mStagedSessions) {
+            for (int id : childSessionIds) {
+                final PackageInstallerSession s = mStagedSessions.get(id);
+                if (!isApexSession(s)) {
+                    apkNames.add(s.getPackageName());
+                }
+            }
+        }
+        final List<PackageInstallerSession> apexSessions = extractApexSessions(session);
+        for (PackageInstallerSession apexSession : apexSessions) {
+            String packageName = apexSession.getPackageName();
+            for (String apkInApex : mApexManager.getApksInApex(packageName)) {
+                if (!apkNames.add(apkInApex)) {
+                    throw new PackageManagerException(
+                            SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
+                            "Package: " + packageName + " in session: "
+                                    + apexSession.sessionId + " has duplicate apk-in-apex: "
+                                    + apkInApex, null);
+
+                }
+            }
+        }
+    }
+
     private void installApksInSession(@NonNull PackageInstallerSession session)
             throws PackageManagerException {
 
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index f66b4ee..d137fd0 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -499,20 +499,23 @@
             states = new SparseIntArray();
             invalidateIsUserUnlockedCache();
         }
-        public int get(int userId) {
+        public int get(@UserIdInt int userId) {
             return states.get(userId);
         }
-        public int get(int userId, int fallback) {
+        public int get(@UserIdInt int userId, int fallback) {
             return states.indexOfKey(userId) >= 0 ? states.get(userId) : fallback;
         }
-        public void put(int userId, int state) {
+        public void put(@UserIdInt int userId, int state) {
             states.put(userId, state);
             invalidateIsUserUnlockedCache();
         }
-        public void delete(int userId) {
+        public void delete(@UserIdInt int userId) {
             states.delete(userId);
             invalidateIsUserUnlockedCache();
         }
+        public boolean has(@UserIdInt int userId) {
+            return states.get(userId, UserHandle.USER_NULL) != UserHandle.USER_NULL;
+        }
         @Override
         public String toString() {
             return states.toString();
@@ -3561,6 +3564,13 @@
         if (preCreatedUserData == null) {
             return null;
         }
+        synchronized (mUserStates) {
+            if (mUserStates.has(preCreatedUserData.info.id)) {
+                Slog.w(LOG_TAG, "Cannot reuse pre-created user "
+                        + preCreatedUserData.info.id + " because it didn't stop yet");
+                return null;
+            }
+        }
         final UserInfo preCreatedUser = preCreatedUserData.info;
         final int newFlags = preCreatedUser.flags | flags;
         if (!checkUserTypeConsistency(newFlags)) {
@@ -5225,6 +5235,7 @@
             return userData == null ? null : userData.info;
         }
 
+        @Override
         public @NonNull UserInfo[] getUserInfos() {
             synchronized (mUsersLock) {
                 int userSize = mUsers.size();
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 63aa80e..1be7415 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -881,7 +881,7 @@
     }
 
     @Override
-    public int checkPermission(String permName, String pkgName, int userId) {
+    public int checkPermission(String permName, String pkgName, @UserIdInt int userId) {
         // Not using Objects.requireNonNull() here for compatibility reasons.
         if (permName == null || pkgName == null) {
             return PackageManager.PERMISSION_DENIED;
@@ -2464,7 +2464,28 @@
             return null;
         }
         final PermissionsState permissionsState = ps.getPermissionsState();
-        return permissionsState.getPermissions(userId);
+        if (!ps.getInstantApp(userId)) {
+            return permissionsState.getPermissions(userId);
+        } else {
+            // Install permission state is shared among all users, but instant app state is
+            // per-user, so we can only filter it here unless we make install permission state
+            // per-user as well.
+            final Set<String> instantPermissions = new ArraySet<>(permissionsState.getPermissions(
+                    userId));
+            instantPermissions.removeIf(permissionName -> {
+                BasePermission permission = mSettings.getPermission(permissionName);
+                if (permission == null) {
+                    return true;
+                }
+                if (!permission.isInstant()) {
+                    EventLog.writeEvent(0x534e4554, "140256621", UserHandle.getUid(userId,
+                            ps.getAppId()), permissionName);
+                    return true;
+                }
+                return false;
+            });
+            return instantPermissions;
+        }
     }
 
     @Nullable
@@ -4427,7 +4448,7 @@
      * @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 enforceCrossUserPermission(int callingUid, int userId,
+    private void enforceCrossUserPermission(int callingUid, @UserIdInt int userId,
             boolean requireFullPermission, boolean checkShell,
             boolean requirePermissionWhenSameUser, String message) {
         if (userId < 0) {
@@ -4444,7 +4465,7 @@
             return;
         }
         String errorMessage = buildInvalidCrossUserPermissionMessage(
-                message, requireFullPermission);
+                callingUid, userId, message, requireFullPermission);
         Slog.w(TAG, errorMessage);
         throw new SecurityException(errorMessage);
     }
@@ -4463,7 +4484,7 @@
      * @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,
+    private void enforceCrossUserOrProfilePermission(int callingUid, @UserIdInt int userId,
             boolean requireFullPermission, boolean checkShell,
             String message) {
         if (userId < 0) {
@@ -4489,7 +4510,7 @@
             return;
         }
         String errorMessage = buildInvalidCrossUserOrProfilePermissionMessage(
-                message, requireFullPermission, isSameProfileGroup);
+                callingUid, userId, message, requireFullPermission, isSameProfileGroup);
         Slog.w(TAG, errorMessage);
         throw new SecurityException(errorMessage);
     }
@@ -4524,44 +4545,48 @@
         }
     }
 
-    private static String buildInvalidCrossUserPermissionMessage(
-            String message, boolean requireFullPermission) {
+    private static String buildInvalidCrossUserPermissionMessage(int callingUid,
+            @UserIdInt int userId, String message, boolean requireFullPermission) {
         StringBuilder builder = new StringBuilder();
         if (message != null) {
             builder.append(message);
             builder.append(": ");
         }
-        builder.append("Requires ");
+        builder.append("UID ");
+        builder.append(callingUid);
+        builder.append(" requires ");
         builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
-        if (requireFullPermission) {
-            builder.append(".");
-            return builder.toString();
+        if (!requireFullPermission) {
+            builder.append(" or ");
+            builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS);
         }
-        builder.append(" or ");
-        builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS);
+        builder.append(" to access user ");
+        builder.append(userId);
         builder.append(".");
         return builder.toString();
     }
 
-    private static String buildInvalidCrossUserOrProfilePermissionMessage(
-            String message, boolean requireFullPermission, boolean isSameProfileGroup) {
+    private static String buildInvalidCrossUserOrProfilePermissionMessage(int callingUid,
+            @UserIdInt int userId, String message, boolean requireFullPermission,
+            boolean isSameProfileGroup) {
         StringBuilder builder = new StringBuilder();
         if (message != null) {
             builder.append(message);
             builder.append(": ");
         }
-        builder.append("Requires ");
+        builder.append("UID ");
+        builder.append(callingUid);
+        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) {
+        if (!requireFullPermission) {
             builder.append(" or ");
-            builder.append(android.Manifest.permission.INTERACT_ACROSS_PROFILES);
+            builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS);
+            if (isSameProfileGroup) {
+                builder.append(" or ");
+                builder.append(android.Manifest.permission.INTERACT_ACROSS_PROFILES);
+            }
         }
+        builder.append(" to access user ");
         builder.append(".");
         return builder.toString();
     }
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index 4d48a2e..ae2b040 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -89,7 +89,7 @@
 public final class PermissionPolicyService extends SystemService {
     private static final String LOG_TAG = PermissionPolicyService.class.getSimpleName();
     private static final boolean DEBUG = false;
-    private static final long USER_SENSITIVE_UPDATE_DELAY_MS = 10000;
+    private static final long USER_SENSITIVE_UPDATE_DELAY_MS = 60000;
 
     private final Object mLock = new Object();
 
@@ -283,6 +283,11 @@
                 manager.updateUserSensitiveForApp(uid);
             }
         }, UserHandle.ALL, intentFilter, null, null);
+
+        PermissionControllerManager manager = new PermissionControllerManager(
+                getUserContext(getContext(), Process.myUserHandle()), FgThread.getHandler());
+        FgThread.getHandler().postDelayed(manager::updateUserSensitive,
+                USER_SENSITIVE_UPDATE_DELAY_MS);
     }
 
     /**
@@ -425,8 +430,7 @@
                 throw new IllegalStateException(e);
             }
 
-            FgThread.getHandler().postDelayed(permissionControllerManager::updateUserSensitive,
-                    USER_SENSITIVE_UPDATE_DELAY_MS);
+            permissionControllerManager.updateUserSensitive();
 
             packageManagerInternal.updateRuntimePermissionsFingerprint(userId);
         }
diff --git a/services/core/java/com/android/server/power/ThermalManagerService.java b/services/core/java/com/android/server/power/ThermalManagerService.java
index ef4954a..ae0db44 100644
--- a/services/core/java/com/android/server/power/ThermalManagerService.java
+++ b/services/core/java/com/android/server/power/ThermalManagerService.java
@@ -200,7 +200,7 @@
         final int count = mTemperatureMap.size();
         for (int i = 0; i < count; i++) {
             Temperature t = mTemperatureMap.valueAt(i);
-            if (t.getStatus() >= newStatus) {
+            if (t.getType() == Temperature.TYPE_SKIN && t.getStatus() >= newStatus) {
                 newStatus = t.getStatus();
             }
         }
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 6adff0d..0c85387 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -1156,7 +1156,7 @@
         }
 
         private void enforceListenerPermission() {
-            mContext.enforceCallingPermission(Manifest.permission.TRUST_LISTENER,
+            mContext.enforceCallingOrSelfPermission(Manifest.permission.TRUST_LISTENER,
                     "register trust listener");
         }
 
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index b3ec849..8ccbbdd 100755
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -99,6 +99,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.UUID;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -1177,7 +1178,8 @@
             final int resolvedUserId = resolveCallingUserId(callingPid, callingUid,
                     userId, "createSession");
             final long identity = Binder.clearCallingIdentity();
-            StringBuilder sessionId = new StringBuilder();
+            // Generate a unique session id with a random UUID.
+            String uniqueSessionId = UUID.randomUUID().toString();
             try {
                 synchronized (mLock) {
                     if (userId != mCurrentUserId && !isRecordingSession) {
@@ -1206,20 +1208,17 @@
                         return;
                     }
 
-                    // Create a unique session id with pid, uid and resolved user id
-                    sessionId.append(callingUid).append(callingPid).append(resolvedUserId);
-
                     // Create a new session token and a session state.
                     IBinder sessionToken = new Binder();
                     SessionState sessionState = new SessionState(sessionToken, info.getId(),
                             info.getComponent(), isRecordingSession, client, seq, callingUid,
-                            callingPid, resolvedUserId, sessionId.toString());
+                            callingPid, resolvedUserId, uniqueSessionId);
 
                     // Add them to the global session state map of the current user.
                     userState.sessionStateMap.put(sessionToken, sessionState);
 
                     // Map the session id to the sessionStateMap in the user state
-                    mSessionIdToSessionStateMap.put(sessionId.toString(), sessionState);
+                    mSessionIdToSessionStateMap.put(uniqueSessionId, sessionState);
 
                     // Also, add them to the session state map of the current service.
                     serviceState.sessionTokens.add(sessionToken);
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
index 2b0fe8a..2fc17fe 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
@@ -68,6 +68,11 @@
     private Set<Integer> mUsingFrontendIds = new HashSet<>();
 
     /**
+     * List of the client ids that share frontend with the current client.
+     */
+    private Set<Integer> mShareFeClientIds = new HashSet<>();
+
+    /**
      * List of the Lnb ids that are used by the current client.
      */
     private Set<Integer> mUsingLnbIds = new HashSet<>();
@@ -113,11 +118,7 @@
     }
 
     public int getPriority() {
-        return mPriority;
-    }
-
-    public int getNiceValue() {
-        return mNiceValue;
+        return mPriority - mNiceValue;
     }
 
     public void setGroupId(int groupId) {
@@ -141,17 +142,38 @@
         mUsingFrontendIds.add(frontendId);
     }
 
+    /**
+     * Update the set of client that share frontend with the current client.
+     *
+     * @param clientId the client to share the fe with the current client.
+     */
+    public void shareFrontend(int clientId) {
+        mShareFeClientIds.add(clientId);
+    }
+
+    /**
+     * Remove the given client id from the share frontend client id set.
+     *
+     * @param clientId the client to stop sharing the fe with the current client.
+     */
+    public void stopSharingFrontend(int clientId) {
+        mShareFeClientIds.remove(clientId);
+    }
+
     public Set<Integer> getInUseFrontendIds() {
         return mUsingFrontendIds;
     }
 
+    public Set<Integer> getShareFeClientIds() {
+        return mShareFeClientIds;
+    }
+
     /**
      * Called when the client released a frontend.
-     *
-     * @param frontendId being released.
      */
-    public void releaseFrontend(int frontendId) {
-        mUsingFrontendIds.remove(frontendId);
+    public void releaseFrontend() {
+        mUsingFrontendIds.clear();
+        mShareFeClientIds.clear();
     }
 
     /**
@@ -201,6 +223,7 @@
      */
     public void reclaimAllResources() {
         mUsingFrontendIds.clear();
+        mShareFeClientIds.clear();
         mUsingLnbIds.clear();
         mUsingCasSystemId = INVALID_RESOURCE_ID;
     }
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
index 7cb59dc..fb2347e 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
@@ -210,19 +210,36 @@
             }
             synchronized (mLock) {
                 if (!checkClientExists(request.getClientId())) {
-                    throw new RemoteException("Request frontend from unregistered client:"
+                    throw new RemoteException("Request frontend from unregistered client: "
                             + request.getClientId());
                 }
+                // If the request client is holding or sharing a frontend, throw an exception.
+                if (!getClientProfile(request.getClientId()).getInUseFrontendIds().isEmpty()) {
+                    throw new RemoteException("Release frontend before requesting another one. "
+                            + "Client id: " + request.getClientId());
+                }
                 return requestFrontendInternal(request, frontendHandle);
             }
         }
 
         @Override
-        public void shareFrontend(int selfClientId, int targetClientId) {
+        public void shareFrontend(int selfClientId, int targetClientId) throws RemoteException {
             enforceTunerAccessPermission("shareFrontend");
             enforceTrmAccessPermission("shareFrontend");
-            if (DEBUG) {
-                Slog.d(TAG, "shareFrontend from " + selfClientId + " with " + targetClientId);
+            synchronized (mLock) {
+                if (!checkClientExists(selfClientId)) {
+                    throw new RemoteException("Share frontend request from an unregistered client:"
+                            + selfClientId);
+                }
+                if (!checkClientExists(targetClientId)) {
+                    throw new RemoteException("Request to share frontend with an unregistered "
+                            + "client:" + targetClientId);
+                }
+                if (getClientProfile(targetClientId).getInUseFrontendIds().isEmpty()) {
+                    throw new RemoteException("Request to share frontend with a client that has no "
+                            + "frontend resources. Target client id:" + targetClientId);
+                }
+                shareFrontendInternal(selfClientId, targetClientId);
             }
         }
 
@@ -315,7 +332,7 @@
                     throw new RemoteException(
                             "Client is not the current owner of the releasing fe.");
                 }
-                releaseFrontendInternal(fe);
+                releaseFrontendInternal(fe, clientId);
             }
         }
 
@@ -649,6 +666,17 @@
     }
 
     @VisibleForTesting
+    protected void shareFrontendInternal(int selfClientId, int targetClientId) {
+        if (DEBUG) {
+            Slog.d(TAG, "shareFrontend from " + selfClientId + " with " + targetClientId);
+        }
+        for (int feId : getClientProfile(targetClientId).getInUseFrontendIds()) {
+            getClientProfile(selfClientId).useFrontend(feId);
+        }
+        getClientProfile(targetClientId).shareFrontend(selfClientId);
+    }
+
+    @VisibleForTesting
     protected boolean requestLnbInternal(TunerLnbRequest request, int[] lnbHandle) {
         if (DEBUG) {
             Slog.d(TAG, "requestLnb(request=" + request + ")");
@@ -777,11 +805,17 @@
     }
 
     @VisibleForTesting
-    protected void releaseFrontendInternal(FrontendResource fe) {
+    protected void releaseFrontendInternal(FrontendResource fe, int clientId) {
         if (DEBUG) {
-            Slog.d(TAG, "releaseFrontend(id=" + fe.getId() + ")");
+            Slog.d(TAG, "releaseFrontend(id=" + fe.getId() + ", clientId=" + clientId + " )");
         }
-        updateFrontendClientMappingOnRelease(fe);
+        if (clientId == fe.getOwnerClientId()) {
+            ClientProfile ownerClient = getClientProfile(fe.getOwnerClientId());
+            for (int shareOwnerId : ownerClient.getShareFeClientIds()) {
+                clearFrontendAndClientMapping(getClientProfile(shareOwnerId));
+            }
+        }
+        clearFrontendAndClientMapping(getClientProfile(clientId));
     }
 
     @VisibleForTesting
@@ -882,8 +916,21 @@
             Slog.e(TAG, "Failed to reclaim resources on client " + reclaimingClientId, e);
             return false;
         }
+
+        // Reclaim all the resources of the share owners of the frontend that is used by the current
+        // resource reclaimed client.
         ClientProfile profile = getClientProfile(reclaimingClientId);
-        reclaimingResourcesFromClient(profile);
+        Set<Integer> shareFeClientIds = profile.getShareFeClientIds();
+        for (int clientId : shareFeClientIds) {
+            try {
+                mListeners.get(clientId).getListener().onReclaimResources();
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to reclaim resources on client " + clientId, e);
+                return false;
+            }
+            clearAllResourcesAndClientMapping(getClientProfile(clientId));
+        }
+        clearAllResourcesAndClientMapping(profile);
         return true;
     }
 
@@ -929,16 +976,6 @@
         }
     }
 
-    private void updateFrontendClientMappingOnRelease(@NonNull FrontendResource releasingFrontend) {
-        ClientProfile ownerProfile = getClientProfile(releasingFrontend.getOwnerClientId());
-        releasingFrontend.removeOwner();
-        ownerProfile.releaseFrontend(releasingFrontend.getId());
-        for (int exclusiveGroupMember : releasingFrontend.getExclusiveGroupMemberFeIds()) {
-            getFrontendResource(exclusiveGroupMember).removeOwner();
-            ownerProfile.releaseFrontend(exclusiveGroupMember);
-        }
-    }
-
     private void updateLnbClientMappingOnNewGrant(int grantingId, int ownerClientId) {
         LnbResource grantingLnb = getLnbResource(grantingId);
         ClientProfile ownerProfile = getClientProfile(ownerClientId);
@@ -967,10 +1004,10 @@
     }
 
     /**
-     * Get the owner client's priority from the resource id.
+     * Get the owner client's priority.
      *
      * @param clientId the owner client id.
-     * @return the priority of the owner client of the resource.
+     * @return the priority of the owner client.
      */
     private int getOwnerClientPriority(int clientId) {
         return getClientProfile(clientId).getPriority();
@@ -1011,7 +1048,11 @@
             return;
         }
         if (fe.isInUse()) {
-            releaseFrontendInternal(fe);
+            ClientProfile ownerClient = getClientProfile(fe.getOwnerClientId());
+            for (int shareOwnerId : ownerClient.getShareFeClientIds()) {
+                clearFrontendAndClientMapping(getClientProfile(shareOwnerId));
+            }
+            clearFrontendAndClientMapping(ownerClient);
         }
         for (int excGroupmemberFeId : fe.getExclusiveGroupMemberFeIds()) {
             getFrontendResource(excGroupmemberFeId)
@@ -1093,21 +1134,37 @@
     }
 
     private void removeClientProfile(int clientId) {
-        reclaimingResourcesFromClient(getClientProfile(clientId));
+        for (int shareOwnerId : getClientProfile(clientId).getShareFeClientIds()) {
+            clearFrontendAndClientMapping(getClientProfile(shareOwnerId));
+        }
+        clearAllResourcesAndClientMapping(getClientProfile(clientId));
         mClientProfiles.remove(clientId);
         mListeners.remove(clientId);
     }
 
-    private void reclaimingResourcesFromClient(ClientProfile profile) {
+    private void clearFrontendAndClientMapping(ClientProfile profile) {
         for (Integer feId : profile.getInUseFrontendIds()) {
-            getFrontendResource(feId).removeOwner();
+            FrontendResource fe = getFrontendResource(feId);
+            if (fe.getOwnerClientId() == profile.getId()) {
+                fe.removeOwner();
+                continue;
+            }
+            getClientProfile(fe.getOwnerClientId()).stopSharingFrontend(profile.getId());
         }
+        profile.releaseFrontend();
+    }
+
+    private void clearAllResourcesAndClientMapping(ClientProfile profile) {
+        // Clear Lnb
         for (Integer lnbId : profile.getInUseLnbIds()) {
             getLnbResource(lnbId).removeOwner();
         }
+        // Clear Cas
         if (profile.getInUseCasSystemId() != ClientProfile.INVALID_RESOURCE_ID) {
             getCasResource(profile.getInUseCasSystemId()).removeOwner(profile.getId());
         }
+        // Clear Frontend
+        clearFrontendAndClientMapping(profile);
         profile.reclaimAllResources();
     }
 
diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java b/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java
index cdb6199..5772dea 100644
--- a/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java
+++ b/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java
@@ -75,10 +75,31 @@
     void removeUriPermissionsForPackage(
             String packageName, int userHandle, boolean persistable, boolean targetOnly);
     /**
-     * @param uri This uri must NOT contain an embedded userId.
+     * Remove any {@link UriPermission} associated with the owner whose values match the given
+     * filtering parameters.
+     *
+     * @param token An opaque owner token as returned by {@link #newUriPermissionOwner(String)}.
+     * @param uri This uri must NOT contain an embedded userId. {@code null} to apply to all Uris.
+     * @param mode The modes (as a bitmask) to revoke.
      * @param userId The userId in which the uri is to be resolved.
      */
     void revokeUriPermissionFromOwner(IBinder token, Uri uri, int mode, int userId);
+
+    /**
+     * Remove any {@link UriPermission} associated with the owner whose values match the given
+     * filtering parameters.
+     *
+     * @param token An opaque owner token as returned by {@link #newUriPermissionOwner(String)}.
+     * @param uri This uri must NOT contain an embedded userId. {@code null} to apply to all Uris.
+     * @param mode The modes (as a bitmask) to revoke.
+     * @param userId The userId in which the uri is to be resolved.
+     * @param targetPkg Calling package name to match, or {@code null} to apply to all packages.
+     * @param targetUserId Calling user to match, or {@link UserHandle#USER_ALL} to apply to all
+     *                     users.
+     */
+    void revokeUriPermissionFromOwner(IBinder token, Uri uri, int mode, int userId,
+            String targetPkg, int targetUserId);
+
     boolean checkAuthorityGrants(
             int callingUid, ProviderInfo cpi, int userId, boolean checkUser);
     void dump(PrintWriter pw, boolean dumpAll, String dumpPackage);
diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
index f5e1602..a106dc6 100644
--- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java
+++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
@@ -51,7 +51,6 @@
 import android.app.GrantedUriPermission;
 import android.app.IUriGrantsManager;
 import android.content.ClipData;
-import android.content.ComponentName;
 import android.content.ContentProvider;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -88,11 +87,11 @@
 import com.android.server.SystemService;
 import com.android.server.SystemServiceManager;
 
-import libcore.io.IoUtils;
-
 import com.google.android.collect.Lists;
 import com.google.android.collect.Maps;
 
+import libcore.io.IoUtils;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
@@ -1431,16 +1430,18 @@
 
         @Override
         public void revokeUriPermissionFromOwner(IBinder token, Uri uri, int mode, int userId) {
+            revokeUriPermissionFromOwner(token, uri, mode, userId, null, UserHandle.USER_ALL);
+        }
+
+        @Override
+        public void revokeUriPermissionFromOwner(IBinder token, Uri uri, int mode, int userId,
+                String targetPkg, int targetUserId) {
             final UriPermissionOwner owner = UriPermissionOwner.fromExternalToken(token);
             if (owner == null) {
                 throw new IllegalArgumentException("Unknown owner: " + token);
             }
-
-            if (uri == null) {
-                owner.removeUriPermissions(mode);
-            } else {
-                owner.removeUriPermission(new GrantUri(userId, uri, mode), mode);
-            }
+            GrantUri grantUri = uri == null ? null : new GrantUri(userId, uri, mode);
+            owner.removeUriPermission(grantUri, mode, targetPkg, targetUserId);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/uri/UriPermissionOwner.java b/services/core/java/com/android/server/uri/UriPermissionOwner.java
index 2b404a4..0c26399 100644
--- a/services/core/java/com/android/server/uri/UriPermissionOwner.java
+++ b/services/core/java/com/android/server/uri/UriPermissionOwner.java
@@ -21,6 +21,7 @@
 
 import android.os.Binder;
 import android.os.IBinder;
+import android.os.UserHandle;
 import android.util.ArraySet;
 import android.util.proto.ProtoOutputStream;
 
@@ -74,30 +75,47 @@
     }
 
     void removeUriPermission(GrantUri grantUri, int mode) {
+        removeUriPermission(grantUri, mode, null, UserHandle.USER_ALL);
+    }
+
+    void removeUriPermission(GrantUri grantUri, int mode, String targetPgk, int targetUserId) {
         if ((mode & FLAG_GRANT_READ_URI_PERMISSION) != 0 && mReadPerms != null) {
             Iterator<UriPermission> it = mReadPerms.iterator();
             while (it.hasNext()) {
                 UriPermission perm = it.next();
-                if (grantUri == null || grantUri.equals(perm.uri)) {
-                    perm.removeReadOwner(this);
-                    mService.removeUriPermissionIfNeeded(perm);
-                    it.remove();
+                if (grantUri != null && !grantUri.equals(perm.uri)) {
+                    continue;
                 }
+                if (targetPgk != null && !targetPgk.equals(perm.targetPkg)) {
+                    continue;
+                }
+                if (targetUserId != UserHandle.USER_ALL && targetUserId != perm.targetUserId) {
+                    continue;
+                }
+                perm.removeReadOwner(this);
+                mService.removeUriPermissionIfNeeded(perm);
+                it.remove();
             }
             if (mReadPerms.isEmpty()) {
                 mReadPerms = null;
             }
         }
-        if ((mode & FLAG_GRANT_WRITE_URI_PERMISSION) != 0
-                && mWritePerms != null) {
+        if ((mode & FLAG_GRANT_WRITE_URI_PERMISSION) != 0 && mWritePerms != null) {
             Iterator<UriPermission> it = mWritePerms.iterator();
             while (it.hasNext()) {
                 UriPermission perm = it.next();
-                if (grantUri == null || grantUri.equals(perm.uri)) {
-                    perm.removeWriteOwner(this);
-                    mService.removeUriPermissionIfNeeded(perm);
-                    it.remove();
+                if (grantUri != null && !grantUri.equals(perm.uri)) {
+                    continue;
                 }
+                if (targetPgk != null && !targetPgk.equals(perm.targetPkg)) {
+                    continue;
+                }
+                if (targetUserId != UserHandle.USER_ALL && targetUserId != perm.targetUserId) {
+                    continue;
+                }
+                perm.removeWriteOwner(this);
+                mService.removeUriPermissionIfNeeded(perm);
+                it.remove();
             }
             if (mWritePerms.isEmpty()) {
                 mWritePerms = null;
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 7565d8f..6e9526a 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -542,7 +542,7 @@
                     + " processSwitch=" + processSwitch + " info=" + info);
         }
 
-        if (launchedActivity.mDrawn) {
+        if (launchedActivity.isReportedDrawn()) {
             // Launched activity is already visible. We cannot measure windows drawn delay.
             abort(info, "launched activity already visible");
             return;
@@ -681,7 +681,7 @@
 
     /** @return {@code true} if the given task has an activity will be drawn. */
     private static boolean hasActivityToBeDrawn(Task t) {
-        return t.forAllActivities((r) -> r.mVisibleRequested && !r.mDrawn && !r.finishing);
+        return t.forAllActivities(r -> r.mVisibleRequested && !r.isReportedDrawn() && !r.finishing);
     }
 
     private void checkVisibility(Task t, ActivityRecord r) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index e4f2854..64fa6ca 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -499,7 +499,6 @@
                                            // and reporting to the client that it is hidden.
     private boolean mSetToSleep; // have we told the activity to sleep?
     boolean nowVisible;     // is this activity's window visible?
-    boolean mDrawn;          // is this activity's window drawn?
     boolean mClientVisibilityDeferred;// was the visibility change message to client deferred?
     boolean idle;           // has the activity gone idle?
     boolean hasBeenLaunched;// has this activity ever been launched?
@@ -564,8 +563,8 @@
     private boolean mClientVisible;
 
     boolean firstWindowDrawn;
-    // Last drawn state we reported to the app token.
-    private boolean reportedDrawn;
+    /** Whether the visible window(s) of this activity is drawn. */
+    private boolean mReportedDrawn;
     private final WindowState.UpdateReportedVisibilityResults mReportedVisibilityResults =
             new WindowState.UpdateReportedVisibilityResults();
 
@@ -927,7 +926,7 @@
         pw.println(prefix + "mVisibleRequested=" + mVisibleRequested
                 + " mVisible=" + mVisible + " mClientVisible=" + mClientVisible
                 + ((mDeferHidingClient) ? " mDeferHidingClient=" + mDeferHidingClient : "")
-                + " reportedDrawn=" + reportedDrawn + " reportedVisible=" + reportedVisible);
+                + " reportedDrawn=" + mReportedDrawn + " reportedVisible=" + reportedVisible);
         if (paused) {
             pw.print(prefix); pw.print("paused="); pw.println(paused);
         }
@@ -1213,14 +1212,6 @@
         return task;
     }
 
-    /**
-     * Sets the Task on this activity for the purposes of re-use during launch where we will
-     * re-use another activity instead of this one for the launch.
-     */
-    void setTaskForReuse(Task task) {
-        this.task = task;
-    }
-
     Task getStack() {
         return task != null ? task.getRootTask() : null;
     }
@@ -1591,7 +1582,6 @@
         keysPaused = false;
         inHistory = false;
         nowVisible = false;
-        mDrawn = false;
         mClientVisible = true;
         idle = false;
         hasBeenLaunched = false;
@@ -5404,11 +5394,7 @@
     }
 
     /** Called when the windows associated app window container are drawn. */
-    void onWindowsDrawn(boolean drawn, long timestampNs) {
-        mDrawn = drawn;
-        if (!drawn) {
-            return;
-        }
+    private void onWindowsDrawn(long timestampNs) {
         final TransitionInfoSnapshot info = mStackSupervisor
                 .getActivityMetricsLogger().notifyWindowsDrawn(this, timestampNs);
         final boolean validInfo = info != null;
@@ -5514,7 +5500,7 @@
         if (!nowGone) {
             // If the app is not yet gone, then it can only become visible/drawn.
             if (!nowDrawn) {
-                nowDrawn = reportedDrawn;
+                nowDrawn = mReportedDrawn;
             }
             if (!nowVisible) {
                 nowVisible = reportedVisible;
@@ -5522,9 +5508,11 @@
         }
         if (DEBUG_VISIBILITY) Slog.v(TAG, "VIS " + this + ": interesting="
                 + numInteresting + " visible=" + numVisible);
-        if (nowDrawn != reportedDrawn) {
-            onWindowsDrawn(nowDrawn, SystemClock.elapsedRealtimeNanos());
-            reportedDrawn = nowDrawn;
+        if (nowDrawn != mReportedDrawn) {
+            if (nowDrawn) {
+                onWindowsDrawn(SystemClock.elapsedRealtimeNanos());
+            }
+            mReportedDrawn = nowDrawn;
         }
         if (nowVisible != reportedVisible) {
             if (DEBUG_VISIBILITY) Slog.v(TAG,
@@ -5538,6 +5526,10 @@
         }
     }
 
+    boolean isReportedDrawn() {
+        return mReportedDrawn;
+    }
+
     boolean isClientVisible() {
         return mClientVisible;
     }
@@ -7677,7 +7669,7 @@
         proto.write(VISIBLE_REQUESTED, mVisibleRequested);
         proto.write(CLIENT_VISIBLE, mClientVisible);
         proto.write(DEFER_HIDING_CLIENT, mDeferHidingClient);
-        proto.write(REPORTED_DRAWN, reportedDrawn);
+        proto.write(REPORTED_DRAWN, mReportedDrawn);
         proto.write(REPORTED_VISIBLE, reportedVisible);
         proto.write(NUM_INTERESTING_WINDOWS, mNumInterestingWindows);
         proto.write(NUM_DRAWN_WINDOWS, mNumDrawnWindows);
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 15e88fc..f615838 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1766,8 +1766,8 @@
         } else if (mInTask != null) {
             return mInTask;
         } else {
-            final Task stack = getLaunchStack(mStartActivity, mLaunchFlags,
-                    null /* task */, mOptions);
+            final Task stack = getLaunchStack(mStartActivity, mLaunchFlags, null /* task */,
+                    mOptions);
             final ActivityRecord top = stack.getTopNonFinishingActivity();
             if (top != null) {
                 return top.getTask();
@@ -1870,13 +1870,7 @@
             return START_SUCCESS;
         }
 
-        boolean clearTaskForReuse = false;
         if (reusedTask != null) {
-            if (mStartActivity.getTask() == null) {
-                mStartActivity.setTaskForReuse(reusedTask);
-                clearTaskForReuse = true;
-            }
-
             if (targetTask.intent == null) {
                 // This task was started because of movement of the activity based on
                 // affinity...
@@ -1923,13 +1917,6 @@
         complyActivityFlags(targetTask,
                 reusedTask != null ? reusedTask.getTopNonFinishingActivity() : null, intentGrants);
 
-        if (clearTaskForReuse) {
-            // Clear task for re-use so later code to methods
-            // {@link #setTaskFromReuseOrCreateNewTask}, {@link #setTaskFromSourceRecord}, or
-            // {@link #setTaskFromInTask} can parent it to the task.
-            mStartActivity.setTaskForReuse(null);
-        }
-
         if (mAddingToTask) {
             return START_SUCCESS;
         }
@@ -2515,8 +2502,8 @@
                     intentActivity.setTaskToAffiliateWith(mSourceRecord.getTask());
                 }
 
-                final Task launchStack =
-                        getLaunchStack(mStartActivity, mLaunchFlags, intentTask, mOptions);
+                final Task launchStack = getLaunchStack(mStartActivity, mLaunchFlags, intentTask,
+                        mOptions);
                 if (launchStack == null || launchStack == mTargetStack) {
                     // Do not set mMovedToFront to true below for split-screen-top stack, or
                     // START_TASK_TO_FRONT will be returned and trigger unexpected animations when a
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 31a9c5d..403f225 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -5347,15 +5347,6 @@
         return mAmInternal.isBackgroundActivityStartsEnabled();
     }
 
-    void enableScreenAfterBoot(boolean booted) {
-        writeBootProgressEnableScreen(SystemClock.uptimeMillis());
-        mWindowManager.enableScreenAfterBoot();
-
-        synchronized (mGlobalLock) {
-            updateEventDispatchingLocked(booted);
-        }
-    }
-
     static long getInputDispatchingTimeoutMillisLocked(ActivityRecord r) {
         if (r == null || !r.hasProcess()) {
             return DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
@@ -6449,9 +6440,9 @@
 
         @Override
         public void enableScreenAfterBoot(boolean booted) {
+            writeBootProgressEnableScreen(SystemClock.uptimeMillis());
+            mWindowManager.enableScreenAfterBoot();
             synchronized (mGlobalLock) {
-                writeBootProgressEnableScreen(SystemClock.uptimeMillis());
-                mWindowManager.enableScreenAfterBoot();
                 updateEventDispatchingLocked(booted);
             }
         }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index ba5a382..0215ead 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -117,7 +117,6 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.WindowManagerService.H.REPORT_FOCUS_CHANGE;
 import static com.android.server.wm.WindowManagerService.H.REPORT_HARD_KEYBOARD_STATUS_CHANGE;
-import static com.android.server.wm.WindowManagerService.H.REPORT_LOSING_FOCUS;
 import static com.android.server.wm.WindowManagerService.H.UPDATE_MULTI_WINDOW_STACKS;
 import static com.android.server.wm.WindowManagerService.H.WINDOW_HIDE_TIMEOUT;
 import static com.android.server.wm.WindowManagerService.LAYOUT_REPEAT_THRESHOLD;
@@ -469,12 +468,6 @@
     WindowState mLastFocus = null;
 
     /**
-     * Windows that have lost input focus and are waiting for the new focus window to be displayed
-     * before they are told about this.
-     */
-    ArrayList<WindowState> mLosingFocus = new ArrayList<>();
-
-    /**
      * The foreground app of this display. Windows below this app cannot be the focused window. If
      * the user taps on the area outside of the task of the focused app, we will notify AM about the
      * new task the user wants to interact with.
@@ -899,10 +892,6 @@
             }
         }
 
-        if (!mLosingFocus.isEmpty() && w.isFocused() && w.isDisplayedLw()) {
-            mWmService.mH.obtainMessage(REPORT_LOSING_FOCUS, this).sendToTarget();
-        }
-
         w.updateResizingWindowIfNeeded();
     };
 
@@ -1557,7 +1546,12 @@
             // the heavy operations. This also benefits that the states of multiple activities
             // are handled together.
             r.linkFixedRotationTransform(prevRotatedLaunchingApp);
-            setFixedRotationLaunchingAppUnchecked(r, rotation);
+            if (r != mFixedRotationTransitionListener.mAnimatingRecents) {
+                // Only update the record for normal activity so the display orientation can be
+                // updated when the transition is done if it becomes the top. And the case of
+                // recents can be handled when the recents animation is finished.
+                setFixedRotationLaunchingAppUnchecked(r, rotation);
+            }
             return;
         }
 
@@ -2919,21 +2913,6 @@
         if (mLastFocus != mCurrentFocus) {
             pw.print("  mLastFocus="); pw.println(mLastFocus);
         }
-        if (mLosingFocus.size() > 0) {
-            pw.println();
-            pw.println("  Windows losing focus:");
-            for (int i = mLosingFocus.size() - 1; i >= 0; i--) {
-                final WindowState w = mLosingFocus.get(i);
-                pw.print("  Losing #"); pw.print(i); pw.print(' ');
-                pw.print(w);
-                if (dumpAll) {
-                    pw.println(":");
-                    w.dump(pw, "    ", true);
-                } else {
-                    pw.println();
-                }
-            }
-        }
         pw.print("  mFocusedApp="); pw.println(mFocusedApp);
         if (mLastStatusBarVisibility != 0) {
             pw.print("  mLastStatusBarVisibility=0x");
@@ -3152,7 +3131,6 @@
                 mCurrentFocus, newFocus, getDisplayId(), Debug.getCallers(4));
         final WindowState oldFocus = mCurrentFocus;
         mCurrentFocus = newFocus;
-        mLosingFocus.remove(newFocus);
 
         if (newFocus != null) {
             mWinAddedSinceNullFocus.clear();
@@ -4004,9 +3982,6 @@
 
         mDisplayFrames.onDisplayInfoUpdated(mDisplayInfo,
                 calculateDisplayCutoutForRotation(mDisplayInfo.rotation));
-        // TODO: Not sure if we really need to set the rotation here since we are updating from
-        // the display info above...
-        mDisplayFrames.mRotation = getRotation();
         mDisplayPolicy.beginLayoutLw(mDisplayFrames, getConfiguration().uiMode);
 
         int seq = mLayoutSeq + 1;
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index fe2d08f..aeaffd9 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2816,7 +2816,6 @@
      * @param launchParams The resolved launch params to use.
      * @param realCallingPid The pid from {@link ActivityStarter#setRealCallingPid}
      * @param realCallingUid The uid from {@link ActivityStarter#setRealCallingUid}
-     *
      * @return The stack to use for the launch or INVALID_STACK_ID.
      */
     Task getLaunchStack(@Nullable ActivityRecord r,
@@ -2965,10 +2964,8 @@
         // If {@code r} is already in target display area and its task is the same as the candidate
         // task, the intention should be getting a launch stack for the reusable activity, so we can
         // use the existing stack.
-        if (candidateTask != null && (r.getTask() == null || r.getTask() == candidateTask)) {
-            // TODO(b/153920825): Fix incorrect evaluation of attached state
-            final TaskDisplayArea attachedTaskDisplayArea = r.getTask() != null
-                    ? r.getTask().getDisplayArea() : r.getDisplayArea();
+        if (candidateTask != null) {
+            final TaskDisplayArea attachedTaskDisplayArea = candidateTask.getDisplayArea();
             if (attachedTaskDisplayArea == null || attachedTaskDisplayArea == taskDisplayArea) {
                 return candidateTask.getRootTask();
             }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 7624d4c..cd222a9 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -4740,7 +4740,6 @@
 
     final class H extends android.os.Handler {
         public static final int REPORT_FOCUS_CHANGE = 2;
-        public static final int REPORT_LOSING_FOCUS = 3;
         public static final int WINDOW_FREEZE_TIMEOUT = 11;
 
         public static final int PERSIST_ANIMATION_SCALE = 14;
@@ -4815,11 +4814,6 @@
                         ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "Focus moving from %s"
                                         + " to %s displayId=%d", lastFocus, newFocus,
                                 displayContent.getDisplayId());
-                        if (newFocus != null && lastFocus != null && !newFocus.isDisplayedLw()) {
-                            ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "Delaying loss of focus...");
-                            displayContent.mLosingFocus.add(lastFocus);
-                            lastFocus = null;
-                        }
                     }
 
                     // First notify the accessibility manager for the change so it has
@@ -4842,24 +4836,6 @@
                     break;
                 }
 
-                case REPORT_LOSING_FOCUS: {
-                    final DisplayContent displayContent = (DisplayContent) msg.obj;
-                    ArrayList<WindowState> losers;
-
-                    synchronized (mGlobalLock) {
-                        losers = displayContent.mLosingFocus;
-                        displayContent.mLosingFocus = new ArrayList<>();
-                    }
-
-                    final int N = losers.size();
-                    for (int i = 0; i < N; i++) {
-                        ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "Losing delayed focus: %s",
-                                losers.get(i));
-                        losers.get(i).reportFocusChangedSerialized(false);
-                    }
-                    break;
-                }
-
                 case WINDOW_FREEZE_TIMEOUT: {
                     final DisplayContent displayContent = (DisplayContent) msg.obj;
                     synchronized (mGlobalLock) {
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index ab6e35b..c714eeb 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -196,7 +196,6 @@
     private final Configuration mLastReportedConfiguration = new Configuration();
     // Configuration that is waiting to be dispatched to the process.
     private Configuration mPendingConfiguration;
-    private final Configuration mNewOverrideConfig = new Configuration();
     // Registered display id as a listener to override config change
     private int mDisplayId;
     private ActivityRecord mConfigActivityRecord;
@@ -1292,11 +1291,26 @@
     }
 
     @Override
+    public void onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration) {
+        super.onRequestedOverrideConfigurationChanged(
+                sanitizeProcessConfiguration(overrideConfiguration));
+    }
+
+    @Override
     public void onMergedOverrideConfigurationChanged(Configuration mergedOverrideConfig) {
+        super.onRequestedOverrideConfigurationChanged(
+                sanitizeProcessConfiguration(mergedOverrideConfig));
+    }
+
+    private static Configuration sanitizeProcessConfiguration(Configuration config) {
         // Make sure that we don't accidentally override the activity type.
-        mNewOverrideConfig.setTo(mergedOverrideConfig);
-        mNewOverrideConfig.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
-        super.onRequestedOverrideConfigurationChanged(mNewOverrideConfig);
+        if (config.windowConfiguration.getActivityType() != ACTIVITY_TYPE_UNDEFINED) {
+            final Configuration sanitizedConfig = new Configuration(config);
+            sanitizedConfig.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
+            return sanitizedConfig;
+        }
+
+        return config;
     }
 
     private void updateConfiguration() {
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index f7cd37f..bc4d9a9 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -548,7 +548,7 @@
     void applyFixedRotationTransform(DisplayInfo info, DisplayFrames displayFrames,
             Configuration config) {
         if (mFixedRotationTransformState != null) {
-            return;
+            cleanUpFixedRotationTransformState(true /* replacing */);
         }
         mFixedRotationTransformState = new FixedRotationTransformState(info, displayFrames,
                 new Configuration(config), mDisplayContent.getRotation());
@@ -565,12 +565,12 @@
      * one. This takes the same effect as {@link #applyFixedRotationTransform}.
      */
     void linkFixedRotationTransform(WindowToken other) {
-        if (mFixedRotationTransformState != null) {
+        final FixedRotationTransformState fixedRotationState = other.mFixedRotationTransformState;
+        if (fixedRotationState == null || mFixedRotationTransformState == fixedRotationState) {
             return;
         }
-        final FixedRotationTransformState fixedRotationState = other.mFixedRotationTransformState;
-        if (fixedRotationState == null) {
-            return;
+        if (mFixedRotationTransformState != null) {
+            cleanUpFixedRotationTransformState(true /* replacing */);
         }
         mFixedRotationTransformState = fixedRotationState;
         fixedRotationState.mAssociatedTokens.add(this);
@@ -626,11 +626,17 @@
         // The state is cleared at the end, because it is used to indicate that other windows can
         // use seamless rotation when applying rotation to display.
         for (int i = state.mAssociatedTokens.size() - 1; i >= 0; i--) {
-            state.mAssociatedTokens.get(i).cleanUpFixedRotationTransformState();
+            state.mAssociatedTokens.get(i).cleanUpFixedRotationTransformState(
+                    false /* replacing */);
         }
     }
 
-    private void cleanUpFixedRotationTransformState() {
+    private void cleanUpFixedRotationTransformState(boolean replacing) {
+        if (replacing && mFixedRotationTransformState.mAssociatedTokens.size() > 1) {
+            // The state is not only used by self. Make sure to leave the influence by others.
+            mFixedRotationTransformState.mAssociatedTokens.remove(this);
+            mFixedRotationTransformState.mRotatedContainers.remove(this);
+        }
         mFixedRotationTransformState = null;
         notifyFixedRotationTransform(false /* enabled */);
     }
diff --git a/services/core/jni/com_android_server_VibratorService.cpp b/services/core/jni/com_android_server_VibratorService.cpp
index 529fb88..b3f3a5e 100644
--- a/services/core/jni/com_android_server_VibratorService.cpp
+++ b/services/core/jni/com_android_server_VibratorService.cpp
@@ -181,6 +181,24 @@
     return effects;
 }
 
+static jintArray vibratorGetSupportedPrimitives(JNIEnv* env, jclass /* clazz */,
+                                                jlong controllerPtr) {
+    vibrator::HalController* controller = reinterpret_cast<vibrator::HalController*>(controllerPtr);
+    if (controller == nullptr) {
+        ALOGE("vibratorGetSupportedPrimitives failed because controller was not initialized");
+        return nullptr;
+    }
+    auto result = controller->getSupportedPrimitives();
+    if (!result.isOk()) {
+        return nullptr;
+    }
+    std::vector<aidl::CompositePrimitive> supportedPrimitives = result.value();
+    jintArray primitives = env->NewIntArray(supportedPrimitives.size());
+    env->SetIntArrayRegion(primitives, 0, supportedPrimitives.size(),
+                           reinterpret_cast<jint*>(supportedPrimitives.data()));
+    return primitives;
+}
+
 static jlong vibratorPerformEffect(JNIEnv* env, jclass /* clazz */, jlong controllerPtr,
                                    jlong effect, jlong strength, jobject vibration) {
     vibrator::HalController* controller = reinterpret_cast<vibrator::HalController*>(controllerPtr);
@@ -259,6 +277,7 @@
          "VibratorService$Vibration;)V",
          (void*)vibratorPerformComposedEffect},
         {"vibratorGetSupportedEffects", "(J)[I", (void*)vibratorGetSupportedEffects},
+        {"vibratorGetSupportedPrimitives", "(J)[I", (void*)vibratorGetSupportedPrimitives},
         {"vibratorSetExternalControl", "(JZ)V", (void*)vibratorSetExternalControl},
         {"vibratorGetCapabilities", "(J)J", (void*)vibratorGetCapabilities},
         {"vibratorAlwaysOnEnable", "(JJJJ)V", (void*)vibratorAlwaysOnEnable},
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/CallerIdentity.java b/services/devicepolicy/java/com/android/server/devicepolicy/CallerIdentity.java
new file mode 100644
index 0000000..5193fa8
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/CallerIdentity.java
@@ -0,0 +1,61 @@
+/*
+ * 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.devicepolicy;
+
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.os.UserHandle;
+
+/**
+ * Caller identity containing the caller's UID, package name and component name.
+ * All parameters are verified on object creation unless the component name is null and the
+ * caller is a delegate.
+ */
+class CallerIdentity {
+
+    private final int mUid;
+    @Nullable
+    private final String mPackageName;
+    @Nullable
+    private final ComponentName mComponentName;
+
+    CallerIdentity(int uid, @Nullable String packageName, @Nullable ComponentName componentName) {
+        mUid = uid;
+        mPackageName = packageName;
+        mComponentName = componentName;
+    }
+
+    public int getUid() {
+        return mUid;
+    }
+
+    public int getUserId() {
+        return UserHandle.getUserId(mUid);
+    }
+
+    public UserHandle getUserHandle() {
+        return UserHandle.getUserHandleForUid(mUid);
+    }
+
+    @Nullable  public String getPackageName() {
+        return mPackageName;
+    }
+
+    @Nullable public ComponentName getComponentName() {
+        return mComponentName;
+    }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index cafd56e..6154bef 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -1564,6 +1564,40 @@
     }
 
     /**
+     * Creates a new {@link CallerIdentity} object to represent the caller's identity.
+     */
+    private CallerIdentity getCallerIdentity(String callerPackage) {
+        final int callerUid = mInjector.binderGetCallingUid();
+
+        if (!isCallingFromPackage(callerPackage, callerUid)) {
+            throw new SecurityException(
+                    String.format("Caller with uid %d is not %s", callerUid, callerPackage));
+        }
+
+        return new CallerIdentity(callerUid, callerPackage, null);
+    }
+
+    /**
+     * Creates a new {@link CallerIdentity} object to represent the caller's identity.
+     * The component name should be an active admin for the calling user.
+     */
+    private CallerIdentity getCallerIdentity(@NonNull ComponentName adminComponent) {
+        final int callerUid = mInjector.binderGetCallingUid();
+        final DevicePolicyData policy = getUserData(UserHandle.getUserId(callerUid));
+        ActiveAdmin admin = policy.mAdminMap.get(adminComponent);
+
+        if (admin == null) {
+            throw new SecurityException(String.format("No active admin for %s", adminComponent));
+        }
+        if (admin.getUid() != callerUid) {
+            throw new SecurityException(
+                    String.format("Admin %s is not owned by uid %d", adminComponent, callerUid));
+        }
+
+        return new CallerIdentity(callerUid, adminComponent.getPackageName(), adminComponent);
+    }
+
+    /**
      * Checks if the device is in COMP mode, and if so migrates it to managed profile on a
      * corporate owned device.
      */
@@ -4556,12 +4590,6 @@
         }
     }
 
-    private void enforceDeviceOwner(ComponentName who) {
-        synchronized (getLockObject()) {
-            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
-        }
-    }
-
     private void enforceProfileOrDeviceOwner(ComponentName who) {
         synchronized (getLockObject()) {
             getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
@@ -5161,20 +5189,23 @@
         Objects.requireNonNull(who, "ComponentName is null");
         Preconditions.checkStringNotEmpty(delegatePackage, "Delegate package is null or empty");
         Preconditions.checkCollectionElementsNotNull(scopeList, "Scopes");
+        final CallerIdentity identity = getCallerIdentity(who);
+
         // Remove possible duplicates.
         final ArrayList<String> scopes = new ArrayList(new ArraySet(scopeList));
         // Ensure given scopes are valid.
         if (scopes.retainAll(Arrays.asList(DELEGATIONS))) {
             throw new IllegalArgumentException("Unexpected delegation scopes");
         }
-        final boolean hasDoDelegation = !Collections.disjoint(scopes, DEVICE_OWNER_DELEGATIONS);
         // Retrieve the user ID of the calling process.
-        final int userId = mInjector.userHandleGetCallingUserId();
+        final int userId = identity.getUserId();
+        final boolean hasDoDelegation = !Collections.disjoint(scopes, DEVICE_OWNER_DELEGATIONS);
         synchronized (getLockObject()) {
             // Ensure calling process is device/profile owner.
             if (hasDoDelegation) {
-                getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+                Preconditions.checkCallAuthorization(isDeviceOwner(identity));
             } else {
+                // TODO move whole condition out of synchronized block
                 getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
             }
             // Ensure the delegate is installed (skip this for DELEGATION_CERT_INSTALL in pre-N).
@@ -6166,7 +6197,9 @@
 
     @Override
     public void setRecommendedGlobalProxy(ComponentName who, ProxyInfo proxyInfo) {
-        enforceDeviceOwner(who);
+        Objects.requireNonNull(who, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
         mInjector.binderWithCleanCallingIdentity(
                 () -> mInjector.getConnectivityManager().setGlobalProxy(proxyInfo));
     }
@@ -6587,6 +6620,9 @@
             return;
         }
         Objects.requireNonNull(who, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
+
         // Allow setting this policy to true only if there is a split system user.
         if (forceEphemeralUsers && !mInjector.userManagerIsSplitSystemUser()) {
             throw new UnsupportedOperationException(
@@ -6594,11 +6630,10 @@
         }
         boolean removeAllUsers = false;
         synchronized (getLockObject()) {
-            final ActiveAdmin deviceOwner =
-                    getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+            final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
             if (deviceOwner.forceEphemeralUsers != forceEphemeralUsers) {
                 deviceOwner.forceEphemeralUsers = forceEphemeralUsers;
-                saveSettingsLocked(mInjector.userHandleGetCallingUserId());
+                saveSettingsLocked(identity.getUserId());
                 mUserManagerInternal.setForceEphemeralUsers(forceEphemeralUsers);
                 removeAllUsers = forceEphemeralUsers;
             }
@@ -6614,21 +6649,15 @@
             return false;
         }
         Objects.requireNonNull(who, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
+
         synchronized (getLockObject()) {
-            final ActiveAdmin deviceOwner =
-                    getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+            final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
             return deviceOwner.forceEphemeralUsers;
         }
     }
 
-    private void ensureDeviceOwnerAndAllUsersAffiliated(ComponentName who)
-            throws SecurityException {
-        synchronized (getLockObject()) {
-            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
-        }
-        ensureAllUsersAffiliated();
-    }
-
     private void ensureAllUsersAffiliated() throws SecurityException {
         synchronized (getLockObject()) {
             if (!areAllUsersAffiliatedWithDeviceLocked()) {
@@ -6643,11 +6672,12 @@
             return false;
         }
         Objects.requireNonNull(who, "ComponentName is null");
-
         // TODO: If an unaffiliated user is removed, the admin will be able to request a bugreport
         // which could still contain data related to that user. Should we disallow that, e.g. until
         // next boot? Might not be needed given that this still requires user consent.
-        ensureDeviceOwnerAndAllUsersAffiliated(who);
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
+        ensureAllUsersAffiliated();
 
         if (mRemoteBugreportServiceIsActive.get()
                 || (getDeviceOwnerRemoteBugreportUri() != null)) {
@@ -7171,6 +7201,14 @@
         }
     }
 
+    private boolean isDeviceOwner(CallerIdentity identity) {
+        synchronized (getLockObject()) {
+            return mOwners.hasDeviceOwner()
+                    && mOwners.getDeviceOwnerUserId() == identity.getUserId()
+                    && mOwners.getDeviceOwnerComponent().equals(identity.getComponentName());
+        }
+    }
+
     private boolean isDeviceOwnerPackage(String packageName, int userId) {
         synchronized (getLockObject()) {
             return mOwners.hasDeviceOwner()
@@ -8448,6 +8486,7 @@
     @Override
     public void setDefaultSmsApplication(ComponentName admin, String packageName, boolean parent) {
         Objects.requireNonNull(admin, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(admin);
 
         if (parent) {
             ActiveAdmin ap = getActiveAdminForCallerLocked(admin,
@@ -8456,7 +8495,7 @@
             mInjector.binderWithCleanCallingIdentity(() -> enforcePackageIsSystemPackage(
                     packageName, getProfileParentId(mInjector.userHandleGetCallingUserId())));
         } else {
-            enforceDeviceOwner(admin);
+            Preconditions.checkCallAuthorization(isDeviceOwner(identity));
         }
 
         mInjector.binderWithCleanCallingIdentity(() ->
@@ -9218,14 +9257,14 @@
     public boolean removeUser(ComponentName who, UserHandle userHandle) {
         Objects.requireNonNull(who, "ComponentName is null");
         Objects.requireNonNull(userHandle, "UserHandle is null");
-        enforceDeviceOwner(who);
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
 
-        final int callingUserId = mInjector.userHandleGetCallingUserId();
         return mInjector.binderWithCleanCallingIdentity(() -> {
             String restriction = isManagedProfile(userHandle.getIdentifier())
                     ? UserManager.DISALLOW_REMOVE_MANAGED_PROFILE
                     : UserManager.DISALLOW_REMOVE_USER;
-            if (isAdminAffectedByRestriction(who, restriction, callingUserId)) {
+            if (isAdminAffectedByRestriction(who, restriction, identity.getUserId())) {
                 Log.w(LOG_TAG, "The device owner cannot remove a user because "
                         + restriction + " is enabled, and was not set by the device owner");
                 return false;
@@ -9251,10 +9290,10 @@
     @Override
     public boolean switchUser(ComponentName who, UserHandle userHandle) {
         Objects.requireNonNull(who, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
 
         synchronized (getLockObject()) {
-            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
-
             long id = mInjector.binderClearCallingIdentity();
             try {
                 int userId = UserHandle.USER_SYSTEM;
@@ -9275,7 +9314,8 @@
     public int startUserInBackground(ComponentName who, UserHandle userHandle) {
         Objects.requireNonNull(who, "ComponentName is null");
         Objects.requireNonNull(userHandle, "UserHandle is null");
-        enforceDeviceOwner(who);
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
 
         final int userId = userHandle.getIdentifier();
         if (isManagedProfile(userId)) {
@@ -9307,7 +9347,8 @@
     public int stopUser(ComponentName who, UserHandle userHandle) {
         Objects.requireNonNull(who, "ComponentName is null");
         Objects.requireNonNull(userHandle, "UserHandle is null");
-        enforceDeviceOwner(who);
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
 
         final int userId = userHandle.getIdentifier();
         if (isManagedProfile(userId)) {
@@ -9375,7 +9416,8 @@
     @Override
     public List<UserHandle> getSecondaryUsers(ComponentName who) {
         Objects.requireNonNull(who, "ComponentName is null");
-        enforceDeviceOwner(who);
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
 
         return mInjector.binderWithCleanCallingIdentity(() -> {
             final List<UserInfo> userInfos = mInjector.getUserManager().getUsers(true
@@ -10337,6 +10379,8 @@
     @Override
     public void setGlobalSetting(ComponentName who, String setting, String value) {
         Objects.requireNonNull(who, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
 
         DevicePolicyEventLogger
                 .createEvent(DevicePolicyEnums.SET_GLOBAL_SETTING)
@@ -10345,8 +10389,6 @@
                 .write();
 
         synchronized (getLockObject()) {
-            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
-
             // Some settings are no supported any more. However we do not want to throw a
             // SecurityException to avoid breaking apps.
             if (GLOBAL_SETTINGS_DEPRECATED.contains(setting)) {
@@ -10427,20 +10469,20 @@
 
     @Override
     public void setLocationEnabled(ComponentName who, boolean locationEnabled) {
-        enforceDeviceOwner(Objects.requireNonNull(who));
-
-        UserHandle user = mInjector.binderGetCallingUserHandle();
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
 
         mInjector.binderWithCleanCallingIdentity(() -> {
             boolean wasLocationEnabled = mInjector.getLocationManager().isLocationEnabledForUser(
-                    user);
-            mInjector.getLocationManager().setLocationEnabledForUser(locationEnabled, user);
+                    identity.getUserHandle());
+            mInjector.getLocationManager().setLocationEnabledForUser(locationEnabled,
+                    identity.getUserHandle());
 
             // make a best effort to only show the notification if the admin is actually enabling
             // location. this is subject to race conditions with settings changes, but those are
             // unlikely to realistically interfere
-            if (locationEnabled && (wasLocationEnabled != locationEnabled)) {
-                showLocationSettingsEnabledNotification(user);
+            if (locationEnabled && !wasLocationEnabled) {
+                showLocationSettingsEnabledNotification(identity.getUserHandle());
             }
         });
 
@@ -11967,16 +12009,18 @@
 
     @Override
     public boolean isSystemOnlyUser(ComponentName admin) {
-        enforceDeviceOwner(admin);
-        final int callingUserId = mInjector.userHandleGetCallingUserId();
-        return UserManager.isSplitSystemUser() && callingUserId == UserHandle.USER_SYSTEM;
+        Objects.requireNonNull(admin, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(admin);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
+        return UserManager.isSplitSystemUser() && identity.getUserId() == UserHandle.USER_SYSTEM;
     }
 
     @Override
     public void reboot(ComponentName admin) {
-        Objects.requireNonNull(admin);
-        // Make sure caller has DO.
-        enforceDeviceOwner(admin);
+        Objects.requireNonNull(admin, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(admin);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
+
         mInjector.binderWithCleanCallingIdentity(() -> {
             // Make sure there are no ongoing calls on the device.
             if (mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE) {
@@ -13482,18 +13526,18 @@
         if (!mHasFeature) {
             return;
         }
-        Objects.requireNonNull(admin);
+        Objects.requireNonNull(admin, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(admin);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
 
         synchronized (getLockObject()) {
-            ActiveAdmin deviceOwner =
-                    getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
-
+            ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
             if (deviceOwner.isLogoutEnabled == enabled) {
                 // already in the requested state
                 return;
             }
             deviceOwner.isLogoutEnabled = enabled;
-            saveSettingsLocked(mInjector.userHandleGetCallingUserId());
+            saveSettingsLocked(identity.getUserId());
         }
     }
 
@@ -13659,20 +13703,20 @@
         if (!mHasFeature) {
             return;
         }
-        Objects.requireNonNull(admin);
+        Objects.requireNonNull(admin, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(admin);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
 
         final String startUserSessionMessageString =
                 startUserSessionMessage != null ? startUserSessionMessage.toString() : null;
 
         synchronized (getLockObject()) {
-            final ActiveAdmin deviceOwner =
-                    getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
-
+            final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
             if (TextUtils.equals(deviceOwner.startUserSessionMessage, startUserSessionMessage)) {
                 return;
             }
             deviceOwner.startUserSessionMessage = startUserSessionMessageString;
-            saveSettingsLocked(mInjector.userHandleGetCallingUserId());
+            saveSettingsLocked(identity.getUserId());
         }
 
         mInjector.getActivityManagerInternal()
@@ -13684,20 +13728,20 @@
         if (!mHasFeature) {
             return;
         }
-        Objects.requireNonNull(admin);
+        Objects.requireNonNull(admin, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(admin);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
 
         final String endUserSessionMessageString =
                 endUserSessionMessage != null ? endUserSessionMessage.toString() : null;
 
         synchronized (getLockObject()) {
-            final ActiveAdmin deviceOwner =
-                    getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
-
+            final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
             if (TextUtils.equals(deviceOwner.endUserSessionMessage, endUserSessionMessage)) {
                 return;
             }
             deviceOwner.endUserSessionMessage = endUserSessionMessageString;
-            saveSettingsLocked(mInjector.userHandleGetCallingUserId());
+            saveSettingsLocked(identity.getUserId());
         }
 
         mInjector.getActivityManagerInternal()
@@ -13709,11 +13753,12 @@
         if (!mHasFeature) {
             return null;
         }
-        Objects.requireNonNull(admin);
+        Objects.requireNonNull(admin, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(admin);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
 
         synchronized (getLockObject()) {
-            final ActiveAdmin deviceOwner =
-                    getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+            final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
             return deviceOwner.startUserSessionMessage;
         }
     }
@@ -13723,11 +13768,12 @@
         if (!mHasFeature) {
             return null;
         }
-        Objects.requireNonNull(admin);
+        Objects.requireNonNull(admin, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(admin);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
 
         synchronized (getLockObject()) {
-            final ActiveAdmin deviceOwner =
-                    getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+            final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
             return deviceOwner.endUserSessionMessage;
         }
     }
@@ -13766,9 +13812,10 @@
         if (!mHasFeature || !mHasTelephonyFeature) {
             return -1;
         }
-        Objects.requireNonNull(who, "ComponentName is null in addOverrideApn");
+        Objects.requireNonNull(who, "ComponentName is null");
         Objects.requireNonNull(apnSetting, "ApnSetting is null in addOverrideApn");
-        enforceDeviceOwner(who);
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
 
         TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
         if (tm != null) {
@@ -13786,9 +13833,10 @@
         if (!mHasFeature || !mHasTelephonyFeature) {
             return false;
         }
-        Objects.requireNonNull(who, "ComponentName is null in updateOverrideApn");
+        Objects.requireNonNull(who, "ComponentName is null");
         Objects.requireNonNull(apnSetting, "ApnSetting is null in updateOverrideApn");
-        enforceDeviceOwner(who);
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
 
         if (apnId < 0) {
             return false;
@@ -13808,9 +13856,9 @@
         if (!mHasFeature || !mHasTelephonyFeature) {
             return false;
         }
-        Objects.requireNonNull(who, "ComponentName is null in removeOverrideApn");
-        enforceDeviceOwner(who);
-
+        Objects.requireNonNull(who, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
         return removeOverrideApnUnchecked(apnId);
     }
 
@@ -13829,9 +13877,9 @@
         if (!mHasFeature || !mHasTelephonyFeature) {
             return Collections.emptyList();
         }
-        Objects.requireNonNull(who, "ComponentName is null in getOverrideApns");
-        enforceDeviceOwner(who);
-
+        Objects.requireNonNull(who, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
         return getOverrideApnsUnchecked();
     }
 
@@ -13850,9 +13898,9 @@
         if (!mHasFeature || !mHasTelephonyFeature) {
             return;
         }
-        Objects.requireNonNull(who, "ComponentName is null in setOverrideApnEnabled");
-        enforceDeviceOwner(who);
-
+        Objects.requireNonNull(who, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
         setOverrideApnsEnabledUnchecked(enabled);
     }
 
@@ -13868,8 +13916,9 @@
         if (!mHasFeature || !mHasTelephonyFeature) {
             return false;
         }
-        Objects.requireNonNull(who, "ComponentName is null in isOverrideApnEnabled");
-        enforceDeviceOwner(who);
+        Objects.requireNonNull(who, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
 
         Cursor enforceCursor = mInjector.binderWithCleanCallingIdentity(
                 () -> mContext.getContentResolver().query(
@@ -13951,11 +14000,9 @@
         if (!mHasFeature) {
             return PRIVATE_DNS_SET_ERROR_FAILURE_SETTING;
         }
-
         Objects.requireNonNull(who, "ComponentName is null");
-        enforceDeviceOwner(who);
-
-        final int returnCode;
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
 
         switch (mode) {
             case PRIVATE_DNS_MODE_OPPORTUNISTIC:
@@ -13989,9 +14036,10 @@
         if (!mHasFeature) {
             return PRIVATE_DNS_MODE_UNKNOWN;
         }
-
         Objects.requireNonNull(who, "ComponentName is null");
-        enforceDeviceOwner(who);
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
+
         String currentMode = mInjector.settingsGlobalGetString(PRIVATE_DNS_MODE);
         if (currentMode == null) {
             currentMode = ConnectivityManager.PRIVATE_DNS_DEFAULT_MODE_FALLBACK;
@@ -14013,10 +14061,9 @@
         if (!mHasFeature) {
             return null;
         }
-
         Objects.requireNonNull(who, "ComponentName is null");
-        enforceDeviceOwner(who);
-
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
         return mInjector.settingsGlobalGetString(PRIVATE_DNS_SPECIFIER);
     }
 
@@ -14361,13 +14408,13 @@
 
     @Override
     public void setUserControlDisabledPackages(ComponentName who, List<String> packages) {
-        Preconditions.checkNotNull(who, "ComponentName is null");
+        Objects.requireNonNull(who, "ComponentName is null");
         Preconditions.checkNotNull(packages, "packages is null");
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
 
-        enforceDeviceOwner(who);
         synchronized (getLockObject()) {
-            final int userHandle = mInjector.userHandleGetCallingUserId();
-            setUserControlDisabledPackagesLocked(userHandle, packages);
+            setUserControlDisabledPackagesLocked(identity.getUserId(), packages);
             DevicePolicyEventLogger
                     .createEvent(DevicePolicyEnums.SET_USER_CONTROL_DISABLED_PACKAGES)
                     .setAdmin(who)
@@ -14387,12 +14434,12 @@
 
     @Override
     public List<String> getUserControlDisabledPackages(ComponentName who) {
-        Preconditions.checkNotNull(who, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
 
-        enforceDeviceOwner(who);
-        final int userHandle = mInjector.binderGetCallingUserHandle().getIdentifier();
         synchronized (getLockObject()) {
-            final List<String> packages = getUserData(userHandle).mUserControlDisabledPackages;
+            final List<String> packages =
+                    getUserData(identity.getUserId()).mUserControlDisabledPackages;
             return packages == null ? Collections.EMPTY_LIST : packages;
         }
     }
diff --git a/services/print/java/com/android/server/print/UserState.java b/services/print/java/com/android/server/print/UserState.java
index e8266a5..b93c519 100644
--- a/services/print/java/com/android/server/print/UserState.java
+++ b/services/print/java/com/android/server/print/UserState.java
@@ -30,6 +30,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Context;
@@ -132,7 +133,7 @@
 
     private final Context mContext;
 
-    private final int mUserId;
+    private final @UserIdInt int mUserId;
 
     private final RemotePrintSpooler mSpooler;
 
@@ -650,7 +651,7 @@
 
                 mPrintServiceRecommendationsService =
                         new RemotePrintServiceRecommendationService(mContext,
-                                UserHandle.getUserHandleForUid(mUserId), this);
+                                UserHandle.of(mUserId), this);
             }
             mPrintServiceRecommendationsChangeListenerRecords.add(
                     new ListenerRecord<IRecommendationsChangeListener>(listener) {
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index bc75dcd..12c69ea 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -150,7 +150,8 @@
         }
 
         // Sample for a fraction of app launches.
-        int traceFrequency = SystemProperties.getInt("profcollectd.applaunch_trace_freq", 2);
+        int traceFrequency =
+                SystemProperties.getInt("persist.profcollectd.applaunch_trace_freq", 2);
         int randomNum = ThreadLocalRandom.current().nextInt(100);
         if (randomNum < traceFrequency) {
             try {
diff --git a/services/tests/PackageManagerServiceTests/host/Android.bp b/services/tests/PackageManagerServiceTests/host/Android.bp
index e4e7e22..4f636ef 100644
--- a/services/tests/PackageManagerServiceTests/host/Android.bp
+++ b/services/tests/PackageManagerServiceTests/host/Android.bp
@@ -22,6 +22,7 @@
     ],
     static_libs: [
         "frameworks-base-hostutils",
+        "PackageManagerServiceHostTestsIntentVerifyUtils",
     ],
     test_suites: ["general-tests"],
     java_resources: [
@@ -33,7 +34,15 @@
         ":PackageManagerTestAppVersion4",
         ":PackageManagerTestAppOriginalOverride",
         ":PackageManagerServiceDeviceSideTests",
-    ],
+        ":PackageManagerTestIntentVerifier",
+        ":PackageManagerTestIntentVerifierTarget1",
+        ":PackageManagerTestIntentVerifierTarget2",
+        ":PackageManagerTestIntentVerifierTarget3",
+        ":PackageManagerTestIntentVerifierTarget4Base",
+        ":PackageManagerTestIntentVerifierTarget4NoAutoVerify",
+        ":PackageManagerTestIntentVerifierTarget4Wildcard",
+        ":PackageManagerTestIntentVerifierTarget4WildcardNoAutoVerify",
+    ]
 }
 
 genrule {
diff --git a/services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/Android.bp b/services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/Android.bp
new file mode 100644
index 0000000..b7a0624
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/Android.bp
@@ -0,0 +1,19 @@
+// 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.
+
+java_library {
+    name: "PackageManagerServiceHostTestsIntentVerifyUtils",
+    srcs: ["src/**/*.kt"],
+    host_supported: true,
+}
diff --git a/tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java b/services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/src/com/android/server/pm/test/intent/verify/IntentVerifyTestParams.kt
similarity index 78%
rename from tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java
rename to services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/src/com/android/server/pm/test/intent/verify/IntentVerifyTestParams.kt
index 09ef472..48119e0 100644
--- a/tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java
+++ b/services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/src/com/android/server/pm/test/intent/verify/IntentVerifyTestParams.kt
@@ -13,3 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
+package com.android.server.pm.test.intent.verify
+
+interface IntentVerifyTestParams {
+
+    val methodName: String
+
+    fun toArgsMap(): Map<String, String>
+}
diff --git a/services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/src/com/android/server/pm/test/intent/verify/SetActivityAsAlwaysParams.kt b/services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/src/com/android/server/pm/test/intent/verify/SetActivityAsAlwaysParams.kt
new file mode 100644
index 0000000..26c3903
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/src/com/android/server/pm/test/intent/verify/SetActivityAsAlwaysParams.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.pm.test.intent.verify
+
+data class SetActivityAsAlwaysParams(
+    val uri: String,
+    val packageName: String,
+    val activityName: String,
+    override val methodName: String = "setActivityAsAlways"
+) : IntentVerifyTestParams {
+
+    companion object {
+        private const val KEY_URI = "uri"
+        private const val KEY_PACKAGE_NAME = "packageName"
+        private const val KEY_ACTIVITY_NAME = "activityName"
+
+        fun fromArgs(args: Map<String, String>) = SetActivityAsAlwaysParams(
+                args.getValue(KEY_URI),
+                args.getValue(KEY_PACKAGE_NAME),
+                args.getValue(KEY_ACTIVITY_NAME)
+        )
+    }
+
+    override fun toArgsMap() = mapOf(
+            KEY_URI to uri,
+            KEY_PACKAGE_NAME to packageName,
+            KEY_ACTIVITY_NAME to activityName
+    )
+}
diff --git a/services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/src/com/android/server/pm/test/intent/verify/StartActivityParams.kt b/services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/src/com/android/server/pm/test/intent/verify/StartActivityParams.kt
new file mode 100644
index 0000000..7eddcfb
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/src/com/android/server/pm/test/intent/verify/StartActivityParams.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.pm.test.intent.verify
+
+data class StartActivityParams(
+    val uri: String,
+    val expected: List<String>,
+    val withBrowsers: Boolean = false,
+    override val methodName: String = "verifyActivityStart"
+) : IntentVerifyTestParams {
+    companion object {
+        private const val KEY_URI = "uri"
+        private const val KEY_EXPECTED = "expected"
+        private const val KEY_BROWSER = "browser"
+
+        fun fromArgs(args: Map<String, String>) = StartActivityParams(
+                args.getValue(KEY_URI),
+                args.getValue(KEY_EXPECTED).split(","),
+                args.getValue(KEY_BROWSER).toBoolean()
+        )
+    }
+
+    constructor(
+        uri: String,
+        expected: String,
+        withBrowsers: Boolean = false
+    ) : this(uri, listOf(expected), withBrowsers)
+
+    override fun toArgsMap() = mapOf(
+            KEY_URI to uri,
+            KEY_EXPECTED to expected.joinToString(separator = ","),
+            KEY_BROWSER to withBrowsers.toString()
+    )
+}
diff --git a/services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/src/com/android/server/pm/test/intent/verify/VerifyRequest.kt b/services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/src/com/android/server/pm/test/intent/verify/VerifyRequest.kt
new file mode 100644
index 0000000..f93b1e0
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/src/com/android/server/pm/test/intent/verify/VerifyRequest.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.pm.test.intent.verify
+
+data class VerifyRequest(
+    val id: Int = -1,
+    val scheme: String,
+    val hosts: List<String>,
+    val packageName: String
+) {
+
+    companion object {
+        fun deserialize(value: String?): VerifyRequest {
+            val lines = value?.trim()?.lines()
+                    ?: return VerifyRequest(scheme = "", hosts = emptyList(), packageName = "")
+            return VerifyRequest(
+                    lines[0].removePrefix("id=").toInt(),
+                    lines[1].removePrefix("scheme="),
+                    lines[2].removePrefix("hosts=").split(","),
+                    lines[3].removePrefix("packageName=")
+            )
+        }
+    }
+
+    constructor(id: Int = -1, scheme: String, host: String, packageName: String) :
+            this(id, scheme, listOf(host), packageName)
+
+    fun serializeToString() = """
+        id=$id
+        scheme=$scheme
+        hosts=${hosts.joinToString(separator = ",")}
+        packageName=$packageName
+    """.trimIndent() + "\n"
+}
diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/FactoryPackageTest.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/FactoryPackageTest.kt
index 3847658..e17358d 100644
--- a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/FactoryPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/FactoryPackageTest.kt
@@ -31,7 +31,8 @@
     private val preparer: SystemPreparer = SystemPreparer(tempFolder,
             SystemPreparer.RebootStrategy.FULL, deviceRebootRule) { this.device }
 
-    @get:Rule
+    @Rule
+    @JvmField
     val rules = RuleChain.outerRule(tempFolder).around(preparer)!!
     private val filePath =
             HostUtils.makePathForApk("PackageManagerTestApp.apk", Partition.SYSTEM)
diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/HostUtils.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/HostUtils.kt
index 8dfefaf..24c714c 100644
--- a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/HostUtils.kt
+++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/HostUtils.kt
@@ -18,6 +18,7 @@
 
 import com.android.internal.util.test.SystemPreparer
 import com.android.tradefed.device.ITestDevice
+import org.junit.rules.TemporaryFolder
 import java.io.File
 import java.io.FileOutputStream
 
@@ -34,6 +35,19 @@
     }
 }
 
+internal fun ITestDevice.installJavaResourceApk(
+    tempFolder: TemporaryFolder,
+    javaResource: String,
+    reinstall: Boolean = true,
+    extraArgs: Array<String> = emptyArray()
+): String? {
+    val file = HostUtils.copyResourceToHostFile(javaResource, tempFolder.newFile())
+    return installPackage(file, reinstall, *extraArgs)
+}
+
+internal fun ITestDevice.uninstallPackages(vararg pkgNames: String) =
+        pkgNames.forEach { uninstallPackage(it) }
+
 internal object HostUtils {
 
     fun getDataDir(device: ITestDevice, pkgName: String) =
diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/InvalidNewSystemAppTest.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/InvalidNewSystemAppTest.kt
index b7d1359..37c999c 100644
--- a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/InvalidNewSystemAppTest.kt
+++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/InvalidNewSystemAppTest.kt
@@ -47,7 +47,8 @@
     private val preparer: SystemPreparer = SystemPreparer(tempFolder,
             SystemPreparer.RebootStrategy.FULL, deviceRebootRule) { this.device }
 
-    @get:Rule
+    @Rule
+    @JvmField
     val rules = RuleChain.outerRule(tempFolder).around(preparer)!!
     private val filePath = HostUtils.makePathForApk("PackageManagerTestApp.apk", Partition.PRODUCT)
 
diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/OriginalPackageMigrationTest.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/OriginalPackageMigrationTest.kt
index 4ae3ca5..4becae6 100644
--- a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/OriginalPackageMigrationTest.kt
+++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/OriginalPackageMigrationTest.kt
@@ -47,7 +47,8 @@
     private val preparer: SystemPreparer = SystemPreparer(tempFolder,
             SystemPreparer.RebootStrategy.FULL, deviceRebootRule) { this.device }
 
-    @get:Rule
+    @Rule
+    @JvmField
     val rules = RuleChain.outerRule(tempFolder).around(preparer)!!
 
     @Before
diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/Partition.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/Partition.kt
index 654c11c..6479f58 100644
--- a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/Partition.kt
+++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/Partition.kt
@@ -22,6 +22,7 @@
 // Unfortunately no easy way to access PMS SystemPartitions, so mock them here
 internal enum class Partition(val baseAppFolder: Path) {
     SYSTEM("/system/app"),
+    SYSTEM_PRIVILEGED("/system/priv-app"),
     VENDOR("/vendor/app"),
     PRODUCT("/product/app"),
     SYSTEM_EXT("/system_ext/app")
diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SystemStubMultiUserDisableUninstallTest.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SystemStubMultiUserDisableUninstallTest.kt
index 207f10a..46120af 100644
--- a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SystemStubMultiUserDisableUninstallTest.kt
+++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SystemStubMultiUserDisableUninstallTest.kt
@@ -110,7 +110,8 @@
     private val preparer: SystemPreparer = SystemPreparer(tempFolder,
             SystemPreparer.RebootStrategy.FULL, deviceRebootRule) { this.device }
 
-    @get:Rule
+    @Rule
+    @JvmField
     val rules = RuleChain.outerRule(tempFolder).let {
         if (DEBUG_NO_REBOOT) {
             it!!
diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/intent/verify/IntentFilterVerificationTest.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/intent/verify/IntentFilterVerificationTest.kt
new file mode 100644
index 0000000..fffda8e
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/intent/verify/IntentFilterVerificationTest.kt
@@ -0,0 +1,413 @@
+/*
+ * 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.pm.test.intent.verify
+
+import com.android.internal.util.test.SystemPreparer
+import com.android.server.pm.test.Partition
+import com.android.server.pm.test.deleteApkFolders
+import com.android.server.pm.test.installJavaResourceApk
+import com.android.server.pm.test.pushApk
+import com.android.server.pm.test.uninstallPackages
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.ClassRule
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.RuleChain
+import org.junit.rules.TemporaryFolder
+import org.junit.runner.RunWith
+import java.io.File
+import java.util.concurrent.TimeUnit
+
+@RunWith(DeviceJUnit4ClassRunner::class)
+class IntentFilterVerificationTest : BaseHostJUnit4Test() {
+
+    companion object {
+        private const val VERIFIER = "PackageManagerTestIntentVerifier.apk"
+        private const val VERIFIER_PKG_NAME = "com.android.server.pm.test.intent.verifier"
+        private const val TARGET_PKG_PREFIX = "$VERIFIER_PKG_NAME.target"
+        private const val TARGET_APK_PREFIX = "PackageManagerTestIntentVerifierTarget"
+        private const val TARGET_ONE = "${TARGET_APK_PREFIX}1.apk"
+        private const val TARGET_ONE_PKG_NAME = "$TARGET_PKG_PREFIX.one"
+        private const val TARGET_TWO = "${TARGET_APK_PREFIX}2.apk"
+        private const val TARGET_TWO_PKG_NAME = "$TARGET_PKG_PREFIX.two"
+        private const val TARGET_THREE = "${TARGET_APK_PREFIX}3.apk"
+        private const val TARGET_THREE_PKG_NAME = "$TARGET_PKG_PREFIX.three"
+        private const val TARGET_FOUR_BASE = "${TARGET_APK_PREFIX}4Base.apk"
+        private const val TARGET_FOUR_PKG_NAME = "$TARGET_PKG_PREFIX.four"
+        private const val TARGET_FOUR_NO_AUTO_VERIFY = "${TARGET_APK_PREFIX}4NoAutoVerify.apk"
+        private const val TARGET_FOUR_WILDCARD = "${TARGET_APK_PREFIX}4Wildcard.apk"
+        private const val TARGET_FOUR_WILDCARD_NO_AUTO_VERIFY =
+                "${TARGET_APK_PREFIX}4WildcardNoAutoVerify.apk"
+
+        @get:ClassRule
+        val deviceRebootRule = SystemPreparer.TestRuleDelegate(true)
+    }
+
+    private val tempFolder = TemporaryFolder()
+    private val preparer: SystemPreparer = SystemPreparer(tempFolder,
+            SystemPreparer.RebootStrategy.FULL, deviceRebootRule) { this.device }
+
+    @Rule
+    @JvmField
+    val rules = RuleChain.outerRule(tempFolder).around(preparer)!!
+
+    private val permissionsFile = File("/system/etc/permissions" +
+            "/privapp-PackageManagerIntentFilterVerificationTest-permissions.xml")
+
+    @Before
+    fun cleanupAndPushPermissionsFile() {
+        // In order for the test app to be the verification agent, it needs a permission file
+        // which can be pushed onto the system and removed afterwards.
+        val file = tempFolder.newFile().apply {
+            """
+                <permissions>
+                    <privapp-permissions package="$VERIFIER_PKG_NAME">
+                        <permission name="android.permission.INTENT_FILTER_VERIFICATION_AGENT"/>
+                    </privapp-permissions>
+                </permissions>
+            """
+                    .trimIndent()
+                    .let { writeText(it) }
+        }
+        device.uninstallPackages(TARGET_ONE_PKG_NAME, TARGET_TWO_PKG_NAME, TARGET_THREE_PKG_NAME,
+                TARGET_FOUR_PKG_NAME)
+        preparer.pushApk(VERIFIER, Partition.SYSTEM_PRIVILEGED)
+                .pushFile(file, permissionsFile.toString())
+                .reboot()
+        runTest("clearResponse")
+    }
+
+    @After
+    fun cleanupAndDeletePermissionsFile() {
+        device.uninstallPackages(TARGET_ONE_PKG_NAME, TARGET_TWO_PKG_NAME, TARGET_THREE_PKG_NAME,
+                TARGET_FOUR_PKG_NAME)
+        preparer.deleteApkFolders(Partition.SYSTEM_PRIVILEGED, VERIFIER)
+                .deleteFile(permissionsFile.toString())
+        device.reboot()
+    }
+
+    @Test
+    fun verifyOne() {
+        installPackage(TARGET_ONE)
+
+        assertReceivedRequests(true, VerifyRequest(
+                scheme = "https",
+                hosts = listOf(
+                        "https_only.pm.server.android.com",
+                        "other_activity.pm.server.android.com",
+                        "http_only.pm.server.android.com",
+                        "verify.pm.server.android.com",
+                        "https_plus_non_web_scheme.pm.server.android.com",
+                        "multiple.pm.server.android.com",
+                        // TODO(b/159952358): the following domain should not be
+                        //  verified, this is because the verifier tries to verify all web domains,
+                        //  even in intent filters not marked for auto verify
+                        "no_verify.pm.server.android.com"
+                ),
+                packageName = TARGET_ONE_PKG_NAME
+        ))
+
+        runTest(StartActivityParams(
+                uri = "https://https_only.pm.server.android.com",
+                expected = "$TARGET_ONE_PKG_NAME.TargetActivity"
+        ))
+    }
+
+    @Test
+    fun nonWebScheme() {
+        installPackage(TARGET_TWO)
+        assertReceivedRequests(null)
+    }
+
+    @Test
+    fun verifyHttpNonSecureOnly() {
+        installPackage(TARGET_THREE)
+        assertReceivedRequests(true, VerifyRequest(
+                scheme = "https",
+                hosts = listOf(
+                        "multiple.pm.server.android.com"
+                ),
+                packageName = TARGET_THREE_PKG_NAME
+        ))
+
+        runTest(StartActivityParams(
+                uri = "http://multiple.pm.server.android.com",
+                expected = "$TARGET_THREE_PKG_NAME.TargetActivity"
+        ))
+    }
+
+    @Test
+    fun multipleResults() {
+        installPackage(TARGET_ONE)
+        installPackage(TARGET_THREE)
+        assertReceivedRequests(true, VerifyRequest(
+                scheme = "https",
+                hosts = listOf(
+                        "https_only.pm.server.android.com",
+                        "other_activity.pm.server.android.com",
+                        "http_only.pm.server.android.com",
+                        "verify.pm.server.android.com",
+                        "https_plus_non_web_scheme.pm.server.android.com",
+                        "multiple.pm.server.android.com",
+                        // TODO(b/159952358): the following domain should not be
+                        //  verified, this is because the verifier tries to verify all web domains,
+                        //  even in intent filters not marked for auto verify
+                        "no_verify.pm.server.android.com"
+                ),
+                packageName = TARGET_ONE_PKG_NAME
+        ), VerifyRequest(
+                scheme = "https",
+                hosts = listOf(
+                        "multiple.pm.server.android.com"
+                ),
+                packageName = TARGET_THREE_PKG_NAME
+        ))
+
+        // Target3 declares http non-s, so it should be included in the set here
+        runTest(StartActivityParams(
+                uri = "http://multiple.pm.server.android.com",
+                expected = listOf(
+                        "$TARGET_ONE_PKG_NAME.TargetActivity2",
+                        "$TARGET_THREE_PKG_NAME.TargetActivity"
+                )
+        ))
+
+        // But it excludes https, so it shouldn't resolve here
+        runTest(StartActivityParams(
+                uri = "https://multiple.pm.server.android.com",
+                expected = "$TARGET_ONE_PKG_NAME.TargetActivity2"
+        ))
+
+        // Remove Target3 and return to single verified Target1 app for http non-s
+        device.uninstallPackage(TARGET_THREE_PKG_NAME)
+        runTest(StartActivityParams(
+                uri = "http://multiple.pm.server.android.com",
+                expected = "$TARGET_ONE_PKG_NAME.TargetActivity2"
+        ))
+    }
+
+    @Test
+    fun demoteAlways() {
+        installPackage(TARGET_FOUR_BASE)
+        assertReceivedRequests(false, VerifyRequest(
+                scheme = "https",
+                host = "failing.pm.server.android.com",
+                packageName = TARGET_FOUR_PKG_NAME
+        ))
+
+        runTest(StartActivityParams(
+                uri = "https://failing.pm.server.android.com",
+                expected = "$TARGET_FOUR_PKG_NAME.TargetActivity",
+                withBrowsers = true
+        ))
+        runTest(SetActivityAsAlwaysParams(
+                uri = "https://failing.pm.server.android.com",
+                packageName = TARGET_FOUR_PKG_NAME,
+                activityName = "$TARGET_FOUR_PKG_NAME.TargetActivity"
+        ))
+        runTest(StartActivityParams(
+                uri = "https://failing.pm.server.android.com",
+                expected = "$TARGET_FOUR_PKG_NAME.TargetActivity"
+        ))
+
+        // Re-installing with same host/verify set will maintain always setting
+        installPackage(TARGET_FOUR_BASE)
+        assertReceivedRequests(null)
+        runTest(StartActivityParams(
+                uri = "https://failing.pm.server.android.com",
+                expected = "$TARGET_FOUR_PKG_NAME.TargetActivity"
+        ))
+
+        // Installing with new wildcard host will downgrade out of always, re-including browsers
+        installPackage(TARGET_FOUR_WILDCARD)
+
+        // TODO(b/159952358): The first request without the wildcard should not be sent. This is
+        //  caused by the request being queued even if it should be dropped from the previous
+        //  install case since the host set didn't change.
+        assertReceivedRequests(false, VerifyRequest(
+                scheme = "https",
+                hosts = listOf("failing.pm.server.android.com"),
+                packageName = TARGET_FOUR_PKG_NAME
+        ), VerifyRequest(
+                scheme = "https",
+                hosts = listOf("failing.pm.server.android.com", "wildcard.tld"),
+                packageName = TARGET_FOUR_PKG_NAME
+        ))
+        runTest(StartActivityParams(
+                uri = "https://failing.pm.server.android.com",
+                expected = "$TARGET_FOUR_PKG_NAME.TargetActivity",
+                withBrowsers = true
+        ))
+    }
+
+    @Test
+    fun unverifiedReinstallResendRequest() {
+        installPackage(TARGET_FOUR_BASE)
+        assertReceivedRequests(false, VerifyRequest(
+                scheme = "https",
+                host = "failing.pm.server.android.com",
+                packageName = TARGET_FOUR_PKG_NAME
+        ))
+
+        installPackage(TARGET_FOUR_BASE)
+
+        assertReceivedRequests(false, VerifyRequest(
+                scheme = "https",
+                host = "failing.pm.server.android.com",
+                packageName = TARGET_FOUR_PKG_NAME
+        ))
+    }
+
+    @Test
+    fun unverifiedUpdateRemovingDomainNoRequestDemoteAlways() {
+        installPackage(TARGET_FOUR_WILDCARD)
+        assertReceivedRequests(false, VerifyRequest(
+                scheme = "https",
+                hosts = listOf("failing.pm.server.android.com", "wildcard.tld"),
+                packageName = TARGET_FOUR_PKG_NAME
+        ))
+
+        runTest(SetActivityAsAlwaysParams(
+                uri = "https://failing.pm.server.android.com",
+                packageName = TARGET_FOUR_PKG_NAME,
+                activityName = "$TARGET_FOUR_PKG_NAME.TargetActivity"
+        ))
+
+        // Re-installing with a smaller host/verify set will not request re-verification
+        installPackage(TARGET_FOUR_BASE)
+        assertReceivedRequests(null)
+        runTest(StartActivityParams(
+                uri = "https://failing.pm.server.android.com",
+                expected = "$TARGET_FOUR_PKG_NAME.TargetActivity"
+        ))
+
+        // Re-installing with a (now) larger host/verify set will re-request and demote
+        installPackage(TARGET_FOUR_WILDCARD)
+        // TODO(b/159952358): The first request should not be sent. This is caused by the request
+        //  being queued even if it should be dropped from the previous install case.
+        assertReceivedRequests(false, VerifyRequest(
+                scheme = "https",
+                host = "failing.pm.server.android.com",
+                packageName = TARGET_FOUR_PKG_NAME
+        ), VerifyRequest(
+                scheme = "https",
+                hosts = listOf("failing.pm.server.android.com", "wildcard.tld"),
+                packageName = TARGET_FOUR_PKG_NAME
+        ))
+
+        runTest(StartActivityParams(
+                uri = "https://failing.pm.server.android.com",
+                expected = "$TARGET_FOUR_PKG_NAME.TargetActivity",
+                withBrowsers = true
+        ))
+    }
+
+    // TODO(b/159952358): I would expect this to demote
+    // TODO(b/32810168)
+    @Test
+    fun verifiedUpdateRemovingAutoVerifyMaintainsAlways() {
+        installPackage(TARGET_FOUR_BASE)
+        assertReceivedRequests(true, VerifyRequest(
+                scheme = "https",
+                host = "failing.pm.server.android.com",
+                packageName = TARGET_FOUR_PKG_NAME
+        ))
+
+        runTest(StartActivityParams(
+                uri = "https://failing.pm.server.android.com",
+                expected = "$TARGET_FOUR_PKG_NAME.TargetActivity"
+        ))
+
+        installPackage(TARGET_FOUR_NO_AUTO_VERIFY)
+        assertReceivedRequests(null)
+
+        runTest(StartActivityParams(
+                uri = "https://failing.pm.server.android.com",
+                expected = "$TARGET_FOUR_PKG_NAME.TargetActivity"
+        ))
+    }
+
+    @Test
+    fun verifiedUpdateRemovingAutoVerifyAddingDomainDemotesAlways() {
+        installPackage(TARGET_FOUR_BASE)
+
+        assertReceivedRequests(true, VerifyRequest(
+                scheme = "https",
+                host = "failing.pm.server.android.com",
+                packageName = TARGET_FOUR_PKG_NAME
+        ))
+
+        runTest(StartActivityParams(
+                uri = "https://failing.pm.server.android.com",
+                expected = "$TARGET_FOUR_PKG_NAME.TargetActivity"
+        ))
+
+        installPackage(TARGET_FOUR_WILDCARD_NO_AUTO_VERIFY)
+        assertReceivedRequests(null)
+
+        runTest(StartActivityParams(
+                uri = "https://failing.pm.server.android.com",
+                expected = "$TARGET_FOUR_PKG_NAME.TargetActivity",
+                withBrowsers = true
+        ))
+    }
+
+    private fun installPackage(javaResourceName: String) {
+        // Need to pass --user as verification is not currently run for all user installs
+        assertThat(device.installJavaResourceApk(tempFolder, javaResourceName,
+                extraArgs = arrayOf("--user", device.currentUser.toString()))).isNull()
+    }
+
+    private fun assertReceivedRequests(success: Boolean?, vararg expected: VerifyRequest?) {
+        // TODO(b/159952358): This can probably be less than 10
+        // Because tests have to assert that multiple broadcasts aren't received, there's no real
+        // better way to await for a value than sleeping for a long enough time.
+        TimeUnit.SECONDS.sleep(10)
+
+        val params = mutableMapOf<String, String>()
+        if (expected.any { it != null }) {
+            params["expected"] = expected.filterNotNull()
+                    .joinToString(separator = "") { it.serializeToString() }
+        }
+        runTest("compareLastReceived", params)
+
+        if (success != null) {
+            if (success) {
+                runTest("verifyPreviousReceivedSuccess")
+            } else {
+                runTest("verifyPreviousReceivedFailure")
+            }
+            runTest("clearResponse")
+        }
+    }
+
+    private fun runTest(params: IntentVerifyTestParams) =
+            runTest(params.methodName, params.toArgsMap())
+
+    private fun runTest(testName: String, args: Map<String, String> = emptyMap()) {
+        val escapedArgs = args.mapValues {
+            // Need to escape strings so that args are passed properly through the shell command
+            "\"${it.value.trim('"')}\""
+        }
+        runDeviceTests(device, null, VERIFIER_PKG_NAME, "$VERIFIER_PKG_NAME.VerifyReceiverTest",
+                testName, null, 10 * 60 * 1000L, 10 * 60 * 1000L, 0L, true, false, escapedArgs)
+    }
+}
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifier/Android.bp b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifier/Android.bp
new file mode 100644
index 0000000..e82f57d
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifier/Android.bp
@@ -0,0 +1,28 @@
+// 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.
+
+android_test_helper_app {
+    name: "PackageManagerTestIntentVerifier",
+    srcs: [ "src/**/*.kt" ],
+    static_libs: [
+        "androidx.test.core",
+        "androidx.test.espresso.core",
+        "androidx.test.runner",
+        "compatibility-device-util-axt",
+        "junit",
+        "truth-prebuilt",
+        "PackageManagerServiceHostTestsIntentVerifyUtils",
+    ],
+    platform_apis: true,
+}
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifier/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifier/AndroidManifest.xml
new file mode 100644
index 0000000..17b50b0
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifier/AndroidManifest.xml
@@ -0,0 +1,38 @@
+<?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.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.server.pm.test.intent.verifier"
+    >
+
+    <uses-permission android:name="android.permission.INTENT_FILTER_VERIFICATION_AGENT" />
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+    <uses-permission android:name="android.permission.SET_PREFERRED_APPLICATIONS" />
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.server.pm.test.intent.verifier"
+        />
+
+    <application>
+        <receiver android:name=".VerifyReceiver" android:exported="true">
+            <intent-filter android:priority="999">
+                <action android:name="android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION"/>
+                <data android:mimeType="application/vnd.android.package-archive"/>
+            </intent-filter>
+        </receiver>
+    </application>
+
+</manifest>
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifier/src/com/android/server/pm/test/intent/verifier/VerifyReceiver.kt b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifier/src/com/android/server/pm/test/intent/verifier/VerifyReceiver.kt
new file mode 100644
index 0000000..073c2be
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifier/src/com/android/server/pm/test/intent/verifier/VerifyReceiver.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.pm.test.intent.verifier
+
+import android.content.BroadcastReceiver
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import com.android.server.pm.test.intent.verify.VerifyRequest
+
+class VerifyReceiver : BroadcastReceiver() {
+
+    override fun onReceive(context: Context, intent: Intent) {
+        if (intent.action != Intent.ACTION_INTENT_FILTER_NEEDS_VERIFICATION) return
+        val params = intent.toVerifyParams()
+
+        // If the receiver is called for a normal request, proxy it to the real verifier on device
+        if (params.hosts.none { it.contains("pm.server.android.com") }) {
+            sendToRealVerifier(context, Intent(intent))
+            return
+        }
+
+        // When the receiver is invoked for a test install, there is no direct connection to host,
+        // so store the result in a file to read and assert on later. Append is intentional so that
+        // amount of invocations and clean up can be verified.
+        context.filesDir.resolve("test.txt")
+                .appendText(params.serializeToString())
+    }
+
+    private fun sendToRealVerifier(context: Context, intent: Intent) {
+        context.packageManager.queryBroadcastReceivers(intent, 0)
+                .first { it.activityInfo?.packageName != context.packageName }
+                .let { it.activityInfo!! }
+                .let { intent.setComponent(ComponentName(it.packageName, it.name)) }
+                .run { context.sendBroadcast(intent) }
+    }
+
+    private fun Intent.toVerifyParams() = VerifyRequest(
+            id = getIntExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_ID, -1),
+            scheme = getStringExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_URI_SCHEME)!!,
+            hosts = getStringExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_HOSTS)!!
+                    .split(' '),
+            packageName = getStringExtra(
+                    PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_PACKAGE_NAME)!!
+
+    )
+}
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifier/src/com/android/server/pm/test/intent/verifier/VerifyReceiverTest.kt b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifier/src/com/android/server/pm/test/intent/verifier/VerifyReceiverTest.kt
new file mode 100644
index 0000000..6de3d4e
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifier/src/com/android/server/pm/test/intent/verifier/VerifyReceiverTest.kt
@@ -0,0 +1,160 @@
+/*
+ * 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.pm.test.intent.verifier
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.pm.PackageManager
+import android.net.Uri
+import android.os.Bundle
+import android.os.UserHandle
+import androidx.test.InstrumentationRegistry
+import androidx.test.runner.AndroidJUnit4
+import com.android.compatibility.common.util.ShellIdentityUtils
+import com.android.server.pm.test.intent.verify.SetActivityAsAlwaysParams
+import com.android.server.pm.test.intent.verify.StartActivityParams
+import com.android.server.pm.test.intent.verify.VerifyRequest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.io.File
+
+@RunWith(AndroidJUnit4::class)
+class VerifyReceiverTest {
+
+    val args: Bundle = InstrumentationRegistry.getArguments()
+    val context: Context = InstrumentationRegistry.getContext()
+
+    private val file = context.filesDir.resolve("test.txt")
+
+    @Test
+    fun clearResponse() {
+        file.delete()
+    }
+
+    @Test
+    fun compareLastReceived() {
+        val lastReceivedText = file.readTextIfExists()
+        val expectedText = args.getString("expected")
+        if (expectedText.isNullOrEmpty()) {
+            assertThat(lastReceivedText).isEmpty()
+            return
+        }
+
+        val expectedParams = expectedText.parseParams()
+        val lastReceivedParams = lastReceivedText.parseParams()
+
+        assertThat(lastReceivedParams).hasSize(expectedParams.size)
+
+        lastReceivedParams.zip(expectedParams).forEach { (actual, expected) ->
+            assertThat(actual.hosts).containsExactlyElementsIn(expected.hosts)
+            assertThat(actual.packageName).isEqualTo(expected.packageName)
+            assertThat(actual.scheme).isEqualTo(expected.scheme)
+        }
+    }
+
+    @Test
+    fun setActivityAsAlways() {
+        val params = SetActivityAsAlwaysParams.fromArgs(
+                args.keySet().associateWith { args.getString(it)!! })
+        val uri = Uri.parse(params.uri)
+        val filter = IntentFilter().apply {
+            addAction(Intent.ACTION_VIEW)
+            addCategory(Intent.CATEGORY_DEFAULT)
+            addDataScheme(uri.scheme)
+            addDataAuthority(uri.authority, null)
+        }
+
+        val intent = Intent(Intent.ACTION_VIEW, uri).apply {
+            addCategory(Intent.CATEGORY_DEFAULT)
+        }
+        val allResults = context.packageManager.queryIntentActivities(intent, 0)
+        val allComponents = allResults
+                .map { ComponentName(it.activityInfo.packageName, it.activityInfo.name) }
+                .toTypedArray()
+        val matchingInfo = allResults.first {
+            it.activityInfo.packageName == params.packageName &&
+                    it.activityInfo.name == params.activityName
+        }
+
+        ShellIdentityUtils.invokeMethodWithShellPermissions(context.packageManager,
+                ShellIdentityUtils.ShellPermissionMethodHelper<Unit, PackageManager> {
+                    it.addUniquePreferredActivity(filter, matchingInfo.match, allComponents,
+                            ComponentName(matchingInfo.activityInfo.packageName,
+                                    matchingInfo.activityInfo.name))
+                    it.updateIntentVerificationStatusAsUser(params.packageName,
+                            PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS,
+                            UserHandle.myUserId())
+                }, "android.permission.SET_PREFERRED_APPLICATIONS")
+    }
+
+    @Test
+    fun verifyPreviousReceivedSuccess() {
+        file.readTextIfExists()
+                .parseParams()
+                .forEach {
+                    context.packageManager.verifyIntentFilter(it.id,
+                            PackageManager.INTENT_FILTER_VERIFICATION_SUCCESS, emptyList())
+                }
+    }
+
+    @Test
+    fun verifyPreviousReceivedFailure() {
+        file.readTextIfExists()
+                .parseParams()
+                .forEach {
+                    context.packageManager.verifyIntentFilter(it.id,
+                            PackageManager.INTENT_FILTER_VERIFICATION_FAILURE, it.hosts)
+                }
+    }
+
+    @Test
+    fun verifyActivityStart() {
+        val params = StartActivityParams
+                .fromArgs(args.keySet().associateWith { args.getString(it)!! })
+        val uri = Uri.parse(params.uri)
+        val intent = Intent(Intent.ACTION_VIEW).apply {
+            data = uri
+            addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+        }
+
+        val expectedActivities = params.expected.toMutableList()
+
+        if (params.withBrowsers) {
+            // Since the host doesn't know what browsers the device has, query here and add it to
+            // set if it's expected that browser are returned
+            val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse("https://example.com"))
+            expectedActivities += context.packageManager.queryIntentActivities(browserIntent, 0)
+                    .map { it.activityInfo.name }
+        }
+
+        val infos = context.packageManager.queryIntentActivities(intent, 0)
+                .map { it.activityInfo.name }
+        assertThat(infos).containsExactlyElementsIn(expectedActivities)
+    }
+
+    private fun File.readTextIfExists() = if (exists()) readText() else ""
+
+    // Rudimentary list deserialization by splitting text block into 4 line sections
+    private fun String.parseParams() = trim()
+            .lines()
+            .windowed(4, 4)
+            .map { it.joinToString(separator = "\n") }
+            .map { VerifyRequest.deserialize(it) }
+}
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/Android.bp b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/Android.bp
new file mode 100644
index 0000000..7161fdd
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/Android.bp
@@ -0,0 +1,48 @@
+// 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.
+
+android_test_helper_app {
+    name: "PackageManagerTestIntentVerifierTarget1",
+    manifest: "AndroidManifest1.xml",
+}
+
+android_test_helper_app {
+    name: "PackageManagerTestIntentVerifierTarget2",
+    manifest: "AndroidManifest2.xml",
+}
+
+android_test_helper_app {
+    name: "PackageManagerTestIntentVerifierTarget3",
+    manifest: "AndroidManifest3.xml",
+}
+
+android_test_helper_app {
+    name: "PackageManagerTestIntentVerifierTarget4Base",
+    manifest: "AndroidManifest4Base.xml",
+}
+
+android_test_helper_app {
+    name: "PackageManagerTestIntentVerifierTarget4NoAutoVerify",
+    manifest: "AndroidManifest4NoAutoVerify.xml",
+}
+
+android_test_helper_app {
+    name: "PackageManagerTestIntentVerifierTarget4Wildcard",
+    manifest: "AndroidManifest4Wildcard.xml",
+}
+
+android_test_helper_app {
+    name: "PackageManagerTestIntentVerifierTarget4WildcardNoAutoVerify",
+    manifest: "AndroidManifest4WildcardNoAutoVerify.xml",
+}
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest1.xml b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest1.xml
new file mode 100644
index 0000000..6cf5c76
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest1.xml
@@ -0,0 +1,94 @@
+<?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.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.server.pm.test.intent.verifier.target.one" android:versionCode="1">
+
+    <application>
+        <activity android:name=".TargetActivity" android:exported="true">
+            <intent-filter android:autoVerify="true">
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="http" />
+                <data android:scheme="https" />
+                <data android:host="verify.pm.server.android.com" />
+            </intent-filter>
+
+            <intent-filter android:autoVerify="false">
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="http" />
+                <data android:scheme="https" />
+                <data android:host="no_verify.pm.server.android.com" />
+            </intent-filter>
+
+            <intent-filter android:autoVerify="true">
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="http" />
+                <data android:host="http_only.pm.server.android.com" />
+            </intent-filter>
+
+            <intent-filter android:autoVerify="true">
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="https" />
+                <data android:host="https_only.pm.server.android.com" />
+            </intent-filter>
+
+            <intent-filter android:autoVerify="true">
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="htttps" />
+                <data android:host="non_http.pm.server.android.com" />
+            </intent-filter>
+
+            <intent-filter android:autoVerify="true">
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="https" />
+                <data android:scheme="non_web_scheme" />
+                <data android:host="https_plus_non_web_scheme.pm.server.android.com" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".TargetActivity2" android:exported="true">
+            <intent-filter android:autoVerify="true">
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="http" />
+                <data android:scheme="https" />
+                <data android:host="other_activity.pm.server.android.com" />
+            </intent-filter>
+
+            <intent-filter android:autoVerify="true">
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="http" />
+                <data android:scheme="https" />
+                <data android:host="multiple.pm.server.android.com" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest2.xml b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest2.xml
new file mode 100644
index 0000000..087ef70
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest2.xml
@@ -0,0 +1,34 @@
+<?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.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.server.pm.test.intent.verifier.target.two"
+    android:versionCode="1">
+
+    <application>
+        <activity android:name=".TargetActivity" android:exported="true">
+            <intent-filter android:autoVerify="true">
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="http" />
+                <data android:scheme="https" />
+                <data android:scheme="non_web_scheme" />
+                <data android:host="only_https_plus_non_web_scheme.pm.server.android.com" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest3.xml b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest3.xml
new file mode 100644
index 0000000..eb75b5e
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest3.xml
@@ -0,0 +1,32 @@
+<?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.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.server.pm.test.intent.verifier.target.three"
+    android:versionCode="1">
+
+    <application>
+        <activity android:name=".TargetActivity" android:exported="true">
+            <intent-filter android:autoVerify="true">
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="http" />
+                <data android:host="multiple.pm.server.android.com" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest4Base.xml b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest4Base.xml
new file mode 100644
index 0000000..7eacb8b
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest4Base.xml
@@ -0,0 +1,33 @@
+<?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.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.server.pm.test.intent.verifier.target.four"
+    android:versionCode="1">
+
+    <application>
+        <activity android:name=".TargetActivity" android:exported="true">
+            <intent-filter android:autoVerify="true">
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="http" />
+                <data android:scheme="https" />
+                <data android:host="failing.pm.server.android.com" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest4NoAutoVerify.xml b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest4NoAutoVerify.xml
new file mode 100644
index 0000000..ecfee55
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest4NoAutoVerify.xml
@@ -0,0 +1,33 @@
+<?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.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.server.pm.test.intent.verifier.target.four"
+    android:versionCode="1">
+
+    <application>
+        <activity android:name=".TargetActivity" android:exported="true">
+            <intent-filter android:autoVerify="false">
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="http" />
+                <data android:scheme="https" />
+                <data android:host="failing.pm.server.android.com" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest4Wildcard.xml b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest4Wildcard.xml
new file mode 100644
index 0000000..0f0f53b
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest4Wildcard.xml
@@ -0,0 +1,34 @@
+<?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.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.server.pm.test.intent.verifier.target.four"
+    android:versionCode="1">
+
+    <application>
+        <activity android:name=".TargetActivity" android:exported="true">
+            <intent-filter android:autoVerify="true">
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="http" />
+                <data android:scheme="https" />
+                <data android:host="failing.pm.server.android.com" />
+                <data android:host="*.wildcard.tld" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest4WildcardNoAutoVerify.xml b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest4WildcardNoAutoVerify.xml
new file mode 100644
index 0000000..d5652e1
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest4WildcardNoAutoVerify.xml
@@ -0,0 +1,34 @@
+<?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.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.server.pm.test.intent.verifier.target.four"
+    android:versionCode="1">
+
+    <application>
+        <activity android:name=".TargetActivity" android:exported="true">
+            <intent-filter android:autoVerify="false">
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="http" />
+                <data android:scheme="https" />
+                <data android:host="failing.pm.server.android.com" />
+                <data android:host="*.wildcard.tld" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
index 2f5e883..3220dff 100644
--- a/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
@@ -48,6 +48,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.net.Uri;
 import android.os.BatteryManager;
 import android.os.Handler;
 import android.os.Looper;
@@ -282,7 +283,7 @@
                 eq(ServiceType.FORCE_ALL_APPS_STANDBY),
                 powerSaveObserverCaptor.capture());
 
-        verify(mMockContext).registerReceiver(
+        verify(mMockContext, times(2)).registerReceiver(
                 receiverCaptor.capture(), any(IntentFilter.class));
         verify(mMockAppStandbyInternal).addListener(
                 appIdleStateChangeListenerCaptor.capture());
@@ -1242,6 +1243,67 @@
         assertTrue(instance.isForceAllAppsStandbyEnabled());
     }
 
+    @Test
+    public void testStateClearedOnPackageRemoved() throws Exception {
+        final AppStateTrackerTestable instance = newInstance();
+        callStart(instance);
+
+        instance.mActiveUids.put(UID_1, true);
+        instance.mForegroundUids.put(UID_2, true);
+        instance.mRunAnyRestrictedPackages.add(Pair.create(UID_1, PACKAGE_1));
+        instance.mExemptedBucketPackages.add(UserHandle.getUserId(UID_2), PACKAGE_2);
+
+        // Replace PACKAGE_1, nothing should change
+        Intent packageRemoved = new Intent(Intent.ACTION_PACKAGE_REMOVED)
+                .putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(UID_1))
+                .putExtra(Intent.EXTRA_UID, UID_1)
+                .putExtra(Intent.EXTRA_REPLACING, true)
+                .setData(Uri.fromParts(IntentFilter.SCHEME_PACKAGE, PACKAGE_1, null));
+        mReceiver.onReceive(mMockContext, packageRemoved);
+
+        assertEquals(1, instance.mActiveUids.size());
+        assertEquals(1, instance.mForegroundUids.size());
+        assertEquals(1, instance.mRunAnyRestrictedPackages.size());
+        assertEquals(1, instance.mExemptedBucketPackages.size());
+
+        // Replace PACKAGE_2, nothing should change
+        packageRemoved = new Intent(Intent.ACTION_PACKAGE_REMOVED)
+                .putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(UID_2))
+                .putExtra(Intent.EXTRA_UID, UID_2)
+                .putExtra(Intent.EXTRA_REPLACING, true)
+                .setData(Uri.fromParts(IntentFilter.SCHEME_PACKAGE, PACKAGE_2, null));
+        mReceiver.onReceive(mMockContext, packageRemoved);
+
+        assertEquals(1, instance.mActiveUids.size());
+        assertEquals(1, instance.mForegroundUids.size());
+        assertEquals(1, instance.mRunAnyRestrictedPackages.size());
+        assertEquals(1, instance.mExemptedBucketPackages.size());
+
+        // Remove PACKAGE_1
+        packageRemoved = new Intent(Intent.ACTION_PACKAGE_REMOVED)
+                .putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(UID_1))
+                .putExtra(Intent.EXTRA_UID, UID_1)
+                .setData(Uri.fromParts(IntentFilter.SCHEME_PACKAGE, PACKAGE_1, null));
+        mReceiver.onReceive(mMockContext, packageRemoved);
+
+        assertEquals(0, instance.mActiveUids.size());
+        assertEquals(1, instance.mForegroundUids.size());
+        assertEquals(0, instance.mRunAnyRestrictedPackages.size());
+        assertEquals(1, instance.mExemptedBucketPackages.size());
+
+        // Remove PACKAGE_2
+        packageRemoved = new Intent(Intent.ACTION_PACKAGE_REMOVED)
+                .putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(UID_2))
+                .putExtra(Intent.EXTRA_UID, UID_2)
+                .setData(Uri.fromParts(IntentFilter.SCHEME_PACKAGE, PACKAGE_2, null));
+        mReceiver.onReceive(mMockContext, packageRemoved);
+
+        assertEquals(0, instance.mActiveUids.size());
+        assertEquals(0, instance.mForegroundUids.size());
+        assertEquals(0, instance.mRunAnyRestrictedPackages.size());
+        assertEquals(0, instance.mExemptedBucketPackages.size());
+    }
+
     static int[] array(int... appIds) {
         Arrays.sort(appIds);
         return appIds;
diff --git a/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java b/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java
index 02d10e3..b7a36f2 100644
--- a/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java
@@ -223,9 +223,24 @@
     }
 
     @Test
-    public void arePrimitivesSupported_withComposeCapability_returnsAlwaysTrue() {
+    public void arePrimitivesSupported_withNullResultFromNative_returnsAlwaysFalse() {
         mockVibratorCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
-        assertArrayEquals(new boolean[]{true, true},
+        when(mNativeWrapperMock.vibratorGetSupportedPrimitives()).thenReturn(null);
+
+        assertArrayEquals(new boolean[]{false, false},
+                createService().arePrimitivesSupported(new int[]{
+                        VibrationEffect.Composition.PRIMITIVE_CLICK,
+                        VibrationEffect.Composition.PRIMITIVE_QUICK_RISE
+                }));
+    }
+
+    @Test
+    public void arePrimitivesSupported_withSomeSupportedPrimitives_returnsBasedOnNativeResult() {
+        mockVibratorCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        when(mNativeWrapperMock.vibratorGetSupportedPrimitives())
+                .thenReturn(new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK});
+
+        assertArrayEquals(new boolean[]{true, false},
                 createService().arePrimitivesSupported(new int[]{
                         VibrationEffect.Composition.PRIMITIVE_CLICK,
                         VibrationEffect.Composition.PRIMITIVE_QUICK_RISE
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MockWindowMagnificationConnection.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MockWindowMagnificationConnection.java
index c29c510..42ba842 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MockWindowMagnificationConnection.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MockWindowMagnificationConnection.java
@@ -17,23 +17,33 @@
 package com.android.server.accessibility.magnification;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import android.graphics.Rect;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.view.Display;
 import android.view.accessibility.IWindowMagnificationConnection;
 import android.view.accessibility.IWindowMagnificationConnectionCallback;
 
+/**
+ * Mocks the basic logic of window magnification in System UI. We assume the screen size is
+ * unlimited, so source bounds is always on the center of the mirror window bounds.
+ */
 class MockWindowMagnificationConnection  {
 
+    public static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY;
     private final IWindowMagnificationConnection mConnection;
     private final Binder mBinder;
     private IBinder.DeathRecipient mDeathRecipient;
     private IWindowMagnificationConnectionCallback mIMirrorWindowCallback;
+    private Rect mMirrorWindowFrame = new Rect(0, 0, 500, 500);
 
     MockWindowMagnificationConnection() throws RemoteException {
         mConnection = mock(IWindowMagnificationConnection.class);
@@ -50,6 +60,30 @@
             return null;
         }).when(mBinder).linkToDeath(
                 any(IBinder.DeathRecipient.class), eq(0));
+        stubConnection();
+    }
+
+    private void stubConnection() throws RemoteException {
+        doAnswer((invocation) -> {
+            final int displayId = invocation.getArgument(0);
+            if (displayId != TEST_DISPLAY) {
+                throw new IllegalArgumentException("only support default display :" + displayId);
+            }
+            computeMirrorWindowFrame(invocation.getArgument(1), invocation.getArgument(2));
+
+            mIMirrorWindowCallback.onWindowMagnifierBoundsChanged(TEST_DISPLAY,
+                    mMirrorWindowFrame);
+            return null;
+        }).when(mConnection).enableWindowMagnification(anyInt(),
+                anyFloat(), anyFloat(), anyFloat());
+    }
+
+    private void computeMirrorWindowFrame(float centerX, float centerY) {
+        final float offsetX = Float.isNaN(centerX) ? 0
+                : centerX - mMirrorWindowFrame.exactCenterX();
+        final float offsetY = Float.isNaN(centerY) ? 0
+                : centerY - mMirrorWindowFrame.exactCenterY();
+        mMirrorWindowFrame.offset((int) offsetX, (int) offsetY);
     }
 
     IWindowMagnificationConnection getConnection() {
@@ -60,12 +94,16 @@
         return mBinder;
     }
 
-    public IBinder.DeathRecipient getDeathRecipient() {
+    IBinder.DeathRecipient getDeathRecipient() {
         return mDeathRecipient;
     }
 
-    public IWindowMagnificationConnectionCallback getConnectionCallback() {
+    IWindowMagnificationConnectionCallback getConnectionCallback() {
         return mIMirrorWindowCallback;
     }
+
+    public Rect getMirrorWindowFrame() {
+        return new Rect(mMirrorWindowFrame);
+    }
 }
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/TwoFingersDownTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/TwoFingersDownTest.java
index 2b1bdc5..ed8dc4e 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/TwoFingersDownTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/TwoFingersDownTest.java
@@ -87,15 +87,15 @@
         secondPointerCoords.x = DEFAULT_X + 10;
         secondPointerCoords.y = DEFAULT_Y + 10;
 
-        final MotionEvent pointerDownEvent = TouchEventGenerator.pointerDownEvent(
+        final MotionEvent twoPointersDownEvent = TouchEventGenerator.twoPointersDownEvent(
                 Display.DEFAULT_DISPLAY, defPointerCoords, secondPointerCoords);
 
         mGesturesObserver.onMotionEvent(downEvent, downEvent, 0);
-        mGesturesObserver.onMotionEvent(pointerDownEvent, pointerDownEvent, 0);
+        mGesturesObserver.onMotionEvent(twoPointersDownEvent, twoPointersDownEvent, 0);
 
         verify(mListener, timeout(sTimeoutMillis)).onGestureCompleted(
-                MagnificationGestureMatcher.GESTURE_TWO_FINGER_DOWN, pointerDownEvent,
-                pointerDownEvent, 0);
+                MagnificationGestureMatcher.GESTURE_TWO_FINGER_DOWN, twoPointersDownEvent,
+                twoPointersDownEvent, 0);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java
index e580340..bec9f26 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java
@@ -16,14 +16,9 @@
 
 package com.android.server.accessibility.magnification;
 
-import static android.view.MotionEvent.ACTION_POINTER_DOWN;
-
 import static com.android.server.testutils.TestUtils.strictMock;
 
 import static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.anyFloat;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 
 import android.content.Context;
@@ -63,11 +58,9 @@
     public static final int LAST_STATE = STATE_SHOW_MAGNIFIER_TRIPLE_TAP;
 
     // Co-prime x and y, to potentially catch x-y-swapped errors
-    public static final float DEFAULT_X = 301;
-    public static final float DEFAULT_Y = 299;
-    //Assume first pointer position (DEFAULT_X,DEFAULT_Y) is in the window.
-    public static Rect DEFAULT_WINDOW_FRAME = new Rect(0, 0, 500, 500);
-    private static final int DISPLAY_0 = 0;
+    public static final float DEFAULT_TAP_X = 301;
+    public static final float DEFAULT_TAP_Y = 299;
+    private static final int DISPLAY_0 = MockWindowMagnificationConnection.TEST_DISPLAY;
 
     private Context mContext;
     private WindowMagnificationManager mWindowMagnificationManager;
@@ -83,14 +76,6 @@
                 mContext, mWindowMagnificationManager, mock(ScaleChangedListener.class),
                 /** detectTripleTap= */true,   /** detectShortcutTrigger= */true, DISPLAY_0);
         mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-        mMockConnection.getConnectionCallback().onWindowMagnifierBoundsChanged(DISPLAY_0,
-                DEFAULT_WINDOW_FRAME);
-        doAnswer((invocation) -> {
-            mMockConnection.getConnectionCallback().onWindowMagnifierBoundsChanged(DISPLAY_0,
-                    DEFAULT_WINDOW_FRAME);
-            return null;
-        }).when(mMockConnection.getConnection()).enableWindowMagnification(eq(DISPLAY_0),
-                anyFloat(), anyFloat(), anyFloat());
         mWindowMagnificationGestureHandler.setNext(strictMock(EventStreamTransformation.class));
     }
 
@@ -208,10 +193,11 @@
                 break;
                 case STATE_TWO_FINGERS_DOWN: {
                     goFromStateIdleTo(STATE_SHOW_MAGNIFIER);
-                    send(downEvent());
+                    final Rect frame = mMockConnection.getMirrorWindowFrame();
+                    send(downEvent(frame.centerX(), frame.centerY()));
                     //Second finger is outside the window.
-                    send(pointerEvent(ACTION_POINTER_DOWN, DEFAULT_WINDOW_FRAME.right + 10,
-                            DEFAULT_WINDOW_FRAME.bottom + 10));
+                    send(twoPointerDownEvent(new float[]{frame.centerX(), frame.centerX() + 10},
+                            new float[]{frame.centerY(), frame.centerY() + 10}));
                 }
                 break;
                 case STATE_SHOW_MAGNIFIER_TRIPLE_TAP: {
@@ -243,7 +229,8 @@
             }
             break;
             case STATE_TWO_FINGERS_DOWN: {
-                send(upEvent());
+                final Rect frame = mMockConnection.getMirrorWindowFrame();
+                send(upEvent(frame.centerX(), frame.centerY()));
                 returnToNormalFrom(STATE_SHOW_MAGNIFIER);
             }
             break;
@@ -286,12 +273,8 @@
         }
     }
 
-    private MotionEvent downEvent() {
-        return TouchEventGenerator.downEvent(DISPLAY_0, DEFAULT_X, DEFAULT_Y);
-    }
-
-    private MotionEvent upEvent() {
-        return upEvent(DEFAULT_X, DEFAULT_Y);
+    private MotionEvent downEvent(float x, float y) {
+        return TouchEventGenerator.downEvent(DISPLAY_0, x, y);
     }
 
     private MotionEvent upEvent(float x, float y) {
@@ -299,18 +282,18 @@
     }
 
     private void tap() {
-        send(downEvent());
-        send(upEvent());
+        send(downEvent(DEFAULT_TAP_X, DEFAULT_TAP_Y));
+        send(upEvent(DEFAULT_TAP_X, DEFAULT_TAP_Y));
     }
 
-    private MotionEvent pointerEvent(int action, float x, float y) {
+    private MotionEvent twoPointerDownEvent(float[] x, float[] y) {
         final MotionEvent.PointerCoords defPointerCoords = new MotionEvent.PointerCoords();
-        defPointerCoords.x = DEFAULT_X;
-        defPointerCoords.y = DEFAULT_Y;
+        defPointerCoords.x = x[0];
+        defPointerCoords.y = y[0];
         final MotionEvent.PointerCoords pointerCoords = new MotionEvent.PointerCoords();
-        pointerCoords.x = x;
-        pointerCoords.y = y;
-        return TouchEventGenerator.pointerDownEvent(DISPLAY_0, defPointerCoords, pointerCoords);
+        pointerCoords.x = x[1];
+        pointerCoords.y = y[1];
+        return TouchEventGenerator.twoPointersDownEvent(DISPLAY_0, defPointerCoords, pointerCoords);
     }
 
     private String stateDump() {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/utils/TouchEventGenerator.java b/services/tests/servicestests/src/com/android/server/accessibility/utils/TouchEventGenerator.java
index 7cbf3ee..a05881f 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/utils/TouchEventGenerator.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/utils/TouchEventGenerator.java
@@ -43,9 +43,9 @@
         return generateSingleTouchEvent(displayId, ACTION_UP, x, y);
     }
 
-    public static MotionEvent pointerDownEvent(int displayId, PointerCoords defPointerCoords,
+    public static MotionEvent twoPointersDownEvent(int displayId, PointerCoords defPointerCoords,
             PointerCoords pointerCoords) {
-        return generatePointerEvent(displayId, ACTION_POINTER_DOWN, defPointerCoords,
+        return generateTwoPointersEvent(displayId, ACTION_POINTER_DOWN, defPointerCoords,
                 pointerCoords);
     }
 
@@ -59,7 +59,7 @@
         return ev;
     }
 
-    private static MotionEvent generatePointerEvent(int displayId, int action,
+    private static MotionEvent generateTwoPointersEvent(int displayId, int action,
             PointerCoords defPointerCoords, PointerCoords pointerCoords) {
         final long  downTime = SystemClock.uptimeMillis();
         MotionEvent.PointerProperties defPointerProperties = new MotionEvent.PointerProperties();
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index 6a797f3..03dce4c 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -32,7 +32,6 @@
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import static com.android.server.am.ActivityManagerInternalTest.CustomThread;
-import static com.android.server.am.ActivityManagerService.DISPATCH_UIDS_CHANGED_UI_MSG;
 import static com.android.server.am.ActivityManagerService.Injector;
 import static com.android.server.am.ProcessList.NETWORK_STATE_BLOCK;
 import static com.android.server.am.ProcessList.NETWORK_STATE_NO_CHANGE;
@@ -548,10 +547,10 @@
             pendingChange.processState = procStatesForPendingUidRecords[i];
             pendingChange.procStateSeq = i;
             changeItems.put(changesForPendingUidRecords[i], pendingChange);
-            mAms.mPendingUidChanges.add(pendingChange);
+            mAms.mUidObserverController.mPendingUidChanges.add(pendingChange);
         }
 
-        mAms.dispatchUidsChanged();
+        mAms.mUidObserverController.dispatchUidsChanged();
         // Verify the required changes have been dispatched to observers.
         for (int i = 0; i < observers.length; ++i) {
             final int changeToObserve = changesToObserve[i];
@@ -647,8 +646,8 @@
         changeItem.change = UidRecord.CHANGE_PROCSTATE;
         changeItem.processState = ActivityManager.PROCESS_STATE_LAST_ACTIVITY;
         changeItem.procStateSeq = 111;
-        mAms.mPendingUidChanges.add(changeItem);
-        mAms.dispatchUidsChanged();
+        mAms.mUidObserverController.mPendingUidChanges.add(changeItem);
+        mAms.mUidObserverController.dispatchUidsChanged();
         // First process state message is always delivered regardless of whether the process state
         // change is above or below the cutpoint (PROCESS_STATE_SERVICE).
         verify(observer).onUidStateChanged(TEST_UID,
@@ -657,15 +656,15 @@
         verifyNoMoreInteractions(observer);
 
         changeItem.processState = ActivityManager.PROCESS_STATE_RECEIVER;
-        mAms.mPendingUidChanges.add(changeItem);
-        mAms.dispatchUidsChanged();
+        mAms.mUidObserverController.mPendingUidChanges.add(changeItem);
+        mAms.mUidObserverController.dispatchUidsChanged();
         // Previous process state change is below cutpoint (PROCESS_STATE_SERVICE) and
         // the current process state change is also below cutpoint, so no callback will be invoked.
         verifyNoMoreInteractions(observer);
 
         changeItem.processState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
-        mAms.mPendingUidChanges.add(changeItem);
-        mAms.dispatchUidsChanged();
+        mAms.mUidObserverController.mPendingUidChanges.add(changeItem);
+        mAms.mUidObserverController.dispatchUidsChanged();
         // Previous process state change is below cutpoint (PROCESS_STATE_SERVICE) and
         // the current process state change is above cutpoint, so callback will be invoked with the
         // current process state change.
@@ -675,15 +674,15 @@
         verifyNoMoreInteractions(observer);
 
         changeItem.processState = ActivityManager.PROCESS_STATE_TOP;
-        mAms.mPendingUidChanges.add(changeItem);
-        mAms.dispatchUidsChanged();
+        mAms.mUidObserverController.mPendingUidChanges.add(changeItem);
+        mAms.mUidObserverController.dispatchUidsChanged();
         // Previous process state change is above cutpoint (PROCESS_STATE_SERVICE) and
         // the current process state change is also above cutpoint, so no callback will be invoked.
         verifyNoMoreInteractions(observer);
 
         changeItem.processState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
-        mAms.mPendingUidChanges.add(changeItem);
-        mAms.dispatchUidsChanged();
+        mAms.mUidObserverController.mPendingUidChanges.add(changeItem);
+        mAms.mUidObserverController.dispatchUidsChanged();
         // Previous process state change is above cutpoint (PROCESS_STATE_SERVICE) and
         // the current process state change is below cutpoint, so callback will be invoked with the
         // current process state change.
@@ -720,20 +719,21 @@
 
         // Verify that when there no observers listening to uid state changes, then there will
         // be no changes to validateUids.
-        mAms.mPendingUidChanges.addAll(pendingItemsForUids);
-        mAms.dispatchUidsChanged();
+        mAms.mUidObserverController.mPendingUidChanges.addAll(pendingItemsForUids);
+        mAms.mUidObserverController.dispatchUidsChanged();
         assertEquals("No observers registered, so validateUids should be empty",
-                0, mAms.mValidateUids.size());
+                0, mAms.mUidObserverController.mValidateUids.size());
 
         final IUidObserver observer = mock(IUidObserver.Stub.class);
         when(observer.asBinder()).thenReturn((IBinder) observer);
         mAms.registerUidObserver(observer, 0, 0, null);
         // Verify that when observers are registered, then validateUids is correctly updated.
-        mAms.mPendingUidChanges.addAll(pendingItemsForUids);
-        mAms.dispatchUidsChanged();
+        mAms.mUidObserverController.mPendingUidChanges.addAll(pendingItemsForUids);
+        mAms.mUidObserverController.dispatchUidsChanged();
         for (int i = 0; i < pendingItemsForUids.size(); ++i) {
             final UidRecord.ChangeItem item = pendingItemsForUids.get(i);
-            final UidRecord validateUidRecord = mAms.mValidateUids.get(item.uid);
+            final UidRecord validateUidRecord =
+                    mAms.mUidObserverController.mValidateUids.get(item.uid);
             if ((item.change & UidRecord.CHANGE_GONE) != 0) {
                 assertNull("validateUidRecord should be null since the change is either "
                         + "CHANGE_GONE or CHANGE_GONE_IDLE", validateUidRecord);
@@ -759,7 +759,7 @@
         // Verify that when uid state changes to CHANGE_GONE or CHANGE_GONE_IDLE, then it
         // will be removed from validateUids.
         assertNotEquals("validateUids should not be empty", 0,
-                mAms.mValidateUids.size());
+                mAms.mUidObserverController.mValidateUids.size());
         for (int i = 0; i < pendingItemsForUids.size(); ++i) {
             final UidRecord.ChangeItem item = pendingItemsForUids.get(i);
             // Assign CHANGE_GONE_IDLE to some items and CHANGE_GONE to the others, using even/odd
@@ -767,10 +767,11 @@
             item.change = (i % 2) == 0 ? (UidRecord.CHANGE_GONE | UidRecord.CHANGE_IDLE)
                     : UidRecord.CHANGE_GONE;
         }
-        mAms.mPendingUidChanges.addAll(pendingItemsForUids);
-        mAms.dispatchUidsChanged();
-        assertEquals("validateUids should be empty, size=" + mAms.mValidateUids.size(),
-                0, mAms.mValidateUids.size());
+        mAms.mUidObserverController.mPendingUidChanges.addAll(pendingItemsForUids);
+        mAms.mUidObserverController.dispatchUidsChanged();
+        assertEquals("validateUids should be empty, size="
+                + mAms.mUidObserverController.mValidateUids.size(),
+                        0, mAms.mUidObserverController.mValidateUids.size());
     }
 
     @Test
@@ -792,7 +793,7 @@
     @Test
     public void testEnqueueUidChangeLocked_nullUidRecord() {
         // Use "null" uidRecord to make sure there is no crash.
-        mAms.enqueueUidChangeLocked(null, TEST_UID, UidRecord.CHANGE_ACTIVE);
+        mAms.mUidObserverController.enqueueUidChangeLocked(null, TEST_UID, UidRecord.CHANGE_ACTIVE);
     }
 
     private void verifyLastProcStateSeqUpdated(UidRecord uidRecord, int uid, long curProcstateSeq) {
@@ -801,7 +802,7 @@
             final int changeToDispatch = UID_RECORD_CHANGES[i];
             // Reset lastProcStateSeqDispatchToObservers after every test.
             uidRecord.lastDispatchedProcStateSeq = 0;
-            mAms.enqueueUidChangeLocked(uidRecord, uid, changeToDispatch);
+            mAms.mUidObserverController.enqueueUidChangeLocked(uidRecord, uid, changeToDispatch);
             // Verify there is no effect on curProcStateSeq.
             assertEquals(curProcstateSeq, uidRecord.curProcStateSeq);
             if ((changeToDispatch & UidRecord.CHANGE_GONE) != 0) {
@@ -833,9 +834,9 @@
             // Reset the current state
             mHandler.reset();
             uidRecord.pendingChange = null;
-            mAms.mPendingUidChanges.clear();
+            mAms.mUidObserverController.mPendingUidChanges.clear();
 
-            mAms.enqueueUidChangeLocked(uidRecord, -1, changeToDispatch);
+            mAms.mUidObserverController.enqueueUidChangeLocked(uidRecord, -1, changeToDispatch);
 
             // Verify that UidRecord.pendingChange is updated correctly.
             assertNotNull(uidRecord.pendingChange);
@@ -843,8 +844,7 @@
             assertEquals(expectedProcState, uidRecord.pendingChange.processState);
             assertEquals(TEST_PROC_STATE_SEQ1, uidRecord.pendingChange.procStateSeq);
 
-            // Verify that DISPATCH_UIDS_CHANGED_UI_MSG is posted to handler.
-            mHandler.waitForMessage(DISPATCH_UIDS_CHANGED_UI_MSG);
+            // TODO: Verify that DISPATCH_UIDS_CHANGED_UI_MSG is posted to handler.
         }
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java b/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java
index a9cef20..609af8d 100644
--- a/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java
+++ b/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java
@@ -21,6 +21,8 @@
 import android.media.AudioSystem;
 import android.util.Log;
 
+import java.util.List;
+
 /**
  * Provides an adapter for AudioSystem that does nothing.
  * Overridden methods can be configured.
@@ -66,13 +68,13 @@
     }
 
     @Override
-    public int setPreferredDeviceForStrategy(int strategy,
-            @NonNull AudioDeviceAttributes device) {
+    public int setDevicesRoleForStrategy(int strategy, int role,
+            @NonNull List<AudioDeviceAttributes> devices) {
         return AudioSystem.AUDIO_STATUS_OK;
     }
 
     @Override
-    public int removePreferredDeviceForStrategy(int strategy) {
+    public int removeDevicesRoleForStrategy(int strategy, int role) {
         return AudioSystem.AUDIO_STATUS_OK;
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
index a5df532..94cad1e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
@@ -269,21 +269,6 @@
                 eq(callback), eq(UserHandle.getCallingUserId()));
     }
 
-    @Test
-    public void testResetLockout_callsBiometricServiceResetLockout() throws
-            Exception {
-        mAuthService = new AuthService(mContext, mInjector);
-        mAuthService.onStart();
-
-        final int userId = 100;
-        final byte[] token = new byte[0];
-
-        mAuthService.mImpl.resetLockout(userId, token);
-
-        waitForIdle();
-        verify(mBiometricService).resetLockout(eq(userId), AdditionalMatchers.aryEq(token));
-    }
-
     private static void waitForIdle() {
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index dad360d4..36c55cc 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -17,7 +17,6 @@
 package com.android.server.biometrics.sensors;
 
 import android.content.Context;
-import android.os.IBinder;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.annotation.NonNull;
@@ -56,8 +55,8 @@
         mScheduler.scheduleClientMonitor(client1);
         mScheduler.scheduleClientMonitor(client2);
 
-        client1.mFinishCallback.onClientFinished(client1, true /* success */);
-        client1.mFinishCallback.onClientFinished(client1, true /* success */);
+        client1.mCallback.onClientFinished(client1, true /* success */);
+        client1.mCallback.onClientFinished(client1, true /* success */);
     }
 
     private static class TestClientMonitor extends ClientMonitor<Object> {
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 8fc2287..32afe82 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -149,6 +149,7 @@
     public static final String NOT_PROFILE_OWNER_MSG = "does not own the profile";
     public static final String NOT_ORG_OWNED_PROFILE_OWNER_MSG =
             "not the profile owner on organization-owned device";
+    public static final String INVALID_CALLING_IDENTITY_MSG = "Calling identity is not authorized";
     public static final String ONGOING_CALL_MSG = "ongoing call on the device";
 
     // TODO replace all instances of this with explicit {@link #mServiceContext}.
@@ -2404,13 +2405,13 @@
         // Set admin1 as DA.
         dpm.setActiveAdmin(admin1, false);
         assertTrue(dpm.isAdminActive(admin1));
-        assertExpectException(SecurityException.class, /* messageRegex= */ NOT_DEVICE_OWNER_MSG,
-                () -> dpm.reboot(admin1));
+        assertExpectException(SecurityException.class, /* messageRegex= */
+                INVALID_CALLING_IDENTITY_MSG, () -> dpm.reboot(admin1));
 
         // Set admin1 as PO.
         assertTrue(dpm.setProfileOwner(admin1, null, UserHandle.USER_SYSTEM));
-        assertExpectException(SecurityException.class, /* messageRegex= */ NOT_DEVICE_OWNER_MSG,
-                () -> dpm.reboot(admin1));
+        assertExpectException(SecurityException.class, /* messageRegex= */
+                INVALID_CALLING_IDENTITY_MSG, () -> dpm.reboot(admin1));
 
         // Remove PO and add DO.
         dpm.clearProfileOwner(admin1);
@@ -2623,7 +2624,7 @@
                         UserHandle.myUserId(), UserManager.RESTRICTION_SOURCE_DEVICE_OWNER))
         ).when(getServices().userManager).getUserRestrictionSources(
                 eq(UserManager.DISALLOW_ADJUST_VOLUME),
-                eq(UserHandle.getUserHandleForUid(UserHandle.myUserId())));
+                eq(UserHandle.of(UserHandle.myUserId())));
         intent = dpm.createAdminSupportIntent(UserManager.DISALLOW_ADJUST_VOLUME);
         assertNotNull(intent);
         assertEquals(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS, intent.getAction());
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
index e4c9cc3..1d914ec 100644
--- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
@@ -88,36 +88,36 @@
     public void testVoiceImes() throws Exception {
         // locale: en_US
         assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_EN_US,
-                "DummyDefaultEnKeyboardIme", "DummyDefaultAutoVoiceIme");
+                "FakeDefaultEnKeyboardIme", "FakeDefaultAutoVoiceIme");
         assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_EN_US,
-                "DummyDefaultEnKeyboardIme", "DummyNonDefaultAutoVoiceIme0",
-                "DummyNonDefaultAutoVoiceIme1");
+                "FakeDefaultEnKeyboardIme", "FakeNonDefaultAutoVoiceIme0",
+                "FakeNonDefaultAutoVoiceIme1");
         assertDefaultEnabledMinimumImes(getImesWithDefaultVoiceIme(), LOCALE_EN_US,
-                "DummyDefaultEnKeyboardIme");
+                "FakeDefaultEnKeyboardIme");
         assertDefaultEnabledMinimumImes(getImesWithoutDefaultVoiceIme(), LOCALE_EN_US,
-                "DummyDefaultEnKeyboardIme");
+                "FakeDefaultEnKeyboardIme");
 
         // locale: en_GB
         assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_EN_GB,
-                "DummyDefaultEnKeyboardIme", "DummyDefaultAutoVoiceIme");
+                "FakeDefaultEnKeyboardIme", "FakeDefaultAutoVoiceIme");
         assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_EN_GB,
-                "DummyDefaultEnKeyboardIme", "DummyNonDefaultAutoVoiceIme0",
-                "DummyNonDefaultAutoVoiceIme1");
+                "FakeDefaultEnKeyboardIme", "FakeNonDefaultAutoVoiceIme0",
+                "FakeNonDefaultAutoVoiceIme1");
         assertDefaultEnabledMinimumImes(getImesWithDefaultVoiceIme(), LOCALE_EN_GB,
-                "DummyDefaultEnKeyboardIme");
+                "FakeDefaultEnKeyboardIme");
         assertDefaultEnabledMinimumImes(getImesWithoutDefaultVoiceIme(), LOCALE_EN_GB,
-                "DummyDefaultEnKeyboardIme");
+                "FakeDefaultEnKeyboardIme");
 
         // locale: ja_JP
         assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_JA_JP,
-                "DummyDefaultEnKeyboardIme", "DummyDefaultAutoVoiceIme");
+                "FakeDefaultEnKeyboardIme", "FakeDefaultAutoVoiceIme");
         assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_JA_JP,
-                "DummyDefaultEnKeyboardIme", "DummyNonDefaultAutoVoiceIme0",
-                "DummyNonDefaultAutoVoiceIme1");
+                "FakeDefaultEnKeyboardIme", "FakeNonDefaultAutoVoiceIme0",
+                "FakeNonDefaultAutoVoiceIme1");
         assertDefaultEnabledMinimumImes(getImesWithDefaultVoiceIme(), LOCALE_JA_JP,
-                "DummyDefaultEnKeyboardIme");
+                "FakeDefaultEnKeyboardIme");
         assertDefaultEnabledMinimumImes(getImesWithoutDefaultVoiceIme(), LOCALE_JA_JP,
-                "DummyDefaultEnKeyboardIme");
+                "FakeDefaultEnKeyboardIme");
     }
 
     @Test
@@ -189,67 +189,67 @@
 
     @Test
     public void testGetImplicitlyApplicableSubtypesLocked() throws Exception {
-        final InputMethodSubtype nonAutoEnUS = createDummyInputMethodSubtype("en_US",
+        final InputMethodSubtype nonAutoEnUS = createFakeInputMethodSubtype("en_US",
                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
                 IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoEnGB = createDummyInputMethodSubtype("en_GB",
+        final InputMethodSubtype nonAutoEnGB = createFakeInputMethodSubtype("en_GB",
                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
                 IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoEnIN = createDummyInputMethodSubtype("en_IN",
+        final InputMethodSubtype nonAutoEnIN = createFakeInputMethodSubtype("en_IN",
                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
                 IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoFrCA = createDummyInputMethodSubtype("fr_CA",
+        final InputMethodSubtype nonAutoFrCA = createFakeInputMethodSubtype("fr_CA",
                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
                 IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoFr = createDummyInputMethodSubtype("fr_CA",
+        final InputMethodSubtype nonAutoFr = createFakeInputMethodSubtype("fr_CA",
                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
                 IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoFil = createDummyInputMethodSubtype("fil",
+        final InputMethodSubtype nonAutoFil = createFakeInputMethodSubtype("fil",
                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
                 IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoIn = createDummyInputMethodSubtype("in",
+        final InputMethodSubtype nonAutoIn = createFakeInputMethodSubtype("in",
                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
                 IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoId = createDummyInputMethodSubtype("id",
+        final InputMethodSubtype nonAutoId = createFakeInputMethodSubtype("id",
                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
                 IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype autoSubtype = createDummyInputMethodSubtype("auto",
+        final InputMethodSubtype autoSubtype = createFakeInputMethodSubtype("auto",
                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
                 IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoJa = createDummyInputMethodSubtype("ja",
+        final InputMethodSubtype nonAutoJa = createFakeInputMethodSubtype("ja",
                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
                 !IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoHi = createDummyInputMethodSubtype("hi",
+        final InputMethodSubtype nonAutoHi = createFakeInputMethodSubtype("hi",
                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
                 !IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoSrCyrl = createDummyInputMethodSubtype("sr",
+        final InputMethodSubtype nonAutoSrCyrl = createFakeInputMethodSubtype("sr",
                 "sr-Cyrl", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
                 !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
                 !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoSrLatn = createDummyInputMethodSubtype("sr_ZZ",
+        final InputMethodSubtype nonAutoSrLatn = createFakeInputMethodSubtype("sr_ZZ",
                 "sr-Latn", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
                 !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, IS_ASCII_CAPABLE,
                 !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoHandwritingEn = createDummyInputMethodSubtype("en",
+        final InputMethodSubtype nonAutoHandwritingEn = createFakeInputMethodSubtype("en",
                 SUBTYPE_MODE_HANDWRITING, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
                 !IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoHandwritingFr = createDummyInputMethodSubtype("fr",
+        final InputMethodSubtype nonAutoHandwritingFr = createFakeInputMethodSubtype("fr",
                 SUBTYPE_MODE_HANDWRITING, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
                 !IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoHandwritingSrCyrl = createDummyInputMethodSubtype("sr",
+        final InputMethodSubtype nonAutoHandwritingSrCyrl = createFakeInputMethodSubtype("sr",
                 "sr-Cyrl", SUBTYPE_MODE_HANDWRITING, !IS_AUX,
                 !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
                 !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoHandwritingSrLatn = createDummyInputMethodSubtype("sr_ZZ",
+        final InputMethodSubtype nonAutoHandwritingSrLatn = createFakeInputMethodSubtype("sr_ZZ",
                 "sr-Latn", SUBTYPE_MODE_HANDWRITING, !IS_AUX,
                 !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
                 !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
         final InputMethodSubtype nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype =
-                createDummyInputMethodSubtype("zz", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
+                createFakeInputMethodSubtype("zz", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
                         !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
                         IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
         final InputMethodSubtype nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2 =
-                createDummyInputMethodSubtype("zz", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
+                createFakeInputMethodSubtype("zz", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
                         !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
                         IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
 
@@ -266,9 +266,9 @@
             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2);
             subtypes.add(nonAutoHandwritingEn);
             subtypes.add(nonAutoHandwritingFr);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
@@ -290,9 +290,9 @@
             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2);
             subtypes.add(nonAutoHandwritingEn);
             subtypes.add(nonAutoHandwritingFr);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
@@ -314,9 +314,9 @@
             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
             subtypes.add(nonAutoHandwritingEn);
             subtypes.add(nonAutoHandwritingFr);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
@@ -339,9 +339,9 @@
             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2);
             subtypes.add(nonAutoHandwritingEn);
             subtypes.add(nonAutoHandwritingFr);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
@@ -360,9 +360,9 @@
             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2);
             subtypes.add(nonAutoHandwritingEn);
             subtypes.add(nonAutoHandwritingFr);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
@@ -382,9 +382,9 @@
             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2);
             subtypes.add(nonAutoHandwritingEn);
             subtypes.add(nonAutoHandwritingFr);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
@@ -404,9 +404,9 @@
             subtypes.add(nonAutoHandwritingEn);
             subtypes.add(nonAutoHandwritingFr);
             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
@@ -421,9 +421,9 @@
             subtypes.add(nonAutoHandwritingEn);
             subtypes.add(nonAutoHandwritingFr);
             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
@@ -438,9 +438,9 @@
             subtypes.add(nonAutoEnUS);
             subtypes.add(nonAutoHi);
             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
@@ -460,9 +460,9 @@
             subtypes.add(nonAutoHandwritingFr);
             subtypes.add(nonAutoHandwritingSrCyrl);
             subtypes.add(nonAutoHandwritingSrLatn);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
@@ -480,9 +480,9 @@
             subtypes.add(nonAutoHandwritingFr);
             subtypes.add(nonAutoHandwritingSrCyrl);
             subtypes.add(nonAutoHandwritingSrLatn);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
@@ -506,9 +506,9 @@
             subtypes.add(nonAutoHandwritingFr);
             subtypes.add(nonAutoHandwritingSrCyrl);
             subtypes.add(nonAutoHandwritingSrLatn);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
@@ -533,9 +533,9 @@
             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
             subtypes.add(nonAutoEnUS);
             subtypes.add(nonAutoFil);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
@@ -551,9 +551,9 @@
             subtypes.add(nonAutoJa);
             subtypes.add(nonAutoEnUS);
             subtypes.add(nonAutoFil);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
@@ -567,9 +567,9 @@
             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
             subtypes.add(nonAutoIn);
             subtypes.add(nonAutoEnUS);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
@@ -581,9 +581,9 @@
             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
             subtypes.add(nonAutoIn);
             subtypes.add(nonAutoEnUS);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
@@ -595,9 +595,9 @@
             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
             subtypes.add(nonAutoId);
             subtypes.add(nonAutoEnUS);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
@@ -609,9 +609,9 @@
             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
             subtypes.add(nonAutoId);
             subtypes.add(nonAutoEnUS);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
@@ -631,9 +631,9 @@
             subtypes.add(nonAutoFil);
             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
@@ -649,22 +649,22 @@
 
     @Test
     public void testContainsSubtypeOf() throws Exception {
-        final InputMethodSubtype nonAutoEnUS = createDummyInputMethodSubtype("en_US",
+        final InputMethodSubtype nonAutoEnUS = createFakeInputMethodSubtype("en_US",
                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
                 IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoEnGB = createDummyInputMethodSubtype("en_GB",
+        final InputMethodSubtype nonAutoEnGB = createFakeInputMethodSubtype("en_GB",
                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
                 IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoFil = createDummyInputMethodSubtype("fil",
+        final InputMethodSubtype nonAutoFil = createFakeInputMethodSubtype("fil",
                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
                 IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoFilPH = createDummyInputMethodSubtype("fil_PH",
+        final InputMethodSubtype nonAutoFilPH = createFakeInputMethodSubtype("fil_PH",
                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
                 IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoIn = createDummyInputMethodSubtype("in",
+        final InputMethodSubtype nonAutoIn = createFakeInputMethodSubtype("in",
                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
                 IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoId = createDummyInputMethodSubtype("id",
+        final InputMethodSubtype nonAutoId = createFakeInputMethodSubtype("id",
                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
                 IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
 
@@ -673,9 +673,9 @@
         {
             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
             subtypes.add(nonAutoEnUS);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
 
             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_EN, !CHECK_COUNTRY,
@@ -705,9 +705,9 @@
         {
             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
             subtypes.add(nonAutoFil);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FIL, !CHECK_COUNTRY,
                     SUBTYPE_MODE_KEYBOARD));
@@ -732,9 +732,9 @@
         {
             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
             subtypes.add(nonAutoFilPH);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FIL, !CHECK_COUNTRY,
                     SUBTYPE_MODE_KEYBOARD));
@@ -760,9 +760,9 @@
             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
             subtypes.add(nonAutoIn);
             subtypes.add(nonAutoEnUS);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_IN, !CHECK_COUNTRY,
                     SUBTYPE_MODE_KEYBOARD));
@@ -779,9 +779,9 @@
             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
             subtypes.add(nonAutoId);
             subtypes.add(nonAutoEnUS);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_IN, !CHECK_COUNTRY,
                     SUBTYPE_MODE_KEYBOARD));
@@ -866,7 +866,7 @@
         assertEquals(expected.hashCode(), actual.hashCode());
     }
 
-    private static InputMethodInfo createDummyInputMethodInfo(String packageName, String name,
+    private static InputMethodInfo createFakeInputMethodInfo(String packageName, String name,
             CharSequence label, boolean isAuxIme, boolean isDefault,
             List<InputMethodSubtype> subtypes) {
         final ResolveInfo ri = new ResolveInfo();
@@ -885,15 +885,15 @@
         return new InputMethodInfo(ri, isAuxIme, "", subtypes, 1, isDefault);
     }
 
-    private static InputMethodSubtype createDummyInputMethodSubtype(String locale, String mode,
+    private static InputMethodSubtype createFakeInputMethodSubtype(String locale, String mode,
             boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype,
             boolean isAsciiCapable, boolean isEnabledWhenDefaultIsNotAsciiCapable) {
-        return createDummyInputMethodSubtype(locale, null /* languageTag */, mode, isAuxiliary,
+        return createFakeInputMethodSubtype(locale, null /* languageTag */, mode, isAuxiliary,
                 overridesImplicitlyEnabledSubtype, isAsciiCapable,
                 isEnabledWhenDefaultIsNotAsciiCapable);
     }
 
-    private static InputMethodSubtype createDummyInputMethodSubtype(String locale,
+    private static InputMethodSubtype createFakeInputMethodSubtype(String locale,
             String languageTag, String mode, boolean isAuxiliary,
             boolean overridesImplicitlyEnabledSubtype, boolean isAsciiCapable,
             boolean isEnabledWhenDefaultIsNotAsciiCapable) {
@@ -920,14 +920,14 @@
         ArrayList<InputMethodInfo> preinstalledImes = new ArrayList<>();
         {
             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(createDummyInputMethodSubtype("auto", SUBTYPE_MODE_VOICE, IS_AUX,
+            subtypes.add(createFakeInputMethodSubtype("auto", SUBTYPE_MODE_VOICE, IS_AUX,
                     IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            subtypes.add(createDummyInputMethodSubtype("en_US", SUBTYPE_MODE_VOICE, IS_AUX,
+            subtypes.add(createFakeInputMethodSubtype("en_US", SUBTYPE_MODE_VOICE, IS_AUX,
                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            preinstalledImes.add(createDummyInputMethodInfo("DummyDefaultAutoVoiceIme",
-                    "dummy.voice0", "DummyVoice0", IS_AUX, IS_DEFAULT, subtypes));
+            preinstalledImes.add(createFakeInputMethodInfo("FakeDefaultAutoVoiceIme",
+                    "fake.voice0", "FakeVoice0", IS_AUX, IS_DEFAULT, subtypes));
         }
         preinstalledImes.addAll(getImesWithoutDefaultVoiceIme());
         return preinstalledImes;
@@ -937,41 +937,41 @@
         ArrayList<InputMethodInfo> preinstalledImes = new ArrayList<>();
         {
             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(createDummyInputMethodSubtype("auto", SUBTYPE_MODE_VOICE, IS_AUX,
+            subtypes.add(createFakeInputMethodSubtype("auto", SUBTYPE_MODE_VOICE, IS_AUX,
                     IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            subtypes.add(createDummyInputMethodSubtype("en_US", SUBTYPE_MODE_VOICE, IS_AUX,
+            subtypes.add(createFakeInputMethodSubtype("en_US", SUBTYPE_MODE_VOICE, IS_AUX,
                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            preinstalledImes.add(createDummyInputMethodInfo("DummyNonDefaultAutoVoiceIme0",
-                    "dummy.voice1", "DummyVoice1", IS_AUX, !IS_DEFAULT, subtypes));
+            preinstalledImes.add(createFakeInputMethodInfo("FakeNonDefaultAutoVoiceIme0",
+                    "fake.voice1", "FakeVoice1", IS_AUX, !IS_DEFAULT, subtypes));
         }
         {
             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(createDummyInputMethodSubtype("auto", SUBTYPE_MODE_VOICE, IS_AUX,
+            subtypes.add(createFakeInputMethodSubtype("auto", SUBTYPE_MODE_VOICE, IS_AUX,
                     IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            subtypes.add(createDummyInputMethodSubtype("en_US", SUBTYPE_MODE_VOICE, IS_AUX,
+            subtypes.add(createFakeInputMethodSubtype("en_US", SUBTYPE_MODE_VOICE, IS_AUX,
                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            preinstalledImes.add(createDummyInputMethodInfo("DummyNonDefaultAutoVoiceIme1",
-                    "dummy.voice2", "DummyVoice2", IS_AUX, !IS_DEFAULT, subtypes));
+            preinstalledImes.add(createFakeInputMethodInfo("FakeNonDefaultAutoVoiceIme1",
+                    "fake.voice2", "FakeVoice2", IS_AUX, !IS_DEFAULT, subtypes));
         }
         {
             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(createDummyInputMethodSubtype("en_US", SUBTYPE_MODE_VOICE, IS_AUX,
+            subtypes.add(createFakeInputMethodSubtype("en_US", SUBTYPE_MODE_VOICE, IS_AUX,
                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            preinstalledImes.add(createDummyInputMethodInfo("DummyNonDefaultVoiceIme2",
-                    "dummy.voice3", "DummyVoice3", IS_AUX, !IS_DEFAULT, subtypes));
+            preinstalledImes.add(createFakeInputMethodInfo("FakeNonDefaultVoiceIme2",
+                    "fake.voice3", "FakeVoice3", IS_AUX, !IS_DEFAULT, subtypes));
         }
         {
             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(createDummyInputMethodSubtype("en_US", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
+            subtypes.add(createFakeInputMethodSubtype("en_US", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, IS_ASCII_CAPABLE,
                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            preinstalledImes.add(createDummyInputMethodInfo("DummyDefaultEnKeyboardIme",
-                    "dummy.keyboard0", "DummyKeyboard0", !IS_AUX, IS_DEFAULT, subtypes));
+            preinstalledImes.add(createFakeInputMethodInfo("FakeDefaultEnKeyboardIme",
+                    "fake.keyboard0", "FakeKeyboard0", !IS_AUX, IS_DEFAULT, subtypes));
         }
         return preinstalledImes;
     }
@@ -991,91 +991,91 @@
     private static ArrayList<InputMethodInfo> getSamplePreinstalledImes(final String localeString) {
         ArrayList<InputMethodInfo> preinstalledImes = new ArrayList<>();
 
-        // a dummy Voice IME
+        // a fake Voice IME
         {
             final boolean isDefaultIme = false;
             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(createDummyInputMethodSubtype("", SUBTYPE_MODE_VOICE, IS_AUX,
+            subtypes.add(createFakeInputMethodSubtype("", SUBTYPE_MODE_VOICE, IS_AUX,
                     IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            preinstalledImes.add(createDummyInputMethodInfo("com.android.apps.inputmethod.voice",
-                    "com.android.inputmethod.voice", "DummyVoiceIme", IS_AUX, isDefaultIme,
+            preinstalledImes.add(createFakeInputMethodInfo("com.android.apps.inputmethod.voice",
+                    "com.android.inputmethod.voice", "FakeVoiceIme", IS_AUX, isDefaultIme,
                     subtypes));
         }
-        // a dummy Hindi IME
+        // a fake Hindi IME
         {
             final boolean isDefaultIme = contains(new String[]{ "hi", "en-rIN" }, localeString);
             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
             // TODO: This subtype should be marked as IS_ASCII_CAPABLE
-            subtypes.add(createDummyInputMethodSubtype("en_IN", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
+            subtypes.add(createFakeInputMethodSubtype("en_IN", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            subtypes.add(createDummyInputMethodSubtype("hi", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
+            subtypes.add(createFakeInputMethodSubtype("hi", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            preinstalledImes.add(createDummyInputMethodInfo("com.android.apps.inputmethod.hindi",
-                    "com.android.inputmethod.hindi", "DummyHindiIme", !IS_AUX, isDefaultIme,
+            preinstalledImes.add(createFakeInputMethodInfo("com.android.apps.inputmethod.hindi",
+                    "com.android.inputmethod.hindi", "FakeHindiIme", !IS_AUX, isDefaultIme,
                     subtypes));
         }
 
-        // a dummy Pinyin IME
+        // a fake Pinyin IME
         {
             final boolean isDefaultIme = contains(new String[]{ "zh-rCN" }, localeString);
             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(createDummyInputMethodSubtype("zh_CN", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
+            subtypes.add(createFakeInputMethodSubtype("zh_CN", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            preinstalledImes.add(createDummyInputMethodInfo("com.android.apps.inputmethod.pinyin",
-                    "com.android.apps.inputmethod.pinyin", "DummyPinyinIme", !IS_AUX, isDefaultIme,
+            preinstalledImes.add(createFakeInputMethodInfo("com.android.apps.inputmethod.pinyin",
+                    "com.android.apps.inputmethod.pinyin", "FakePinyinIme", !IS_AUX, isDefaultIme,
                     subtypes));
         }
 
-        // a dummy Korean IME
+        // a fake Korean IME
         {
             final boolean isDefaultIme = contains(new String[]{ "ko" }, localeString);
             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(createDummyInputMethodSubtype("ko", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
+            subtypes.add(createFakeInputMethodSubtype("ko", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            preinstalledImes.add(createDummyInputMethodInfo("com.android.apps.inputmethod.korean",
-                    "com.android.apps.inputmethod.korean", "DummyKoreanIme", !IS_AUX, isDefaultIme,
+            preinstalledImes.add(createFakeInputMethodInfo("com.android.apps.inputmethod.korean",
+                    "com.android.apps.inputmethod.korean", "FakeKoreanIme", !IS_AUX, isDefaultIme,
                     subtypes));
         }
 
-        // a dummy Latin IME
+        // a fake Latin IME
         {
             final boolean isDefaultIme = contains(
                     new String[]{ "en-rUS", "en-rGB", "en-rIN", "en", "hi" }, localeString);
             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(createDummyInputMethodSubtype("en_US", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
+            subtypes.add(createFakeInputMethodSubtype("en_US", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, IS_ASCII_CAPABLE,
                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            subtypes.add(createDummyInputMethodSubtype("en_GB", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
+            subtypes.add(createFakeInputMethodSubtype("en_GB", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, IS_ASCII_CAPABLE,
                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            subtypes.add(createDummyInputMethodSubtype("en_IN", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
+            subtypes.add(createFakeInputMethodSubtype("en_IN", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, IS_ASCII_CAPABLE,
                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            subtypes.add(createDummyInputMethodSubtype("hi", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
+            subtypes.add(createFakeInputMethodSubtype("hi", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, IS_ASCII_CAPABLE,
                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            preinstalledImes.add(createDummyInputMethodInfo("com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, isDefaultIme,
+            preinstalledImes.add(createFakeInputMethodInfo("com.android.apps.inputmethod.latin",
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, isDefaultIme,
                     subtypes));
         }
 
-        // a dummy Japanese IME
+        // a fake Japanese IME
         {
             final boolean isDefaultIme = contains(new String[]{ "ja", "ja-rJP" }, localeString);
             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(createDummyInputMethodSubtype("ja", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
+            subtypes.add(createFakeInputMethodSubtype("ja", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            subtypes.add(createDummyInputMethodSubtype("emoji", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
+            subtypes.add(createFakeInputMethodSubtype("emoji", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            preinstalledImes.add(createDummyInputMethodInfo("com.android.apps.inputmethod.japanese",
-                    "com.android.apps.inputmethod.japanese", "DummyJapaneseIme", !IS_AUX,
+            preinstalledImes.add(createFakeInputMethodInfo("com.android.apps.inputmethod.japanese",
+                    "com.android.apps.inputmethod.japanese", "FakeJapaneseIme", !IS_AUX,
                     isDefaultIme, subtypes));
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
index 1b5c56a..d44d37e 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -19,7 +19,6 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
@@ -169,7 +168,7 @@
 
         final ArrayList<UserInfo> allUsers = new ArrayList<>(mPrimaryUserProfiles);
         allUsers.add(SECONDARY_USER_INFO);
-        when(mUserManager.getUsers(anyBoolean())).thenReturn(allUsers);
+        when(mUserManager.getUsers()).thenReturn(allUsers);
 
         when(mActivityManager.unlockUser(anyInt(), any(), any(), any())).thenAnswer(
                 new Answer<Boolean>() {
diff --git a/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java
index 083df28..aaa74dd 100644
--- a/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java
@@ -284,28 +284,29 @@
     @Test
     public void testNotify() throws RemoteException {
         int status = Temperature.THROTTLING_SEVERE;
+        // Should only notify event not status
         Temperature newBattery = new Temperature(50, Temperature.TYPE_BATTERY, "batt", status);
         mFakeHal.mCallback.onValues(newBattery);
         verify(mEventListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
                 .times(1)).notifyThrottling(newBattery);
         verify(mStatusListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
-                .times(1)).onStatusChange(status);
+                .times(0)).onStatusChange(anyInt());
         verify(mEventListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
                 .times(0)).notifyThrottling(newBattery);
         verify(mStatusListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
-                .times(1)).onStatusChange(status);
+                .times(0)).onStatusChange(anyInt());
         resetListenerMock();
-        // Should only notify event not status
+        // Notify both event and status
         Temperature newSkin = new Temperature(50, Temperature.TYPE_SKIN, "skin1", status);
         mFakeHal.mCallback.onValues(newSkin);
         verify(mEventListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
                 .times(1)).notifyThrottling(newSkin);
         verify(mStatusListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
-                .times(0)).onStatusChange(anyInt());
+                .times(1)).onStatusChange(status);
         verify(mEventListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
                 .times(1)).notifyThrottling(newSkin);
         verify(mStatusListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
-                .times(0)).onStatusChange(anyInt());
+                .times(1)).onStatusChange(status);
         resetListenerMock();
         // Back to None, should only notify event not status
         status = Temperature.THROTTLING_NONE;
@@ -345,10 +346,13 @@
 
     @Test
     public void testGetCurrentStatus() throws RemoteException {
-        int status = Temperature.THROTTLING_EMERGENCY;
+        int status = Temperature.THROTTLING_SEVERE;
         Temperature newSkin = new Temperature(100, Temperature.TYPE_SKIN, "skin1", status);
         mFakeHal.mCallback.onValues(newSkin);
         assertEquals(status, mService.mService.getCurrentThermalStatus());
+        int battStatus = Temperature.THROTTLING_EMERGENCY;
+        Temperature newBattery = new Temperature(60, Temperature.TYPE_BATTERY, "batt", battStatus);
+        assertEquals(status, mService.mService.getCurrentThermalStatus());
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
index f934323..7b07102 100644
--- a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
@@ -74,7 +74,7 @@
             mReclaimed = true;
         }
 
-        public boolean isRelaimed() {
+        public boolean isReclaimed() {
             return mReclaimed;
         }
     }
@@ -387,13 +387,13 @@
                 new TunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
         assertThat(mTunerResourceManagerService
                 .requestFrontendInternal(request, frontendHandle)).isFalse();
-        assertThat(listener.isRelaimed()).isFalse();
+        assertThat(listener.isReclaimed()).isFalse();
 
         request =
                 new TunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBS);
         assertThat(mTunerResourceManagerService
                 .requestFrontendInternal(request, frontendHandle)).isFalse();
-        assertThat(listener.isRelaimed()).isFalse();
+        assertThat(listener.isReclaimed()).isFalse();
     }
 
     @Test
@@ -452,7 +452,7 @@
                 .getOwnerClientId()).isEqualTo(clientId1[0]);
         assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].getId())
                 .getOwnerClientId()).isEqualTo(clientId1[0]);
-        assertThat(listener.isRelaimed()).isTrue();
+        assertThat(listener.isReclaimed()).isTrue();
     }
 
     @Test
@@ -486,7 +486,7 @@
 
         // Release frontend
         mTunerResourceManagerService.releaseFrontendInternal(mTunerResourceManagerService
-                .getFrontendResource(frontendId));
+                .getFrontendResource(frontendId), clientId[0]);
         assertThat(mTunerResourceManagerService
                 .getFrontendResource(frontendId).isInUse()).isFalse();
         assertThat(mTunerResourceManagerService
@@ -548,7 +548,7 @@
         assertThat(mTunerResourceManagerService.getCasResource(1)
                 .getOwnerClientIds()).isEqualTo(new HashSet<Integer>(Arrays.asList(clientId1[0])));
         assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isFalse();
-        assertThat(listener.isRelaimed()).isTrue();
+        assertThat(listener.isReclaimed()).isTrue();
     }
 
     @Test
@@ -633,7 +633,7 @@
                 .isInUse()).isTrue();
         assertThat(mTunerResourceManagerService.getLnbResource(lnbIds[0])
                 .getOwnerClientId()).isEqualTo(clientId1[0]);
-        assertThat(listener.isRelaimed()).isTrue();
+        assertThat(listener.isReclaimed()).isTrue();
         assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0])
                 .getInUseLnbIds().size()).isEqualTo(0);
     }
@@ -761,4 +761,293 @@
                 backgroundRecordProfile)).isEqualTo(
                         (backgroundPlaybackPriority > backgroundRecordPriority));
     }
+
+    @Test
+    public void shareFrontendTest_FrontendWithExclusiveGroupReadyToShare() {
+        /**** Register Clients and Set Priority ****/
+
+        // Int array to save the returned client ids
+        int[] ownerClientId0 = new int[1];
+        int[] ownerClientId1 = new int[1];
+        int[] shareClientId0 = new int[1];
+        int[] shareClientId1 = new int[1];
+
+        // Predefined client profiles
+        ResourceClientProfile[] ownerProfiles = new ResourceClientProfile[2];
+        ResourceClientProfile[] shareProfiles = new ResourceClientProfile[2];
+        ownerProfiles[0] = new ResourceClientProfile(
+                "0" /*sessionId*/,
+                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE);
+        ownerProfiles[1] = new ResourceClientProfile(
+                "1" /*sessionId*/,
+                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE);
+        shareProfiles[0] = new ResourceClientProfile(
+                "2" /*sessionId*/,
+                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_RECORD);
+        shareProfiles[1] = new ResourceClientProfile(
+                "3" /*sessionId*/,
+                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_RECORD);
+
+        // Predefined client reclaim listeners
+        TestResourcesReclaimListener ownerListener0 = new TestResourcesReclaimListener();
+        TestResourcesReclaimListener shareListener0 = new TestResourcesReclaimListener();
+        TestResourcesReclaimListener ownerListener1 = new TestResourcesReclaimListener();
+        TestResourcesReclaimListener shareListener1 = new TestResourcesReclaimListener();
+        // Register clients and validate the returned client ids
+        mTunerResourceManagerService
+                .registerClientProfileInternal(ownerProfiles[0], ownerListener0, ownerClientId0);
+        mTunerResourceManagerService
+                .registerClientProfileInternal(shareProfiles[0], shareListener0, shareClientId0);
+        mTunerResourceManagerService
+                .registerClientProfileInternal(ownerProfiles[1], ownerListener1, ownerClientId1);
+        mTunerResourceManagerService
+                .registerClientProfileInternal(shareProfiles[1], shareListener1, shareClientId1);
+        assertThat(ownerClientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+        assertThat(shareClientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+        assertThat(ownerClientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+        assertThat(shareClientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+
+        mTunerResourceManagerService.updateClientPriorityInternal(
+                ownerClientId0[0],
+                100/*priority*/,
+                0/*niceValue*/);
+        mTunerResourceManagerService.updateClientPriorityInternal(
+                shareClientId0[0],
+                200/*priority*/,
+                0/*niceValue*/);
+        mTunerResourceManagerService.updateClientPriorityInternal(
+                ownerClientId1[0],
+                300/*priority*/,
+                0/*niceValue*/);
+        mTunerResourceManagerService.updateClientPriorityInternal(
+                shareClientId1[0],
+                400/*priority*/,
+                0/*niceValue*/);
+
+        /**** Init Frontend Resources ****/
+
+        // Predefined frontend info
+        TunerFrontendInfo[] infos = new TunerFrontendInfo[2];
+        infos[0] = new TunerFrontendInfo(
+                0 /*id*/,
+                FrontendSettings.TYPE_DVBT,
+                1 /*exclusiveGroupId*/);
+        infos[1] = new TunerFrontendInfo(
+                1 /*id*/,
+                FrontendSettings.TYPE_DVBS,
+                1 /*exclusiveGroupId*/);
+
+        /**** Init Lnb Resources ****/
+        int[] lnbIds = {1};
+        mTunerResourceManagerService.setLnbInfoListInternal(lnbIds);
+
+        // Update frontend list in TRM
+        mTunerResourceManagerService.setFrontendInfoListInternal(infos);
+
+        /**** Request Frontend ****/
+
+        // Predefined frontend request and array to save returned frontend handle
+        int[] frontendHandle = new int[1];
+        TunerFrontendRequest request = new TunerFrontendRequest(
+                ownerClientId0[0] /*clientId*/,
+                FrontendSettings.TYPE_DVBT);
+
+        // Request call and validate granted resource and internal mapping
+        assertThat(mTunerResourceManagerService
+                .requestFrontendInternal(request, frontendHandle))
+                .isTrue();
+        assertThat(mTunerResourceManagerService
+                .getResourceIdFromHandle(frontendHandle[0]))
+                .isEqualTo(infos[0].getId());
+        assertThat(mTunerResourceManagerService
+                .getClientProfile(ownerClientId0[0])
+                .getInUseFrontendIds())
+                .isEqualTo(new HashSet<Integer>(Arrays.asList(
+                        infos[0].getId(),
+                        infos[1].getId())));
+
+        /**** Share Frontend ****/
+
+        // Share frontend call and validate the internal mapping
+        mTunerResourceManagerService.shareFrontendInternal(
+                shareClientId0[0]/*selfClientId*/,
+                ownerClientId0[0]/*targetClientId*/);
+        mTunerResourceManagerService.shareFrontendInternal(
+                shareClientId1[0]/*selfClientId*/,
+                ownerClientId0[0]/*targetClientId*/);
+        // Verify fe in use status
+        assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].getId())
+                .isInUse()).isTrue();
+        assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].getId())
+                .isInUse()).isTrue();
+        // Verify fe owner status
+        assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].getId())
+                .getOwnerClientId()).isEqualTo(ownerClientId0[0]);
+        assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].getId())
+                .getOwnerClientId()).isEqualTo(ownerClientId0[0]);
+        // Verify share fe client status in the primary owner client
+        assertThat(mTunerResourceManagerService.getClientProfile(ownerClientId0[0])
+                .getShareFeClientIds())
+                .isEqualTo(new HashSet<Integer>(Arrays.asList(
+                        shareClientId0[0],
+                        shareClientId1[0])));
+        // Verify in use frontend list in all the primary owner and share owner clients
+        assertThat(mTunerResourceManagerService
+                .getClientProfile(ownerClientId0[0])
+                .getInUseFrontendIds())
+                .isEqualTo(new HashSet<Integer>(Arrays.asList(
+                        infos[0].getId(),
+                        infos[1].getId())));
+        assertThat(mTunerResourceManagerService
+                .getClientProfile(shareClientId0[0])
+                .getInUseFrontendIds())
+                .isEqualTo(new HashSet<Integer>(Arrays.asList(
+                        infos[0].getId(),
+                        infos[1].getId())));
+        assertThat(mTunerResourceManagerService
+                .getClientProfile(shareClientId1[0])
+                .getInUseFrontendIds())
+                .isEqualTo(new HashSet<Integer>(Arrays.asList(
+                        infos[0].getId(),
+                        infos[1].getId())));
+
+        /**** Remove Frontend Share Owner ****/
+
+        // Unregister the second share fe client
+        mTunerResourceManagerService.unregisterClientProfileInternal(shareClientId1[0]);
+
+        // Validate the internal mapping
+        assertThat(mTunerResourceManagerService.getClientProfile(ownerClientId0[0])
+                .getShareFeClientIds())
+                .isEqualTo(new HashSet<Integer>(Arrays.asList(
+                        shareClientId0[0])));
+        assertThat(mTunerResourceManagerService
+                .getClientProfile(ownerClientId0[0])
+                .getInUseFrontendIds())
+                .isEqualTo(new HashSet<Integer>(Arrays.asList(
+                        infos[0].getId(),
+                        infos[1].getId())));
+        assertThat(mTunerResourceManagerService
+                .getClientProfile(shareClientId0[0])
+                .getInUseFrontendIds())
+                .isEqualTo(new HashSet<Integer>(Arrays.asList(
+                        infos[0].getId(),
+                        infos[1].getId())));
+
+        /**** Request Shared Frontend with Higher Priority Client ****/
+
+        // Predefined second frontend request
+        request = new TunerFrontendRequest(
+                ownerClientId1[0] /*clientId*/,
+                FrontendSettings.TYPE_DVBT);
+
+        // Second request call
+        assertThat(mTunerResourceManagerService
+                .requestFrontendInternal(request, frontendHandle))
+                .isTrue();
+
+        // Validate granted resource and internal mapping
+        assertThat(mTunerResourceManagerService
+                .getResourceIdFromHandle(frontendHandle[0]))
+                .isEqualTo(infos[0].getId());
+        assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].getId())
+                .getOwnerClientId()).isEqualTo(ownerClientId1[0]);
+        assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].getId())
+                .getOwnerClientId()).isEqualTo(ownerClientId1[0]);
+        assertThat(mTunerResourceManagerService
+                .getClientProfile(ownerClientId1[0])
+                .getInUseFrontendIds())
+                .isEqualTo(new HashSet<Integer>(Arrays.asList(
+                        infos[0].getId(),
+                        infos[1].getId())));
+        assertThat(mTunerResourceManagerService
+                .getClientProfile(ownerClientId0[0])
+                .getInUseFrontendIds()
+                .isEmpty())
+                .isTrue();
+        assertThat(mTunerResourceManagerService
+                .getClientProfile(shareClientId0[0])
+                .getInUseFrontendIds()
+                .isEmpty())
+                .isTrue();
+        assertThat(mTunerResourceManagerService
+                .getClientProfile(ownerClientId0[0])
+                .getShareFeClientIds()
+                .isEmpty())
+                .isTrue();
+        assertThat(ownerListener0.isReclaimed()).isTrue();
+        assertThat(shareListener0.isReclaimed()).isTrue();
+
+        /**** Release Frontend Resource From Primary Owner ****/
+
+        // Reshare the frontend
+        mTunerResourceManagerService.shareFrontendInternal(
+                shareClientId0[0]/*selfClientId*/,
+                ownerClientId1[0]/*targetClientId*/);
+
+        // Release the frontend resource from the primary owner
+        mTunerResourceManagerService.releaseFrontendInternal(mTunerResourceManagerService
+                .getFrontendResource(infos[0].getId()), ownerClientId1[0]);
+
+        // Validate the internal mapping
+        assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].getId())
+                .isInUse()).isFalse();
+        assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].getId())
+                .isInUse()).isFalse();
+        // Verify client status
+        assertThat(mTunerResourceManagerService
+                .getClientProfile(ownerClientId1[0])
+                .getInUseFrontendIds()
+                .isEmpty())
+                .isTrue();
+        assertThat(mTunerResourceManagerService
+                .getClientProfile(shareClientId0[0])
+                .getInUseFrontendIds()
+                .isEmpty())
+                .isTrue();
+        assertThat(mTunerResourceManagerService
+                .getClientProfile(ownerClientId1[0])
+                .getShareFeClientIds()
+                .isEmpty())
+                .isTrue();
+
+        /**** Unregister Primary Owner when the Share owner owns an Lnb ****/
+
+        // Predefined Lnb request and handle array
+        TunerLnbRequest requestLnb = new TunerLnbRequest(shareClientId0[0]);
+        int[] lnbHandle = new int[1];
+
+        // Request for an Lnb
+        assertThat(mTunerResourceManagerService
+                .requestLnbInternal(requestLnb, lnbHandle))
+                .isTrue();
+
+        // Request and share the frontend resource again
+        assertThat(mTunerResourceManagerService
+                .requestFrontendInternal(request, frontendHandle))
+                .isTrue();
+        mTunerResourceManagerService.shareFrontendInternal(
+                shareClientId0[0]/*selfClientId*/,
+                ownerClientId1[0]/*targetClientId*/);
+
+        // Unregister the primary owner of the shared frontend
+        mTunerResourceManagerService.unregisterClientProfileInternal(ownerClientId1[0]);
+
+        // Validate the internal mapping
+        assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].getId())
+                .isInUse()).isFalse();
+        assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].getId())
+                .isInUse()).isFalse();
+        // Verify client status
+        assertThat(mTunerResourceManagerService
+                .getClientProfile(shareClientId0[0])
+                .getInUseFrontendIds()
+                .isEmpty())
+                .isTrue();
+        assertThat(mTunerResourceManagerService
+                .getClientProfile(shareClientId0[0])
+                .getInUseLnbIds())
+                .isEqualTo(new HashSet<Integer>(Arrays.asList(
+                        lnbIds[0])));
+    }
 }
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 4dec7a1..7c7b1a2 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -1383,7 +1383,7 @@
                 getStandbyBucket(mController, PACKAGE_1));
 
         mController.postReportSyncScheduled(PACKAGE_1, USER_ID, false);
-        mStateChangedLatch.await(100, TimeUnit.MILLISECONDS);
+        mStateChangedLatch.await(1000, TimeUnit.MILLISECONDS);
         assertEquals("Unexempted sync scheduled should bring the package out of the Never bucket",
                 STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
 
@@ -1391,7 +1391,7 @@
 
         mStateChangedLatch = new CountDownLatch(1);
         mController.postReportSyncScheduled(PACKAGE_1, USER_ID, false);
-        mStateChangedLatch.await(100, TimeUnit.MILLISECONDS);
+        mStateChangedLatch.await(1000, TimeUnit.MILLISECONDS);
         assertEquals("Unexempted sync scheduled should not elevate a non Never bucket",
                 STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
     }
@@ -1402,7 +1402,7 @@
         mInjector.mDeviceIdleMode = true;
         mStateChangedLatch = new CountDownLatch(1);
         mController.postReportSyncScheduled(PACKAGE_1, USER_ID, true);
-        mStateChangedLatch.await(100, TimeUnit.MILLISECONDS);
+        mStateChangedLatch.await(1000, TimeUnit.MILLISECONDS);
         assertEquals("Exempted sync scheduled in doze should set bucket to working set",
                 STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
 
@@ -1410,7 +1410,7 @@
         mInjector.mDeviceIdleMode = false;
         mStateChangedLatch = new CountDownLatch(1);
         mController.postReportSyncScheduled(PACKAGE_1, USER_ID, true);
-        mStateChangedLatch.await(100, TimeUnit.MILLISECONDS);
+        mStateChangedLatch.await(1000, TimeUnit.MILLISECONDS);
         assertEquals("Exempted sync scheduled while not in doze should set bucket to active",
                 STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
     }
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 5b2d738..9319bea 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -3886,7 +3886,7 @@
         mService.updateUriPermissions(recordB, recordA, mContext.getPackageName(),
                 USER_SYSTEM);
         verify(mUgmInternal, times(1)).revokeUriPermissionFromOwner(any(),
-                eq(message1.getDataUri()), anyInt(), anyInt());
+                eq(message1.getDataUri()), anyInt(), anyInt(), eq(null), eq(-1));
 
         // Update back means we grant access to first again
         reset(mUgm);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
index 46c3e22..eb78172 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -20,6 +20,7 @@
 import static android.app.ActivityManager.START_TASK_TO_FRONT;
 import static android.content.ComponentName.createRelative;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions;
@@ -176,7 +177,8 @@
     public void testOnActivityLaunchCancelled_hasDrawn() {
         onActivityLaunched(mTopActivity);
 
-        mTopActivity.mVisibleRequested = mTopActivity.mDrawn = true;
+        mTopActivity.mVisibleRequested = true;
+        doReturn(true).when(mTopActivity).isReportedDrawn();
 
         // Cannot time already-visible activities.
         notifyActivityLaunched(START_TASK_TO_FRONT, mTopActivity);
@@ -187,16 +189,14 @@
 
     @Test
     public void testOnActivityLaunchCancelled_finishedBeforeDrawn() {
-        mTopActivity.mVisibleRequested = mTopActivity.mDrawn = true;
+        mTopActivity.mVisibleRequested = true;
+        doReturn(true).when(mTopActivity).isReportedDrawn();
 
-        // Suppress resume when creating the record because we want to notify logger manually.
-        mSupervisor.beginDeferResume();
         // Create an activity with different process that meets process switch.
         final ActivityRecord noDrawnActivity = new ActivityBuilder(mAtm)
                 .setTask(mTopActivity.getTask())
                 .setProcessName("other")
                 .build();
-        mSupervisor.readyToResume();
 
         notifyActivityLaunching(noDrawnActivity.intent);
         notifyActivityLaunched(START_SUCCESS, noDrawnActivity);
@@ -294,7 +294,7 @@
     public void testOnActivityLaunchCancelledTrampoline() {
         onActivityLaunchedTrampoline();
 
-        mTopActivity.mDrawn = true;
+        doReturn(true).when(mTopActivity).isReportedDrawn();
 
         // Cannot time already-visible activities.
         notifyActivityLaunched(START_TASK_TO_FRONT, mTopActivity);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java
index 96b9700..27e2d13 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java
@@ -22,6 +22,7 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -38,13 +39,8 @@
 
 import android.app.WaitResult;
 import android.content.pm.ActivityInfo;
-import android.graphics.PixelFormat;
-import android.hardware.display.DisplayManager;
-import android.hardware.display.VirtualDisplay;
-import android.media.ImageReader;
 import android.platform.test.annotations.Presubmit;
 import android.view.Display;
-import android.view.DisplayInfo;
 
 import androidx.test.filters.MediumTest;
 
@@ -180,49 +176,28 @@
                 eq(true) /* focused */);
     }
 
+    /**
+     * Ensures that a trusted display can launch arbitrary activity and an untrusted display can't.
+     */
     @Test
-    /** Ensures that a trusted virtual display can launch arbitrary activities. */
-    public void testTrustedVirtualDisplayCanLaunchActivities() {
-        final DisplayContent newDisplay = addNewDisplayContentAt(DisplayContent.POSITION_TOP);
-        final Task stack = new StackBuilder(mRootWindowContainer)
-                .setDisplay(newDisplay).build();
-        final ActivityRecord unresizableActivity = stack.getTopNonFinishingActivity();
-        VirtualDisplay virtualDisplay = createVirtualDisplay(true);
-        final boolean allowed = mSupervisor.isCallerAllowedToLaunchOnDisplay(1234, 1234,
-                virtualDisplay.getDisplay().getDisplayId(), unresizableActivity.info);
+    public void testDisplayCanLaunchActivities() {
+        final Display display = mDisplayContent.mDisplay;
+        // An empty info without FLAG_ALLOW_EMBEDDED.
+        final ActivityInfo activityInfo = new ActivityInfo();
+        final int callingPid = 12345;
+        final int callingUid = 12345;
+        spyOn(display);
 
-        assertThat(allowed).isTrue();
-    }
+        doReturn(true).when(display).isTrusted();
+        final boolean allowedOnTrusted = mSupervisor.isCallerAllowedToLaunchOnDisplay(callingPid,
+                callingUid, display.getDisplayId(), activityInfo);
 
-    @Test
-    /** Ensures that an untrusted virtual display cannot launch arbitrary activities. */
-    public void testUntrustedVirtualDisplayCannotLaunchActivities() {
-        final DisplayContent newDisplay = addNewDisplayContentAt(DisplayContent.POSITION_TOP);
-        final Task stack = new StackBuilder(mRootWindowContainer)
-                .setDisplay(newDisplay).build();
-        final ActivityRecord unresizableActivity = stack.getTopNonFinishingActivity();
-        VirtualDisplay virtualDisplay = createVirtualDisplay(false);
-        final boolean allowed = mSupervisor.isCallerAllowedToLaunchOnDisplay(1234, 1234,
-                virtualDisplay.getDisplay().getDisplayId(), unresizableActivity.info);
+        assertThat(allowedOnTrusted).isTrue();
 
-        assertThat(allowed).isFalse();
-    }
+        doReturn(false).when(display).isTrusted();
+        final boolean allowedOnUntrusted = mSupervisor.isCallerAllowedToLaunchOnDisplay(callingPid,
+                callingUid, display.getDisplayId(), activityInfo);
 
-    private VirtualDisplay createVirtualDisplay(boolean trusted) {
-        final DisplayManager dm = mContext.getSystemService(DisplayManager.class);
-        final DisplayInfo displayInfo = new DisplayInfo();
-        final Display defaultDisplay = dm.getDisplay(Display.DEFAULT_DISPLAY);
-        defaultDisplay.getDisplayInfo(displayInfo);
-        int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
-        if (trusted) {
-            flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED;
-        }
-
-        final ImageReader imageReader = ImageReader.newInstance(
-                displayInfo.logicalWidth, displayInfo.logicalHeight, PixelFormat.RGBA_8888, 2);
-
-        return dm.createVirtualDisplay("virtualDisplay", displayInfo.logicalWidth,
-                displayInfo.logicalHeight,
-                displayInfo.logicalDensityDpi, imageReader.getSurface(), flags);
+        assertThat(allowedOnUntrusted).isFalse();
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java b/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java
index 8223024..578a43c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java
@@ -38,13 +38,13 @@
  */
 @SmallTest
 @Presubmit
-@Ignore("b/163095037")
 public class ProtoLogIntegrationTest {
     @After
     public void tearDown() {
         ProtoLogImpl.setSingleInstance(null);
     }
 
+    @Ignore("b/163095037")
     @Test
     public void testProtoLogToolIntegration() {
         ProtoLogImpl mockedProtoLog = mock(ProtoLogImpl.class);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
index 1ec9bd2..b89d168 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
@@ -890,8 +890,8 @@
 
         // Make sure the root task is valid and can be reused on default display.
         final Task stack = mRootWindowContainer.getValidLaunchStackInTaskDisplayArea(
-                mRootWindowContainer.getDefaultTaskDisplayArea(), activity, task, null,
-                null);
+                mRootWindowContainer.getDefaultTaskDisplayArea(), activity, task,
+                null /* options */, null /* launchParams */);
         assertEquals(task, stack);
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index 2f73655..189e540 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -16,6 +16,8 @@
 
 package com.android.server.wm;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.view.Display.INVALID_DISPLAY;
@@ -253,6 +255,19 @@
         assertFalse(mWpc.registeredForActivityConfigChanges());
     }
 
+    @Test
+    public void testProcessLevelConfiguration() {
+        Configuration config = new Configuration();
+        config.windowConfiguration.setActivityType(ACTIVITY_TYPE_HOME);
+        mWpc.onRequestedOverrideConfigurationChanged(config);
+        assertEquals(ACTIVITY_TYPE_HOME, config.windowConfiguration.getActivityType());
+        assertEquals(ACTIVITY_TYPE_UNDEFINED, mWpc.getActivityType());
+
+        mWpc.onMergedOverrideConfigurationChanged(config);
+        assertEquals(ACTIVITY_TYPE_HOME, config.windowConfiguration.getActivityType());
+        assertEquals(ACTIVITY_TYPE_UNDEFINED, mWpc.getActivityType());
+    }
+
     private TestDisplayContent createTestDisplayContentInContainer() {
         return new TestDisplayContent.Builder(mAtm, 1000, 1500).build();
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
index d9c48fc..e0785c1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
@@ -27,6 +27,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -145,29 +146,40 @@
         assertEquals(0, token.getWindowsCount());
     }
 
-    @UseTestDisplay(addWindows = { W_ACTIVITY, W_WALLPAPER })
     @Test
     public void testFinishFixedRotationTransform() {
-        final WindowToken appToken = mAppWindow.mToken;
-        final WindowToken wallpaperToken = mWallpaperWindow.mToken;
+        final WindowToken[] tokens = new WindowToken[3];
+        for (int i = 0; i < tokens.length; i++) {
+            tokens[i] = createTestWindowToken(TYPE_APPLICATION_OVERLAY, mDisplayContent);
+        }
+
         final Configuration config = new Configuration(mDisplayContent.getConfiguration());
         final int originalRotation = config.windowConfiguration.getRotation();
         final int targetRotation = (originalRotation + 1) % 4;
 
         config.windowConfiguration.setRotation(targetRotation);
-        appToken.applyFixedRotationTransform(mDisplayInfo, mDisplayContent.mDisplayFrames, config);
-        wallpaperToken.linkFixedRotationTransform(appToken);
+        tokens[0].applyFixedRotationTransform(mDisplayInfo, mDisplayContent.mDisplayFrames, config);
+        tokens[1].linkFixedRotationTransform(tokens[0]);
 
         // The window tokens should apply the rotation by the transformation.
-        assertEquals(targetRotation, appToken.getWindowConfiguration().getRotation());
-        assertEquals(targetRotation, wallpaperToken.getWindowConfiguration().getRotation());
+        assertEquals(targetRotation, tokens[0].getWindowConfiguration().getRotation());
+        assertEquals(targetRotation, tokens[1].getWindowConfiguration().getRotation());
 
-        // The display doesn't rotate, the transformation will be canceled.
-        mAppWindow.mToken.finishFixedRotationTransform();
+        tokens[2].applyFixedRotationTransform(mDisplayInfo, mDisplayContent.mDisplayFrames, config);
+        // The tokens[1] was linked to tokens[0], this should make tokens[1] link to tokens[2].
+        tokens[1].linkFixedRotationTransform(tokens[2]);
 
-        // The window tokens should restore to the original rotation.
-        assertEquals(originalRotation, appToken.getWindowConfiguration().getRotation());
-        assertEquals(originalRotation, wallpaperToken.getWindowConfiguration().getRotation());
+        // Assume the display doesn't rotate, the transformation will be canceled.
+        tokens[0].finishFixedRotationTransform();
+
+        // The tokens[0] should restore to the original rotation.
+        assertEquals(originalRotation, tokens[0].getWindowConfiguration().getRotation());
+        // The tokens[1] is linked to tokens[2], it should keep the target rotation.
+        assertNotEquals(originalRotation, tokens[1].getWindowConfiguration().getRotation());
+
+        tokens[2].finishFixedRotationTransform();
+        // The rotation of tokens[1] should be restored because its linked state is finished.
+        assertEquals(originalRotation, tokens[1].getWindowConfiguration().getRotation());
     }
 
     /**
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 78556ef..1e5d92b 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -61,7 +61,6 @@
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ShortcutServiceInternal;
-import android.content.pm.UserInfo;
 import android.content.res.Configuration;
 import android.os.Binder;
 import android.os.Environment;
@@ -306,25 +305,25 @@
 
     @Override
     public void onUserStopping(@NonNull TargetUser user) {
-        final UserInfo userInfo = user.getUserInfo();
+        final int userId = user.getUserIdentifier();
 
         synchronized (mLock) {
             // User was started but never unlocked so no need to report a user stopped event
-            if (!mUserUnlockedStates.get(userInfo.id)) {
-                persistPendingEventsLocked(userInfo.id);
+            if (!mUserUnlockedStates.get(userId)) {
+                persistPendingEventsLocked(userId);
                 return;
             }
 
             // Report a user stopped event before persisting all stats to disk via the user service
             final Event event = new Event(USER_STOPPED, SystemClock.elapsedRealtime());
             event.mPackage = Event.DEVICE_EVENT_PACKAGE_NAME;
-            reportEvent(event, userInfo.id);
-            final UserUsageStatsService userService = mUserState.get(userInfo.id);
+            reportEvent(event, userId);
+            final UserUsageStatsService userService = mUserState.get(userId);
             if (userService != null) {
                 userService.userStopped();
             }
-            mUserUnlockedStates.put(userInfo.id, false);
-            mUserState.put(userInfo.id, null); // release the service (mainly for GC)
+            mUserUnlockedStates.put(userId, false);
+            mUserState.put(userId, null); // release the service (mainly for GC)
         }
     }
 
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 0ea84da..26d46db 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -1276,7 +1276,8 @@
          * @return The initialized AudioRecord
          */
         private @NonNull AudioRecord createAudioRecordForEvent(
-                @NonNull SoundTrigger.GenericRecognitionEvent event) {
+                @NonNull SoundTrigger.GenericRecognitionEvent event)
+                throws IllegalArgumentException, UnsupportedOperationException {
             AudioAttributes.Builder attributesBuilder = new AudioAttributes.Builder();
             attributesBuilder.setInternalCapturePreset(MediaRecorder.AudioSource.HOTWORD);
             AudioAttributes attributes = attributesBuilder.build();
@@ -1285,21 +1286,15 @@
 
             sEventLogger.log(new SoundTriggerLogger.StringEvent("createAudioRecordForEvent"));
 
-            try {
-                return (new AudioRecord.Builder())
-                            .setAudioAttributes(attributes)
-                            .setAudioFormat((new AudioFormat.Builder())
-                                .setChannelMask(originalFormat.getChannelMask())
-                                .setEncoding(originalFormat.getEncoding())
-                                .setSampleRate(originalFormat.getSampleRate())
-                                .build())
-                            .setSessionId(event.getCaptureSession())
-                            .build();
-            } catch (IllegalArgumentException | UnsupportedOperationException e) {
-                Slog.w(TAG, mPuuid + ": createAudioRecordForEvent(" + event
-                        + "), failed to create AudioRecord");
-                return null;
-            }
+            return (new AudioRecord.Builder())
+                        .setAudioAttributes(attributes)
+                        .setAudioFormat((new AudioFormat.Builder())
+                            .setChannelMask(originalFormat.getChannelMask())
+                            .setEncoding(originalFormat.getEncoding())
+                            .setSampleRate(originalFormat.getSampleRate())
+                            .build())
+                        .setSessionId(event.getCaptureSession())
+                        .build();
         }
 
         @Override
@@ -1325,13 +1320,13 @@
                     // execute if throttled:
                     () -> {
                         if (event.isCaptureAvailable()) {
-                            AudioRecord capturedData = createAudioRecordForEvent(event);
-
-                            // Currently we need to start and release the audio record to reset
-                            // the DSP even if we don't want to process the event
-                            if (capturedData != null) {
+                            try {
+                                AudioRecord capturedData = createAudioRecordForEvent(event);
                                 capturedData.startRecording();
                                 capturedData.release();
+                            } catch (IllegalArgumentException | UnsupportedOperationException e) {
+                                Slog.w(TAG, mPuuid + ": createAudioRecordForEvent(" + event
+                                        + "), failed to create AudioRecord");
                             }
                         }
                     }));
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 0f898f8..a2215ce 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -164,13 +164,13 @@
         }
     }
 
-    private boolean isSupported(UserInfo user) {
+    @Override
+    public boolean isUserSupported(@NonNull TargetUser user) {
         return user.isFull();
     }
 
-    @Override
-    public boolean isUserSupported(TargetUser user) {
-        return isSupported(user.getUserInfo());
+    private boolean isUserSupported(@NonNull UserInfo user) {
+        return user.isFull();
     }
 
     @Override
@@ -461,7 +461,7 @@
         private void setCurrentUserLocked(@UserIdInt int userHandle) {
             mCurUser = userHandle;
             final UserInfo userInfo = mUserManagerInternal.getUserInfo(mCurUser);
-            mCurUserSupported = isSupported(userInfo);
+            mCurUserSupported = isUserSupported(userInfo);
         }
 
         public void switchUser(@UserIdInt int userHandle) {
@@ -967,10 +967,10 @@
                 throw new IllegalArgumentException("Illegal argument(s) in getKeyphraseSoundModel");
             }
 
-            final int callingUid = UserHandle.getCallingUserId();
+            final int callingUserId = UserHandle.getCallingUserId();
             final long caller = Binder.clearCallingIdentity();
             try {
-                return mDbHelper.getKeyphraseSoundModel(keyphraseId, callingUid, bcp47Locale);
+                return mDbHelper.getKeyphraseSoundModel(keyphraseId, callingUserId, bcp47Locale);
             } finally {
                 Binder.restoreCallingIdentity(caller);
             }
@@ -1010,7 +1010,7 @@
                         "Illegal argument(s) in deleteKeyphraseSoundModel");
             }
 
-            final int callingUid = UserHandle.getCallingUserId();
+            final int callingUserId = UserHandle.getCallingUserId();
             final long caller = Binder.clearCallingIdentity();
             boolean deleted = false;
             try {
@@ -1018,7 +1018,8 @@
                 if (unloadStatus != SoundTriggerInternal.STATUS_OK) {
                     Slog.w(TAG, "Unable to unload keyphrase sound model:" + unloadStatus);
                 }
-                deleted = mDbHelper.deleteKeyphraseSoundModel(keyphraseId, callingUid, bcp47Locale);
+                deleted = mDbHelper.deleteKeyphraseSoundModel(
+                        keyphraseId, callingUserId, bcp47Locale);
                 return deleted ? SoundTriggerInternal.STATUS_OK : SoundTriggerInternal.STATUS_ERROR;
             } finally {
                 if (deleted) {
@@ -1045,11 +1046,11 @@
                 throw new IllegalArgumentException("Illegal argument(s) in isEnrolledForKeyphrase");
             }
 
-            final int callingUid = UserHandle.getCallingUserId();
+            final int callingUserId = UserHandle.getCallingUserId();
             final long caller = Binder.clearCallingIdentity();
             try {
                 KeyphraseSoundModel model =
-                        mDbHelper.getKeyphraseSoundModel(keyphraseId, callingUid, bcp47Locale);
+                        mDbHelper.getKeyphraseSoundModel(keyphraseId, callingUserId, bcp47Locale);
                 return model != null;
             } finally {
                 Binder.restoreCallingIdentity(caller);
@@ -1067,11 +1068,11 @@
                 throw new IllegalArgumentException("Illegal argument(s) in isEnrolledForKeyphrase");
             }
 
-            final int callingUid = UserHandle.getCallingUserId();
+            final int callingUserId = UserHandle.getCallingUserId();
             final long caller = Binder.clearCallingIdentity();
             try {
                 KeyphraseSoundModel model =
-                        mDbHelper.getKeyphraseSoundModel(keyphrase, callingUid, bcp47Locale);
+                        mDbHelper.getKeyphraseSoundModel(keyphrase, callingUserId, bcp47Locale);
                 if (model == null) {
                     return null;
                 }
@@ -1118,11 +1119,11 @@
                 }
             }
 
-            int callingUid = UserHandle.getCallingUserId();
+            final int callingUserId = UserHandle.getCallingUserId();
             final long caller = Binder.clearCallingIdentity();
             try {
                 KeyphraseSoundModel soundModel =
-                        mDbHelper.getKeyphraseSoundModel(keyphraseId, callingUid, bcp47Locale);
+                        mDbHelper.getKeyphraseSoundModel(keyphraseId, callingUserId, bcp47Locale);
                 if (soundModel == null
                         || soundModel.getUuid() == null
                         || soundModel.getKeyphrases() == null) {
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 3646647..6288bc1 100755
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -2488,6 +2488,42 @@
     }
 
     /**
+     * Ask some other {@code ConnectionService} to create a {@code RemoteConference} given an
+     * incoming request. This is used by {@code ConnectionService}s that are registered with
+     * {@link PhoneAccount#CAPABILITY_ADHOC_CONFERENCE_CALLING}.
+     *
+     * @param connectionManagerPhoneAccount See description at
+     *          {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
+     * @param request Details about the incoming conference call.
+     * @return The {@code RemoteConference} object to satisfy this call, or {@code null} to not
+     *         handle the call.
+     */
+    public final @Nullable RemoteConference createRemoteIncomingConference(
+            @Nullable PhoneAccountHandle connectionManagerPhoneAccount,
+            @Nullable ConnectionRequest request) {
+        return mRemoteConnectionManager.createRemoteConference(connectionManagerPhoneAccount,
+                request, true);
+    }
+
+    /**
+     * Ask some other {@code ConnectionService} to create a {@code RemoteConference} given an
+     * outgoing request. This is used by {@code ConnectionService}s that are registered with
+     * {@link PhoneAccount#CAPABILITY_ADHOC_CONFERENCE_CALLING}.
+     *
+     * @param connectionManagerPhoneAccount See description at
+     *          {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
+     * @param request Details about the outgoing conference call.
+     * @return The {@code RemoteConference} object to satisfy this call, or {@code null} to not
+     *         handle the call.
+     */
+    public final @Nullable RemoteConference createRemoteOutgoingConference(
+            @Nullable PhoneAccountHandle connectionManagerPhoneAccount,
+            @Nullable ConnectionRequest request) {
+        return mRemoteConnectionManager.createRemoteConference(connectionManagerPhoneAccount,
+                request, false);
+    }
+
+    /**
      * Indicates to the relevant {@code RemoteConnectionService} that the specified
      * {@link RemoteConnection}s should be merged into a conference call.
      * <p>
diff --git a/telecomm/java/android/telecom/RemoteConference.java b/telecomm/java/android/telecom/RemoteConference.java
index 502b7c0..e024e61 100644
--- a/telecomm/java/android/telecom/RemoteConference.java
+++ b/telecomm/java/android/telecom/RemoteConference.java
@@ -16,14 +16,14 @@
 
 package android.telecom;
 
-import com.android.internal.telecom.IConnectionService;
-
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.RemoteException;
 
+import com.android.internal.telecom.IConnectionService;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -155,6 +155,14 @@
     }
 
     /** @hide */
+    RemoteConference(DisconnectCause disconnectCause) {
+        mId = "NULL";
+        mConnectionService = null;
+        mState = Connection.STATE_DISCONNECTED;
+        mDisconnectCause = disconnectCause;
+    }
+
+    /** @hide */
     String getId() {
         return mId;
     }
@@ -583,4 +591,16 @@
             }
         }
     }
+
+    /**
+     * Create a {@link RemoteConference} represents a failure, and which will
+     * be in {@link Connection#STATE_DISCONNECTED}.
+     *
+     * @param disconnectCause The disconnect cause.
+     * @return a failed {@link RemoteConference}
+     * @hide
+     */
+    public static RemoteConference failure(DisconnectCause disconnectCause) {
+        return new RemoteConference(disconnectCause);
+    }
 }
diff --git a/telecomm/java/android/telecom/RemoteConnection.java b/telecomm/java/android/telecom/RemoteConnection.java
index df33625..52210a5 100644
--- a/telecomm/java/android/telecom/RemoteConnection.java
+++ b/telecomm/java/android/telecom/RemoteConnection.java
@@ -16,10 +16,6 @@
 
 package android.telecom;
 
-import com.android.internal.telecom.IConnectionService;
-import com.android.internal.telecom.IVideoCallback;
-import com.android.internal.telecom.IVideoProvider;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
@@ -33,6 +29,10 @@
 import android.telecom.Logging.Session;
 import android.view.Surface;
 
+import com.android.internal.telecom.IConnectionService;
+import com.android.internal.telecom.IVideoCallback;
+import com.android.internal.telecom.IVideoProvider;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -1114,6 +1114,23 @@
     }
 
     /**
+     * Instructs this {@link RemoteConnection} to initiate a conference with a list of
+     * participants.
+     * <p>
+     *
+     * @param participants with which conference call will be formed.
+     */
+    public void addConferenceParticipants(@NonNull List<Uri> participants) {
+        try {
+            if (mConnected) {
+                mConnectionService.addConferenceParticipants(mConnectionId, participants,
+                        null /*Session.Info*/);
+            }
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    /**
      * Set the audio state of this {@code RemoteConnection}.
      *
      * @param state The audio state of this {@code RemoteConnection}.
diff --git a/telecomm/java/android/telecom/RemoteConnectionManager.java b/telecomm/java/android/telecom/RemoteConnectionManager.java
index 0322218..f3c7bd8 100644
--- a/telecomm/java/android/telecom/RemoteConnectionManager.java
+++ b/telecomm/java/android/telecom/RemoteConnectionManager.java
@@ -73,6 +73,37 @@
         return null;
     }
 
+    /**
+     * Ask a {@code RemoteConnectionService} to create a {@code RemoteConference}.
+     * @param connectionManagerPhoneAccount See description at
+     * {@link ConnectionService#onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
+     * @param request Details about the incoming conference call.
+     * @param isIncoming {@code true} if it's an incoming conference.
+     * @return
+     */
+    public RemoteConference createRemoteConference(
+            PhoneAccountHandle connectionManagerPhoneAccount,
+            ConnectionRequest request,
+            boolean isIncoming) {
+        PhoneAccountHandle accountHandle = request.getAccountHandle();
+        if (accountHandle == null) {
+            throw new IllegalArgumentException("accountHandle must be specified.");
+        }
+
+        ComponentName componentName = request.getAccountHandle().getComponentName();
+        if (!mRemoteConnectionServices.containsKey(componentName)) {
+            throw new UnsupportedOperationException("accountHandle not supported: "
+                    + componentName);
+        }
+
+        RemoteConnectionService remoteService = mRemoteConnectionServices.get(componentName);
+        if (remoteService != null) {
+            return remoteService.createRemoteConference(
+                    connectionManagerPhoneAccount, request, isIncoming);
+        }
+        return null;
+    }
+
     public void conferenceRemoteConnections(RemoteConnection a, RemoteConnection b) {
         if (a.getConnectionService() == b.getConnectionService()) {
             try {
diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java
index a083301..bf6a6ef7 100644
--- a/telecomm/java/android/telecom/RemoteConnectionService.java
+++ b/telecomm/java/android/telecom/RemoteConnectionService.java
@@ -31,9 +31,9 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.List;
 import java.util.UUID;
 
 /**
@@ -591,6 +591,38 @@
         }
     }
 
+    RemoteConference createRemoteConference(
+            PhoneAccountHandle connectionManagerPhoneAccount,
+            ConnectionRequest request,
+            boolean isIncoming) {
+        final String id = UUID.randomUUID().toString();
+        try {
+            if (mConferenceById.isEmpty()) {
+                mOutgoingConnectionServiceRpc.addConnectionServiceAdapter(mServant.getStub(),
+                        null /*Session.Info*/);
+            }
+            RemoteConference conference = new RemoteConference(id, mOutgoingConnectionServiceRpc);
+            mOutgoingConnectionServiceRpc.createConference(connectionManagerPhoneAccount,
+                    id,
+                    request,
+                    isIncoming,
+                    false /* isUnknownCall */,
+                    null /*Session.info*/);
+            conference.registerCallback(new RemoteConference.Callback() {
+                @Override
+                public void onDestroyed(RemoteConference conference) {
+                    mConferenceById.remove(id);
+                    maybeDisconnectAdapter();
+                }
+            });
+            conference.putExtras(request.getExtras());
+            return conference;
+        } catch (RemoteException e) {
+            return RemoteConference.failure(
+                    new DisconnectCause(DisconnectCause.ERROR, e.toString()));
+        }
+    }
+
     private boolean hasConnection(String callId) {
         return mConnectionById.containsKey(callId);
     }
diff --git a/telephony/api/system-current.txt b/telephony/api/system-current.txt
index 09c1659..52e0953 100644
--- a/telephony/api/system-current.txt
+++ b/telephony/api/system-current.txt
@@ -1625,6 +1625,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningStringValue(int, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setRcsProvisioningStatusForCapability(int, boolean);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.Callback);
+    field public static final int KEY_VOICE_OVER_WIFI_ENTITLEMENT_ID = 67; // 0x43
     field public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27; // 0x1b
     field public static final int KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE = 26; // 0x1a
     field public static final int PROVISIONING_VALUE_DISABLED = 0; // 0x0
diff --git a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
index e57b030..d4308c4 100644
--- a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
+++ b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
@@ -17,6 +17,7 @@
 package com.android.internal.telephony;
 
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -74,7 +75,7 @@
      * privileged apps may have changed.
      */
     public static synchronized void disableCarrierAppsUntilPrivileged(String callingPackage,
-            TelephonyManager telephonyManager, int userId, Context context) {
+            TelephonyManager telephonyManager, @UserIdInt int userId, Context context) {
         if (DEBUG) {
             Log.d(TAG, "disableCarrierAppsUntilPrivileged");
         }
@@ -101,7 +102,7 @@
      * 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,
-            int userId, Context context) {
+            @UserIdInt int userId, Context context) {
         if (DEBUG) {
             Log.d(TAG, "disableCarrierAppsUntilPrivileged");
         }
@@ -117,9 +118,9 @@
                 systemCarrierAssociatedAppsDisabledUntilUsed, context);
     }
 
-    private static ContentResolver getContentResolverForUser(Context context, int userId) {
-        Context userContext = context.createContextAsUser(UserHandle.getUserHandleForUid(userId),
-                0);
+    private static ContentResolver getContentResolverForUser(Context context,
+            @UserIdInt int userId) {
+        Context userContext = context.createContextAsUser(UserHandle.of(userId), 0);
         return userContext.getContentResolver();
     }
 
diff --git a/telephony/java/android/telephony/CellIdentityNr.java b/telephony/java/android/telephony/CellIdentityNr.java
index e34bbfc..06c34dc 100644
--- a/telephony/java/android/telephony/CellIdentityNr.java
+++ b/telephony/java/android/telephony/CellIdentityNr.java
@@ -37,7 +37,7 @@
     private static final String TAG = "CellIdentityNr";
 
     private static final int MAX_PCI = 1007;
-    private static final int MAX_TAC = 65535;
+    private static final int MAX_TAC = 16777215; // 0xffffff
     private static final int MAX_NRARFCN = 3279165;
     private static final long MAX_NCI = 68719476735L;
 
@@ -53,7 +53,7 @@
     /**
      *
      * @param pci Physical Cell Id in range [0, 1007].
-     * @param tac 16-bit Tracking Area Code.
+     * @param tac 24-bit Tracking Area Code.
      * @param nrArfcn NR Absolute Radio Frequency Channel Number, in range [0, 3279165].
      * @param bands Bands used by the cell. Band number defined in 3GPP TS 38.101-1 and TS 38.101-2.
      * @param mccStr 3-digit Mobile Country Code in string format.
@@ -199,9 +199,9 @@
 
     /**
      * Get the tracking area code.
-     * @return a 16 bit integer or {@link CellInfo#UNAVAILABLE} if unknown.
+     * @return a 24 bit integer or {@link CellInfo#UNAVAILABLE} if unknown.
      */
-    @IntRange(from = 0, to = 65535)
+    @IntRange(from = 0, to = 16777215)
     public int getTac() {
         return mTac;
     }
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index 2b26087..78dc377 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -46,6 +46,7 @@
 import com.android.internal.telephony.ISms;
 import com.android.internal.telephony.ITelephony;
 import com.android.internal.telephony.SmsRawData;
+import com.android.telephony.Rlog;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -1953,7 +1954,6 @@
     public boolean enableCellBroadcastRange(int startMessageId, int endMessageId,
             @android.telephony.SmsCbMessage.MessageFormat int ranType) {
         boolean success = false;
-
         if (endMessageId < startMessageId) {
             throw new IllegalArgumentException("endMessageId < startMessageId");
         }
@@ -1962,10 +1962,14 @@
             if (iSms != null) {
                 // If getSubscriptionId() returns INVALID or an inactive subscription, we will use
                 // the default phone internally.
-                success = iSms.enableCellBroadcastRangeForSubscriber(getSubscriptionId(),
+                int subId = getSubscriptionId();
+                success = iSms.enableCellBroadcastRangeForSubscriber(subId,
                         startMessageId, endMessageId, ranType);
+                Rlog.d(TAG, "enableCellBroadcastRange: " + (success ? "succeeded" : "failed")
+                        + " at calling enableCellBroadcastRangeForSubscriber. subId = " + subId);
             }
         } catch (RemoteException ex) {
+            Rlog.d(TAG, "enableCellBroadcastRange: " + ex.getStackTrace());
             // ignore it
         }
 
@@ -2019,10 +2023,14 @@
             if (iSms != null) {
                 // If getSubscriptionId() returns INVALID or an inactive subscription, we will use
                 // the default phone internally.
-                success = iSms.disableCellBroadcastRangeForSubscriber(getSubscriptionId(),
+                int subId = getSubscriptionId();
+                success = iSms.disableCellBroadcastRangeForSubscriber(subId,
                         startMessageId, endMessageId, ranType);
+                Rlog.d(TAG, "disableCellBroadcastRange: " + (success ? "succeeded" : "failed")
+                        + " at calling disableCellBroadcastRangeForSubscriber. subId = " + subId);
             }
         } catch (RemoteException ex) {
+            Rlog.d(TAG, "disableCellBroadcastRange: " + ex.getStackTrace());
             // ignore it
         }
 
diff --git a/telephony/java/android/telephony/ims/ImsConferenceState.java b/telephony/java/android/telephony/ims/ImsConferenceState.java
index 21bef00..9bf2f44 100644
--- a/telephony/java/android/telephony/ims/ImsConferenceState.java
+++ b/telephony/java/android/telephony/ims/ImsConferenceState.java
@@ -203,10 +203,10 @@
                     for (String key : participantData.keySet()) {
                         sb.append(key);
                         sb.append("=");
-                        if (ENDPOINT.equals(key) || USER.equals(key)) {
-                            sb.append(Rlog.pii(TAG, participantData.get(key)));
-                        } else {
+                        if (STATUS.equals(key)) {
                             sb.append(participantData.get(key));
+                        } else {
+                            sb.append(Rlog.pii(TAG, participantData.get(key)));
                         }
                         sb.append(", ");
                     }
diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java
index 1a606b7..2a073a1 100644
--- a/telephony/java/android/telephony/ims/ProvisioningManager.java
+++ b/telephony/java/android/telephony/ims/ProvisioningManager.java
@@ -851,6 +851,19 @@
     public static final int KEY_RTT_ENABLED = 66;
 
     /**
+     * An obfuscated string defined by the carrier to indicate VoWiFi entitlement status.
+     *
+     * <p>Implementation note: how to generate the value and how it affects VoWiFi service
+     * should follow carrier requirements. For example, set an empty string could result in
+     * VoWiFi being disabled by IMS service, and set to a specific string could enable.
+     *
+     * <p>Value is in String format.
+     * @see #setProvisioningStringValue(int, String)
+     * @see #getProvisioningStringValue(int)
+     */
+    public static final int KEY_VOICE_OVER_WIFI_ENTITLEMENT_ID = 67;
+
+    /**
      * Callback for IMS provisioning changes.
      */
     public static class Callback {
diff --git a/telephony/java/com/android/ims/ImsConfig.java b/telephony/java/com/android/ims/ImsConfig.java
index d0cec52d..4877860 100644
--- a/telephony/java/com/android/ims/ImsConfig.java
+++ b/telephony/java/com/android/ims/ImsConfig.java
@@ -729,7 +729,8 @@
 
         // Expand the operator config items as needed here, need to change
         // PROVISIONED_CONFIG_END after that.
-        public static final int PROVISIONED_CONFIG_END = RTT_SETTING_ENABLED;
+        public static final int PROVISIONED_CONFIG_END =
+                ProvisioningManager.KEY_VOICE_OVER_WIFI_ENTITLEMENT_ID;
 
         // Expand the operator config items as needed here.
     }
diff --git a/tests/AutoVerify/app1/Android.bp b/tests/AutoVerify/app1/Android.bp
deleted file mode 100644
index 548519f..0000000
--- a/tests/AutoVerify/app1/Android.bp
+++ /dev/null
@@ -1,11 +0,0 @@
-android_app {
-    name: "AutoVerifyTest",
-    srcs: ["src/**/*.java"],
-    resource_dirs: ["res"],
-    platform_apis: true,
-    min_sdk_version: "26",
-    target_sdk_version: "26",
-    optimize: {
-        enabled: false,
-    },
-}
diff --git a/tests/AutoVerify/app1/AndroidManifest.xml b/tests/AutoVerify/app1/AndroidManifest.xml
deleted file mode 100644
index d9caad4..0000000
--- a/tests/AutoVerify/app1/AndroidManifest.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.test.autoverify" >
-
-    <uses-sdk android:targetSdkVersion="26" />
-
-    <application
-        android:label="@string/app_name" >
-        <activity
-            android:name=".MainActivity"
-            android:label="@string/app_name" >
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
-
-            <intent-filter android:autoVerify="true">
-                <action android:name="android.intent.action.VIEW" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="http" />
-                <data android:scheme="https" />
-                <data android:host="explicit.example.com" />
-            </intent-filter>
-        </activity>
-    </application>
-</manifest>
diff --git a/tests/AutoVerify/app1/res/values/strings.xml b/tests/AutoVerify/app1/res/values/strings.xml
deleted file mode 100644
index e234355..0000000
--- a/tests/AutoVerify/app1/res/values/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-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.
--->
-
-<resources>
-    <!-- app icon label, do not translate -->
-    <string name="app_name" translatable="false">AutoVerify Test</string>
-</resources>
diff --git a/tests/AutoVerify/app2/Android.bp b/tests/AutoVerify/app2/Android.bp
deleted file mode 100644
index 1c6c97b..0000000
--- a/tests/AutoVerify/app2/Android.bp
+++ /dev/null
@@ -1,11 +0,0 @@
-android_app {
-    name: "AutoVerifyTest2",
-    srcs: ["src/**/*.java"],
-    resource_dirs: ["res"],
-    platform_apis: true,
-    min_sdk_version: "26",
-    target_sdk_version: "26",
-    optimize: {
-        enabled: false,
-    },
-}
diff --git a/tests/AutoVerify/app2/AndroidManifest.xml b/tests/AutoVerify/app2/AndroidManifest.xml
deleted file mode 100644
index a008078..0000000
--- a/tests/AutoVerify/app2/AndroidManifest.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-<?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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.test.autoverify" >
-
-    <uses-sdk android:targetSdkVersion="26" />
-
-    <application
-        android:label="@string/app_name" >
-        <activity
-            android:name=".MainActivity"
-            android:label="@string/app_name" >
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
-
-            <intent-filter android:autoVerify="true">
-                <action android:name="android.intent.action.VIEW" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="http" />
-                <data android:scheme="https" />
-                <data android:host="explicit.example.com" />
-                <data android:host="*.wildcard.tld" />
-            </intent-filter>
-        </activity>
-    </application>
-</manifest>
diff --git a/tests/AutoVerify/app2/res/values/strings.xml b/tests/AutoVerify/app2/res/values/strings.xml
deleted file mode 100644
index e234355..0000000
--- a/tests/AutoVerify/app2/res/values/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-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.
--->
-
-<resources>
-    <!-- app icon label, do not translate -->
-    <string name="app_name" translatable="false">AutoVerify Test</string>
-</resources>
diff --git a/tests/AutoVerify/app2/src/com/android/test/autoverify/MainActivity.java b/tests/AutoVerify/app2/src/com/android/test/autoverify/MainActivity.java
deleted file mode 100644
index 09ef472..0000000
--- a/tests/AutoVerify/app2/src/com/android/test/autoverify/MainActivity.java
+++ /dev/null
@@ -1,15 +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.
- */
diff --git a/tests/AutoVerify/app3/Android.bp b/tests/AutoVerify/app3/Android.bp
deleted file mode 100644
index 70a2b77..0000000
--- a/tests/AutoVerify/app3/Android.bp
+++ /dev/null
@@ -1,11 +0,0 @@
-android_app {
-    name: "AutoVerifyTest3",
-    srcs: ["src/**/*.java"],
-    resource_dirs: ["res"],
-    platform_apis: true,
-    min_sdk_version: "26",
-    target_sdk_version: "26",
-    optimize: {
-        enabled: false,
-    },
-}
diff --git a/tests/AutoVerify/app3/AndroidManifest.xml b/tests/AutoVerify/app3/AndroidManifest.xml
deleted file mode 100644
index efaabc9..0000000
--- a/tests/AutoVerify/app3/AndroidManifest.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-<?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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.test.autoverify" >
-
-    <uses-sdk android:targetSdkVersion="26" />
-
-    <application
-        android:label="@string/app_name" >
-        <activity
-            android:name=".MainActivity"
-            android:label="@string/app_name" >
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
-
-            <!-- does not request autoVerify -->
-            <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="http" />
-                <data android:scheme="https" />
-                <data android:host="explicit.example.com" />
-            </intent-filter>
-        </activity>
-    </application>
-</manifest>
diff --git a/tests/AutoVerify/app3/res/values/strings.xml b/tests/AutoVerify/app3/res/values/strings.xml
deleted file mode 100644
index e234355..0000000
--- a/tests/AutoVerify/app3/res/values/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-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.
--->
-
-<resources>
-    <!-- app icon label, do not translate -->
-    <string name="app_name" translatable="false">AutoVerify Test</string>
-</resources>
diff --git a/tests/AutoVerify/app3/src/com/android/test/autoverify/MainActivity.java b/tests/AutoVerify/app3/src/com/android/test/autoverify/MainActivity.java
deleted file mode 100644
index 09ef472..0000000
--- a/tests/AutoVerify/app3/src/com/android/test/autoverify/MainActivity.java
+++ /dev/null
@@ -1,15 +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.
- */
diff --git a/tests/AutoVerify/app4/Android.bp b/tests/AutoVerify/app4/Android.bp
deleted file mode 100644
index fbdae11..0000000
--- a/tests/AutoVerify/app4/Android.bp
+++ /dev/null
@@ -1,11 +0,0 @@
-android_app {
-    name: "AutoVerifyTest4",
-    srcs: ["src/**/*.java"],
-    resource_dirs: ["res"],
-    platform_apis: true,
-    min_sdk_version: "26",
-    target_sdk_version: "26",
-    optimize: {
-        enabled: false,
-    },
-}
diff --git a/tests/AutoVerify/app4/AndroidManifest.xml b/tests/AutoVerify/app4/AndroidManifest.xml
deleted file mode 100644
index 1c975f8..0000000
--- a/tests/AutoVerify/app4/AndroidManifest.xml
+++ /dev/null
@@ -1,45 +0,0 @@
-<?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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.test.autoverify" >
-
-    <uses-sdk android:targetSdkVersion="26" />
-
-    <application
-        android:label="@string/app_name" >
-        <activity
-            android:name=".MainActivity"
-            android:label="@string/app_name" >
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
-
-            <!-- intentionally does not autoVerify -->
-            <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="http" />
-                <data android:scheme="https" />
-                <data android:host="explicit.example.com" />
-                <data android:host="*.wildcard.tld" />
-            </intent-filter>
-        </activity>
-    </application>
-</manifest>
diff --git a/tests/AutoVerify/app4/res/values/strings.xml b/tests/AutoVerify/app4/res/values/strings.xml
deleted file mode 100644
index e234355..0000000
--- a/tests/AutoVerify/app4/res/values/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-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.
--->
-
-<resources>
-    <!-- app icon label, do not translate -->
-    <string name="app_name" translatable="false">AutoVerify Test</string>
-</resources>
diff --git a/tests/AutoVerify/app4/src/com/android/test/autoverify/MainActivity.java b/tests/AutoVerify/app4/src/com/android/test/autoverify/MainActivity.java
deleted file mode 100644
index 09ef472..0000000
--- a/tests/AutoVerify/app4/src/com/android/test/autoverify/MainActivity.java
+++ /dev/null
@@ -1,15 +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.
- */
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
index 9a8e37b..57d6127 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
@@ -88,7 +88,7 @@
                     noUncoveredRegions(Surface.ROTATION_0, rotation, bugId = 141361128)
                     navBarLayerRotatesAndScales(Surface.ROTATION_0, rotation)
                     statusBarLayerRotatesScales(Surface.ROTATION_0, rotation)
-                    navBarLayerIsAlwaysVisible()
+                    navBarLayerIsAlwaysVisible(bugId = 140855415)
                     statusBarLayerIsAlwaysVisible(enabled = false)
                     wallpaperLayerBecomesInvisible()
                 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/OpenAppToSplitScreenTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/OpenAppToSplitScreenTest.kt
index 91ec211..279092d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/OpenAppToSplitScreenTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/OpenAppToSplitScreenTest.kt
@@ -87,9 +87,9 @@
                 }
 
                 layersTrace {
-                    navBarLayerIsAlwaysVisible()
+                    navBarLayerIsAlwaysVisible(bugId = 140855415)
                     statusBarLayerIsAlwaysVisible()
-                    noUncoveredRegions(rotation)
+                    noUncoveredRegions(rotation, enabled = false)
                     navBarLayerRotatesAndScales(rotation, bugId = 140855415)
                     statusBarLayerRotatesScales(rotation)
 
diff --git a/tests/RollbackTest/README.txt b/tests/RollbackTest/README.txt
index c0b718a..bc3b3bc 100644
--- a/tests/RollbackTest/README.txt
+++ b/tests/RollbackTest/README.txt
@@ -9,10 +9,10 @@
   - device driven test for staged rollbacks.
 
 TestApp
-  - source for dummy apks used in testing.
+  - source for fake apks used in testing.
 
 TestApex
-  - source for dummy apex modules used in testing.
+  - source for fake apex modules used in testing.
 
 Running the tests
 =================
diff --git a/tests/StagedInstallTest/Android.bp b/tests/StagedInstallTest/Android.bp
index 530d0e4..76f8df0 100644
--- a/tests/StagedInstallTest/Android.bp
+++ b/tests/StagedInstallTest/Android.bp
@@ -18,6 +18,9 @@
     srcs: ["app/src/**/*.java"],
     static_libs: ["androidx.test.rules", "cts-install-lib"],
     test_suites: ["general-tests"],
+    java_resources: [
+        ":com.android.apex.apkrollback.test_v2",
+    ],
 }
 
 java_test_host {
diff --git a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java
index 02597d5..7817239 100644
--- a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java
+++ b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java
@@ -49,6 +49,11 @@
 public class StagedInstallInternalTest {
 
     private static final String TAG = StagedInstallInternalTest.class.getSimpleName();
+    private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test";
+    private static final TestApp TEST_APEX_WITH_APK_V1 = new TestApp("TestApexWithApkV1",
+            APK_IN_APEX_TESTAPEX_NAME, 1, /*isApex*/true, APK_IN_APEX_TESTAPEX_NAME + "_v1.apex");
+    private static final TestApp TEST_APEX_WITH_APK_V2 = new TestApp("TestApexWithApkV2",
+            APK_IN_APEX_TESTAPEX_NAME, 2, /*isApex*/true, APK_IN_APEX_TESTAPEX_NAME + "_v2.apex");
 
     private File mTestStateFile = new File(
             InstrumentationRegistry.getInstrumentation().getContext().getFilesDir(),
@@ -82,6 +87,24 @@
     }
 
     @Test
+    public void testDuplicateApkInApexShouldFail_Commit() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+        // Duplicate packages(TestApp.A) in TEST_APEX_WITH_APK_V2(apk-in-apex) and TestApp.A2(apk)
+        // should fail to install.
+        int sessionId = Install.multi(TEST_APEX_WITH_APK_V2, TestApp.A2).setStaged().commit();
+        storeSessionId(sessionId);
+    }
+
+    @Test
+    public void testDuplicateApkInApexShouldFail_Verify() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+        int sessionId = retrieveLastSessionId();
+        PackageInstaller.SessionInfo info =
+                InstallUtils.getPackageInstaller().getSessionInfo(sessionId);
+        assertThat(info.isStagedSessionFailed()).isTrue();
+    }
+
+    @Test
     public void testSystemServerRestartDoesNotAffectStagedSessions_Commit() throws Exception {
         int sessionId = Install.single(TestApp.A1).setStaged().commit();
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
@@ -96,6 +119,18 @@
         assertSessionReady(sessionId);
     }
 
+    @Test
+    public void testAbandonStagedSessionShouldCleanUp() throws Exception {
+        int id1 = Install.single(TestApp.A1).setStaged().createSession();
+        InstallUtils.getPackageInstaller().abandonSession(id1);
+        int id2 = Install.multi(TestApp.A1).setStaged().createSession();
+        InstallUtils.getPackageInstaller().abandonSession(id2);
+        int id3 = Install.single(TestApp.A1).setStaged().commit();
+        InstallUtils.getPackageInstaller().abandonSession(id3);
+        int id4 = Install.multi(TestApp.A1).setStaged().commit();
+        InstallUtils.getPackageInstaller().abandonSession(id4);
+    }
+
     private static void assertSessionReady(int sessionId) {
         assertSessionState(sessionId,
                 (session) -> assertThat(session.isStagedSessionReady()).isTrue());
diff --git a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
index 55def49..d7b0796 100644
--- a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
+++ b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
@@ -24,11 +24,14 @@
 
 import android.cts.install.lib.host.InstallUtilsHost;
 
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.ddmlib.Log;
 import com.android.tests.rollback.host.AbandonSessionsRule;
 import com.android.tests.util.ModuleTestUtils;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
 import com.android.tradefed.util.ProcessInfo;
 
 import org.junit.After;
@@ -38,6 +41,9 @@
 import org.junit.runner.RunWith;
 
 import java.io.File;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
 
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class StagedInstallInternalTest extends BaseHostJUnit4Test {
@@ -49,6 +55,7 @@
     public AbandonSessionsRule mHostTestRule = new AbandonSessionsRule(this);
     private static final String SHIM_V2 = "com.android.apex.cts.shim.v2.apex";
     private static final String APK_A = "TestAppAv1.apk";
+    private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test";
 
     private final ModuleTestUtils mTestUtils = new ModuleTestUtils(this);
     private final InstallUtilsHost mHostUtils = new InstallUtilsHost(this);
@@ -74,6 +81,8 @@
         } catch (AssertionError e) {
             Log.e(TAG, e);
         }
+        deleteFiles("/system/apex/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex",
+                "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex");
     }
 
     @Before
@@ -86,6 +95,55 @@
         cleanUp();
     }
 
+    /**
+     * Deletes files and reboots the device if necessary.
+     * @param files the paths of files which might contain wildcards
+     */
+    private void deleteFiles(String... files) throws Exception {
+        boolean found = false;
+        for (String file : files) {
+            CommandResult result = getDevice().executeShellV2Command("ls " + file);
+            if (result.getStatus() == CommandStatus.SUCCESS) {
+                found = true;
+                break;
+            }
+        }
+
+        if (found) {
+            if (!getDevice().isAdbRoot()) {
+                getDevice().enableAdbRoot();
+            }
+            getDevice().remountSystemWritable();
+            for (String file : files) {
+                getDevice().executeShellCommand("rm -rf " + file);
+            }
+            getDevice().reboot();
+        }
+    }
+
+    private void pushTestApex() throws Exception {
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
+        final String fileName = APK_IN_APEX_TESTAPEX_NAME + "_v1.apex";
+        final File apex = buildHelper.getTestFile(fileName);
+        if (!getDevice().isAdbRoot()) {
+            getDevice().enableAdbRoot();
+        }
+        getDevice().remountSystemWritable();
+        assertTrue(getDevice().pushFile(apex, "/system/apex/" + fileName));
+        getDevice().reboot();
+    }
+
+    /**
+     * Tests that duplicate packages in apk-in-apex and apk should fail to install.
+     */
+    @Test
+    public void testDuplicateApkInApexShouldFail() throws Exception {
+        pushTestApex();
+        runPhase("testDuplicateApkInApexShouldFail_Commit");
+        getDevice().reboot();
+        runPhase("testDuplicateApkInApexShouldFail_Verify");
+    }
+
     @Test
     public void testSystemServerRestartDoesNotAffectStagedSessions() throws Exception {
         runPhase("testSystemServerRestartDoesNotAffectStagedSessions_Commit");
@@ -145,6 +203,28 @@
         assertThat(sessionIds.length).isEqualTo(3);
     }
 
+    @Test
+    public void testAbandonStagedSessionShouldCleanUp() throws Exception {
+        List<String> before = getStagingDirectories();
+        runPhase("testAbandonStagedSessionShouldCleanUp");
+        List<String> after = getStagingDirectories();
+        // The staging directories generated during the test should be deleted
+        assertThat(after).isEqualTo(before);
+    }
+
+    private List<String> getStagingDirectories() {
+        String baseDir = "/data/app-staging";
+        try {
+            return getDevice().getFileEntry(baseDir).getChildren(false)
+                    .stream().filter(entry -> entry.getName().matches("session_\\d+"))
+                    .map(entry -> entry.getName())
+                    .collect(Collectors.toList());
+        } catch (Exception e) {
+            // Return an empty list if any error
+            return Collections.EMPTY_LIST;
+        }
+    }
+
     private void restartSystemServer() throws Exception {
         // Restart the system server
         ProcessInfo oldPs = getDevice().getProcessByName("system_server");
diff --git a/tests/net/common/java/android/net/DhcpInfoTest.java b/tests/net/common/java/android/net/DhcpInfoTest.java
index 4d45ad7..ab4726b 100644
--- a/tests/net/common/java/android/net/DhcpInfoTest.java
+++ b/tests/net/common/java/android/net/DhcpInfoTest.java
@@ -17,8 +17,8 @@
 package android.net;
 
 import static com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTL;
-import static com.android.testutils.MiscAssertsKt.assertFieldCountEquals;
-import static com.android.testutils.ParcelUtilsKt.parcelingRoundTrip;
+import static com.android.testutils.MiscAsserts.assertFieldCountEquals;
+import static com.android.testutils.ParcelUtils.parcelingRoundTrip;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
diff --git a/tests/net/common/java/android/net/IpPrefixTest.java b/tests/net/common/java/android/net/IpPrefixTest.java
index 985e10d..9c0fc7c 100644
--- a/tests/net/common/java/android/net/IpPrefixTest.java
+++ b/tests/net/common/java/android/net/IpPrefixTest.java
@@ -16,10 +16,10 @@
 
 package android.net;
 
-import static com.android.testutils.MiscAssertsKt.assertEqualBothWays;
-import static com.android.testutils.MiscAssertsKt.assertFieldCountEquals;
-import static com.android.testutils.MiscAssertsKt.assertNotEqualEitherWay;
-import static com.android.testutils.ParcelUtilsKt.assertParcelingIsLossless;
+import static com.android.testutils.MiscAsserts.assertEqualBothWays;
+import static com.android.testutils.MiscAsserts.assertFieldCountEquals;
+import static com.android.testutils.MiscAsserts.assertNotEqualEitherWay;
+import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
diff --git a/tests/net/common/java/android/net/LinkAddressTest.java b/tests/net/common/java/android/net/LinkAddressTest.java
index c74c112..60308e3 100644
--- a/tests/net/common/java/android/net/LinkAddressTest.java
+++ b/tests/net/common/java/android/net/LinkAddressTest.java
@@ -27,10 +27,10 @@
 import static android.system.OsConstants.RT_SCOPE_SITE;
 import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
 
-import static com.android.testutils.MiscAssertsKt.assertEqualBothWays;
-import static com.android.testutils.MiscAssertsKt.assertFieldCountEquals;
-import static com.android.testutils.MiscAssertsKt.assertNotEqualEitherWay;
-import static com.android.testutils.ParcelUtilsKt.assertParcelingIsLossless;
+import static com.android.testutils.MiscAsserts.assertEqualBothWays;
+import static com.android.testutils.MiscAsserts.assertFieldCountEquals;
+import static com.android.testutils.MiscAsserts.assertNotEqualEitherWay;
+import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
diff --git a/tests/net/common/java/android/net/LinkPropertiesTest.java b/tests/net/common/java/android/net/LinkPropertiesTest.java
index 6eba62e..3c3076f 100644
--- a/tests/net/common/java/android/net/LinkPropertiesTest.java
+++ b/tests/net/common/java/android/net/LinkPropertiesTest.java
@@ -20,9 +20,9 @@
 import static android.net.RouteInfo.RTN_UNICAST;
 import static android.net.RouteInfo.RTN_UNREACHABLE;
 
-import static com.android.testutils.ParcelUtilsKt.assertParcelSane;
-import static com.android.testutils.ParcelUtilsKt.assertParcelingIsLossless;
-import static com.android.testutils.ParcelUtilsKt.parcelingRoundTrip;
+import static com.android.testutils.ParcelUtils.assertParcelSane;
+import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
+import static com.android.testutils.ParcelUtils.parcelingRoundTrip;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
diff --git a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
index 3f8261d..e169312 100644
--- a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
@@ -42,8 +42,8 @@
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
 import static android.net.NetworkCapabilities.UNRESTRICTED_CAPABILITIES;
 
-import static com.android.testutils.ParcelUtilsKt.assertParcelSane;
-import static com.android.testutils.ParcelUtilsKt.assertParcelingIsLossless;
+import static com.android.testutils.ParcelUtils.assertParcelSane;
+import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
diff --git a/tests/net/common/java/android/net/RouteInfoTest.java b/tests/net/common/java/android/net/RouteInfoTest.java
index 60cac0b..71689f9 100644
--- a/tests/net/common/java/android/net/RouteInfoTest.java
+++ b/tests/net/common/java/android/net/RouteInfoTest.java
@@ -18,10 +18,10 @@
 
 import static android.net.RouteInfo.RTN_UNREACHABLE;
 
-import static com.android.testutils.MiscAssertsKt.assertEqualBothWays;
-import static com.android.testutils.MiscAssertsKt.assertFieldCountEquals;
-import static com.android.testutils.MiscAssertsKt.assertNotEqualEitherWay;
-import static com.android.testutils.ParcelUtilsKt.assertParcelingIsLossless;
+import static com.android.testutils.MiscAsserts.assertEqualBothWays;
+import static com.android.testutils.MiscAsserts.assertFieldCountEquals;
+import static com.android.testutils.MiscAsserts.assertNotEqualEitherWay;
+import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
diff --git a/tests/net/common/java/android/net/apf/ApfCapabilitiesTest.java b/tests/net/common/java/android/net/apf/ApfCapabilitiesTest.java
index 8480544..d50406f 100644
--- a/tests/net/common/java/android/net/apf/ApfCapabilitiesTest.java
+++ b/tests/net/common/java/android/net/apf/ApfCapabilitiesTest.java
@@ -16,7 +16,7 @@
 
 package android.net.apf;
 
-import static com.android.testutils.ParcelUtilsKt.assertParcelSane;
+import static com.android.testutils.ParcelUtils.assertParcelSane;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
diff --git a/tests/net/integration/util/com/android/server/ConnectivityServiceTestUtils.kt b/tests/net/integration/util/com/android/server/ConnectivityServiceTestUtils.kt
index fa2b99c..165fd37 100644
--- a/tests/net/integration/util/com/android/server/ConnectivityServiceTestUtils.kt
+++ b/tests/net/integration/util/com/android/server/ConnectivityServiceTestUtils.kt
@@ -14,6 +14,8 @@
  * limitations under the License
  */
 
+@file:JvmName("ConnectivityServiceTestUtils")
+
 package com.android.server
 
 import android.net.ConnectivityManager.TYPE_BLUETOOTH
diff --git a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
index 0ffafd4..9f0b41f 100644
--- a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
+++ b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
@@ -24,7 +24,7 @@
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
 
-import static com.android.server.ConnectivityServiceTestUtilsKt.transportToLegacyType;
+import static com.android.server.ConnectivityServiceTestUtils.transportToLegacyType;
 
 import static junit.framework.Assert.assertTrue;
 
@@ -49,7 +49,7 @@
 import android.util.Log;
 
 import com.android.server.connectivity.ConnectivityConstants;
-import com.android.testutils.HandlerUtilsKt;
+import com.android.testutils.HandlerUtils;
 import com.android.testutils.TestableNetworkCallback;
 
 import java.util.Set;
@@ -265,6 +265,6 @@
     }
 
     public void waitForIdle(long timeoutMs) {
-        HandlerUtilsKt.waitForIdle(mHandlerThread, timeoutMs);
+        HandlerUtils.waitForIdle(mHandlerThread, timeoutMs);
     }
 }
diff --git a/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java b/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java
index 1d6c107..06e9405 100644
--- a/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java
+++ b/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java
@@ -21,7 +21,7 @@
 import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport;
 import static android.net.ConnectivityDiagnosticsManager.DataStallReport;
 
-import static com.android.testutils.ParcelUtilsKt.assertParcelSane;
+import static com.android.testutils.ParcelUtils.assertParcelSane;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
diff --git a/tests/net/java/android/net/IpSecConfigTest.java b/tests/net/java/android/net/IpSecConfigTest.java
index c9888b2..25e225e 100644
--- a/tests/net/java/android/net/IpSecConfigTest.java
+++ b/tests/net/java/android/net/IpSecConfigTest.java
@@ -16,8 +16,8 @@
 
 package android.net;
 
-import static com.android.testutils.ParcelUtilsKt.assertParcelSane;
-import static com.android.testutils.ParcelUtilsKt.assertParcelingIsLossless;
+import static com.android.testutils.ParcelUtils.assertParcelSane;
+import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotSame;
diff --git a/tests/net/java/android/net/TcpKeepalivePacketDataTest.java b/tests/net/java/android/net/TcpKeepalivePacketDataTest.java
index cea8c57..835a83e 100644
--- a/tests/net/java/android/net/TcpKeepalivePacketDataTest.java
+++ b/tests/net/java/android/net/TcpKeepalivePacketDataTest.java
@@ -16,7 +16,7 @@
 
 package android.net;
 
-import static com.android.testutils.ParcelUtilsKt.assertParcelingIsLossless;
+import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
diff --git a/tests/net/java/android/net/TelephonyNetworkSpecifierTest.java b/tests/net/java/android/net/TelephonyNetworkSpecifierTest.java
index efb9203..6714bb1 100644
--- a/tests/net/java/android/net/TelephonyNetworkSpecifierTest.java
+++ b/tests/net/java/android/net/TelephonyNetworkSpecifierTest.java
@@ -16,7 +16,7 @@
 
 package android.net;
 
-import static com.android.testutils.ParcelUtilsKt.assertParcelSane;
+import static com.android.testutils.ParcelUtils.assertParcelSane;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
diff --git a/tests/net/java/android/net/nsd/NsdManagerTest.java b/tests/net/java/android/net/nsd/NsdManagerTest.java
index cf7587a..b0a9b8a 100644
--- a/tests/net/java/android/net/nsd/NsdManagerTest.java
+++ b/tests/net/java/android/net/nsd/NsdManagerTest.java
@@ -38,7 +38,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.util.AsyncChannel;
-import com.android.testutils.HandlerUtilsKt;
+import com.android.testutils.HandlerUtils;
 
 import org.junit.After;
 import org.junit.Before;
@@ -73,7 +73,7 @@
 
     @After
     public void tearDown() throws Exception {
-        HandlerUtilsKt.waitForIdle(mServiceHandler, mTimeoutMs);
+        HandlerUtils.waitForIdle(mServiceHandler, mTimeoutMs);
         mServiceHandler.chan.disconnect();
         mServiceHandler.stop();
         if (mManager != null) {
@@ -333,7 +333,7 @@
     }
 
     int verifyRequest(int expectedMessageType) {
-        HandlerUtilsKt.waitForIdle(mServiceHandler, mTimeoutMs);
+        HandlerUtils.waitForIdle(mServiceHandler, mTimeoutMs);
         verify(mServiceHandler, timeout(mTimeoutMs)).handleMessage(any());
         reset(mServiceHandler);
         Message received = mServiceHandler.getLastMessage();
diff --git a/tests/net/java/com/android/internal/net/VpnProfileTest.java b/tests/net/java/com/android/internal/net/VpnProfileTest.java
index e5daa71..46597d1 100644
--- a/tests/net/java/com/android/internal/net/VpnProfileTest.java
+++ b/tests/net/java/com/android/internal/net/VpnProfileTest.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.net;
 
-import static com.android.testutils.ParcelUtilsKt.assertParcelSane;
+import static com.android.testutils.ParcelUtils.assertParcelSane;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 0f24b0c..a3673df 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -78,16 +78,16 @@
 import static android.os.Process.INVALID_UID;
 import static android.system.OsConstants.IPPROTO_TCP;
 
-import static com.android.server.ConnectivityServiceTestUtilsKt.transportToLegacyType;
-import static com.android.testutils.ConcurrentUtilsKt.await;
-import static com.android.testutils.ConcurrentUtilsKt.durationOf;
+import static com.android.server.ConnectivityServiceTestUtils.transportToLegacyType;
+import static com.android.testutils.ConcurrentUtils.await;
+import static com.android.testutils.ConcurrentUtils.durationOf;
 import static com.android.testutils.ExceptionUtils.ignoreExceptions;
-import static com.android.testutils.HandlerUtilsKt.waitForIdleSerialExecutor;
-import static com.android.testutils.MiscAssertsKt.assertContainsExactly;
-import static com.android.testutils.MiscAssertsKt.assertEmpty;
-import static com.android.testutils.MiscAssertsKt.assertLength;
-import static com.android.testutils.MiscAssertsKt.assertRunsInAtMost;
-import static com.android.testutils.MiscAssertsKt.assertThrows;
+import static com.android.testutils.HandlerUtils.waitForIdleSerialExecutor;
+import static com.android.testutils.MiscAsserts.assertContainsExactly;
+import static com.android.testutils.MiscAsserts.assertEmpty;
+import static com.android.testutils.MiscAsserts.assertLength;
+import static com.android.testutils.MiscAsserts.assertRunsInAtMost;
+import static com.android.testutils.MiscAsserts.assertThrows;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -242,7 +242,7 @@
 import com.android.server.net.NetworkPinner;
 import com.android.server.net.NetworkPolicyManagerInternal;
 import com.android.testutils.ExceptionUtils;
-import com.android.testutils.HandlerUtilsKt;
+import com.android.testutils.HandlerUtils;
 import com.android.testutils.RecorderCallback.CallbackEntry;
 import com.android.testutils.TestableNetworkCallback;
 
@@ -518,12 +518,12 @@
     }
 
     private void waitForIdle() {
-        HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
+        HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
         waitForIdle(mCellNetworkAgent, TIMEOUT_MS);
         waitForIdle(mWiFiNetworkAgent, TIMEOUT_MS);
         waitForIdle(mEthernetNetworkAgent, TIMEOUT_MS);
-        HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
-        HandlerUtilsKt.waitForIdle(ConnectivityThread.get(), TIMEOUT_MS);
+        HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
+        HandlerUtils.waitForIdle(ConnectivityThread.get(), TIMEOUT_MS);
     }
 
     private void waitForIdle(TestNetworkAgentWrapper agent, long timeoutMs) {
@@ -614,8 +614,8 @@
             // Waits for the NetworkAgent to be registered, which includes the creation of the
             // NetworkMonitor.
             waitForIdle(TIMEOUT_MS);
-            HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
-            HandlerUtilsKt.waitForIdle(ConnectivityThread.get(), TIMEOUT_MS);
+            HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
+            HandlerUtils.waitForIdle(ConnectivityThread.get(), TIMEOUT_MS);
         }
 
         @Override
@@ -7099,7 +7099,7 @@
                 mConnectivityDiagnosticsCallback, wifiRequest, mContext.getPackageName());
 
         // Block until all other events are done processing.
-        HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
+        HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
 
         verify(mIBinder).linkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt());
         verify(mConnectivityDiagnosticsCallback).asBinder();
@@ -7122,7 +7122,7 @@
                 mConnectivityDiagnosticsCallback, wifiRequest, mContext.getPackageName());
 
         // Block until all other events are done processing.
-        HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
+        HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
 
         verify(mIBinder).linkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt());
         verify(mConnectivityDiagnosticsCallback).asBinder();
@@ -7133,7 +7133,7 @@
                 mConnectivityDiagnosticsCallback, wifiRequest, mContext.getPackageName());
 
         // Block until all other events are done processing.
-        HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
+        HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
 
         assertTrue(mService.mConnectivityDiagnosticsCallbacks.containsKey(mIBinder));
     }
@@ -7285,7 +7285,7 @@
                 mConnectivityDiagnosticsCallback, request, mContext.getPackageName());
 
         // Block until all other events are done processing.
-        HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
+        HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
 
         verify(mConnectivityDiagnosticsCallback)
                 .onConnectivityReportAvailable(argThat(report -> {
@@ -7305,7 +7305,7 @@
                 mConnectivityDiagnosticsCallback, request, mContext.getPackageName());
 
         // Block until all other events are done processing.
-        HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
+        HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
 
         // Connect the cell agent verify that it notifies TestNetworkCallback that it is available
         final TestNetworkCallback callback = new TestNetworkCallback();
@@ -7322,7 +7322,7 @@
         setUpConnectivityDiagnosticsCallback();
 
         // Block until all other events are done processing.
-        HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
+        HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
 
         // Verify onConnectivityReport fired
         verify(mConnectivityDiagnosticsCallback).onConnectivityReportAvailable(
@@ -7343,7 +7343,7 @@
         mCellNetworkAgent.notifyDataStallSuspected();
 
         // Block until all other events are done processing.
-        HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
+        HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
 
         // Verify onDataStallSuspected fired
         verify(mConnectivityDiagnosticsCallback).onDataStallSuspected(
@@ -7364,7 +7364,7 @@
         mService.reportNetworkConnectivity(n, hasConnectivity);
 
         // Block until all other events are done processing.
-        HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
+        HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
 
         // Verify onNetworkConnectivityReported fired
         verify(mConnectivityDiagnosticsCallback)
@@ -7374,7 +7374,7 @@
         mService.reportNetworkConnectivity(n, noConnectivity);
 
         // Block until all other events are done processing.
-        HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
+        HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
 
         // Wait for onNetworkConnectivityReported to fire
         verify(mConnectivityDiagnosticsCallback)
diff --git a/tests/net/java/com/android/server/connectivity/DnsManagerTest.java b/tests/net/java/com/android/server/connectivity/DnsManagerTest.java
index 508b5cd9..753dbf8 100644
--- a/tests/net/java/com/android/server/connectivity/DnsManagerTest.java
+++ b/tests/net/java/com/android/server/connectivity/DnsManagerTest.java
@@ -26,9 +26,9 @@
 import static android.provider.Settings.Global.PRIVATE_DNS_MODE;
 import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER;
 
-import static com.android.testutils.MiscAssertsKt.assertContainsExactly;
-import static com.android.testutils.MiscAssertsKt.assertContainsStringsExactly;
-import static com.android.testutils.MiscAssertsKt.assertFieldCountEquals;
+import static com.android.testutils.MiscAsserts.assertContainsExactly;
+import static com.android.testutils.MiscAsserts.assertContainsStringsExactly;
+import static com.android.testutils.MiscAsserts.assertFieldCountEquals;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
diff --git a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
index aef9386..8ccea1a 100644
--- a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
+++ b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
@@ -19,7 +19,7 @@
 import static android.net.metrics.INetdEventListener.EVENT_GETADDRINFO;
 import static android.net.metrics.INetdEventListener.EVENT_GETHOSTBYNAME;
 
-import static com.android.testutils.MiscAssertsKt.assertStringContains;
+import static com.android.testutils.MiscAsserts.assertStringContains;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
diff --git a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
index a384687..ab12ac0 100644
--- a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
@@ -28,11 +28,17 @@
 import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_VENDOR;
 import static android.content.pm.PackageManager.GET_PERMISSIONS;
 import static android.content.pm.PackageManager.MATCH_ANY_USER;
+import static android.net.INetd.PERMISSION_INTERNET;
+import static android.net.INetd.PERMISSION_NONE;
+import static android.net.INetd.PERMISSION_SYSTEM;
+import static android.net.INetd.PERMISSION_UNINSTALLED;
+import static android.net.INetd.PERMISSION_UPDATE_DEVICE_STATS;
 import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
 import static android.os.Process.SYSTEM_UID;
 
 import static com.android.server.connectivity.PermissionMonitor.NETWORK;
 import static com.android.server.connectivity.PermissionMonitor.SYSTEM;
+import static com.android.server.connectivity.PermissionMonitor.UidNetdPermissionInfo;
 
 import static junit.framework.Assert.fail;
 
@@ -63,7 +69,7 @@
 import android.os.Build;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.util.SparseIntArray;
+import android.util.SparseArray;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -312,7 +318,7 @@
             // Add hook to verify and track result of setPermission.
             doAnswer((InvocationOnMock invocation) -> {
                 final Object[] args = invocation.getArguments();
-                final Boolean isSystem = args[0].equals(INetd.PERMISSION_SYSTEM);
+                final Boolean isSystem = args[0].equals(PERMISSION_SYSTEM);
                 for (final int uid : (int[]) args[1]) {
                     // TODO: Currently, permission monitor will send duplicate commands for each uid
                     // corresponding to each user. Need to fix that and uncomment below test.
@@ -555,39 +561,40 @@
         // SYSTEM_UID1: SYSTEM_PACKAGE1 has internet permission and update device stats permission.
         // SYSTEM_UID2: SYSTEM_PACKAGE2 has only update device stats permission.
 
-        SparseIntArray netdPermissionsAppIds = new SparseIntArray();
-        netdPermissionsAppIds.put(MOCK_UID1, INetd.PERMISSION_INTERNET);
-        netdPermissionsAppIds.put(MOCK_UID2, INetd.PERMISSION_NONE);
-        netdPermissionsAppIds.put(SYSTEM_UID1, INetd.PERMISSION_INTERNET
-                | INetd.PERMISSION_UPDATE_DEVICE_STATS);
-        netdPermissionsAppIds.put(SYSTEM_UID2, INetd.PERMISSION_UPDATE_DEVICE_STATS);
+        final SparseArray<UidNetdPermissionInfo> uidsPermInfo = new SparseArray<>();
+        uidsPermInfo.put(MOCK_UID1, new UidNetdPermissionInfo(PERMISSION_INTERNET));
+        uidsPermInfo.put(MOCK_UID2, new UidNetdPermissionInfo(PERMISSION_NONE));
+        uidsPermInfo.put(SYSTEM_UID1, new UidNetdPermissionInfo(
+                PERMISSION_INTERNET | PERMISSION_UPDATE_DEVICE_STATS));
+        uidsPermInfo.put(SYSTEM_UID2, new UidNetdPermissionInfo(PERMISSION_UPDATE_DEVICE_STATS));
 
         // Send the permission information to netd, expect permission updated.
-        mPermissionMonitor.sendPackagePermissionsToNetd(netdPermissionsAppIds);
+        mPermissionMonitor.sendPackagePermissionsToNetd(uidsPermInfo);
 
-        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET,
+        mNetdServiceMonitor.expectPermission(PERMISSION_INTERNET,
                 new int[]{MOCK_UID1});
-        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_NONE, new int[]{MOCK_UID2});
-        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET
-                | INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{SYSTEM_UID1});
-        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_UPDATE_DEVICE_STATS,
+        mNetdServiceMonitor.expectPermission(PERMISSION_NONE, new int[]{MOCK_UID2});
+        mNetdServiceMonitor.expectPermission(PERMISSION_INTERNET
+                | PERMISSION_UPDATE_DEVICE_STATS, new int[]{SYSTEM_UID1});
+        mNetdServiceMonitor.expectPermission(PERMISSION_UPDATE_DEVICE_STATS,
                 new int[]{SYSTEM_UID2});
 
         // Update permission of MOCK_UID1, expect new permission show up.
-        mPermissionMonitor.sendPackagePermissionsForUid(MOCK_UID1,
-                INetd.PERMISSION_INTERNET | INetd.PERMISSION_UPDATE_DEVICE_STATS);
-        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET
-                | INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
+        mPermissionMonitor.sendPackagePermissionsForUid(MOCK_UID1, new UidNetdPermissionInfo(
+                PERMISSION_INTERNET | PERMISSION_UPDATE_DEVICE_STATS));
+        mNetdServiceMonitor.expectPermission(PERMISSION_INTERNET
+                | PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
 
         // Change permissions of SYSTEM_UID2, expect new permission show up and old permission
         // revoked.
-        mPermissionMonitor.sendPackagePermissionsForUid(SYSTEM_UID2,
-                INetd.PERMISSION_INTERNET);
-        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET, new int[]{SYSTEM_UID2});
+        mPermissionMonitor.sendPackagePermissionsForUid(SYSTEM_UID2, new UidNetdPermissionInfo(
+                PERMISSION_INTERNET));
+        mNetdServiceMonitor.expectPermission(PERMISSION_INTERNET, new int[]{SYSTEM_UID2});
 
         // Revoke permission from SYSTEM_UID1, expect no permission stored.
-        mPermissionMonitor.sendPackagePermissionsForUid(SYSTEM_UID1, INetd.PERMISSION_NONE);
-        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_NONE, new int[]{SYSTEM_UID1});
+        mPermissionMonitor.sendPackagePermissionsForUid(SYSTEM_UID1, new UidNetdPermissionInfo(
+                PERMISSION_NONE));
+        mNetdServiceMonitor.expectPermission(PERMISSION_NONE, new int[]{SYSTEM_UID1});
     }
 
     private PackageInfo setPackagePermissions(String packageName, int uid, String[] permissions)
@@ -611,11 +618,11 @@
         final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService);
 
         addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {INTERNET, UPDATE_DEVICE_STATS});
-        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET
-                | INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
+        mNetdServiceMonitor.expectPermission(PERMISSION_INTERNET
+                | PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
 
         addPackage(MOCK_PACKAGE2, MOCK_UID2, new String[] {INTERNET});
-        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET, new int[]{MOCK_UID2});
+        mNetdServiceMonitor.expectPermission(PERMISSION_INTERNET, new int[]{MOCK_UID2});
     }
 
     @Test
@@ -623,8 +630,8 @@
         final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService);
 
         addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {INTERNET, UPDATE_DEVICE_STATS});
-        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET
-                | INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
+        mNetdServiceMonitor.expectPermission(PERMISSION_INTERNET
+                | PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
 
         // Install another package with the same uid and no permissions should not cause the UID to
         // lose permissions.
@@ -633,8 +640,8 @@
         when(mPackageManager.getPackagesForUid(MOCK_UID1))
               .thenReturn(new String[]{MOCK_PACKAGE1, MOCK_PACKAGE2});
         mPermissionMonitor.onPackageAdded(MOCK_PACKAGE2, MOCK_UID1);
-        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET
-                | INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
+        mNetdServiceMonitor.expectPermission(PERMISSION_INTERNET
+                | PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
     }
 
     @Test
@@ -642,12 +649,12 @@
         final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService);
 
         addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {INTERNET, UPDATE_DEVICE_STATS});
-        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET
-                | INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
+        mNetdServiceMonitor.expectPermission(PERMISSION_INTERNET
+                | PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
 
         when(mPackageManager.getPackagesForUid(MOCK_UID1)).thenReturn(new String[]{});
         mPermissionMonitor.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID1);
-        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_UNINSTALLED, new int[]{MOCK_UID1});
+        mNetdServiceMonitor.expectPermission(PERMISSION_UNINSTALLED, new int[]{MOCK_UID1});
     }
 
     @Test
@@ -655,16 +662,16 @@
         final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService);
 
         addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {INTERNET, UPDATE_DEVICE_STATS});
-        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET
-                | INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
+        mNetdServiceMonitor.expectPermission(PERMISSION_INTERNET
+                | PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
 
         when(mPackageManager.getPackagesForUid(MOCK_UID1)).thenReturn(new String[]{});
         removeAllPermissions(MOCK_UID1);
         mPermissionMonitor.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID1);
-        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_UNINSTALLED, new int[]{MOCK_UID1});
+        mNetdServiceMonitor.expectPermission(PERMISSION_UNINSTALLED, new int[]{MOCK_UID1});
 
         addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {INTERNET});
-        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET, new int[]{MOCK_UID1});
+        mNetdServiceMonitor.expectPermission(PERMISSION_INTERNET, new int[]{MOCK_UID1});
     }
 
     @Test
@@ -672,10 +679,10 @@
         final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService);
 
         addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {});
-        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_NONE, new int[]{MOCK_UID1});
+        mNetdServiceMonitor.expectPermission(PERMISSION_NONE, new int[]{MOCK_UID1});
 
         addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {INTERNET});
-        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET, new int[]{MOCK_UID1});
+        mNetdServiceMonitor.expectPermission(PERMISSION_INTERNET, new int[]{MOCK_UID1});
     }
 
     @Test
@@ -683,8 +690,8 @@
         final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService);
 
         addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {INTERNET, UPDATE_DEVICE_STATS});
-        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET
-                | INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
+        mNetdServiceMonitor.expectPermission(PERMISSION_INTERNET
+                | PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
 
         // Mock another package with the same uid but different permissions.
         final PackageInfo packageInfo2 = buildPackageInfo(PARTITION_SYSTEM, MOCK_UID1, MOCK_USER1);
@@ -695,7 +702,7 @@
         addPermissions(MOCK_UID1, INTERNET);
 
         mPermissionMonitor.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID1);
-        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET, new int[]{MOCK_UID1});
+        mNetdServiceMonitor.expectPermission(PERMISSION_INTERNET, new int[]{MOCK_UID1});
     }
 
     @Test
diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index de1c575..e8c4ee9 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -270,12 +270,12 @@
     }
 
     @Test
-    public void testUidWhiteAndBlacklist() throws Exception {
+    public void testUidAllowAndDenylist() throws Exception {
         final Vpn vpn = createVpn(primaryUser.id);
         final UidRange user = UidRange.createForUser(primaryUser.id);
         final String[] packages = {PKGS[0], PKGS[1], PKGS[2]};
 
-        // Whitelist
+        // Allowed list
         final Set<UidRange> allow = vpn.createUserAndRestrictedProfilesRanges(primaryUser.id,
                 Arrays.asList(packages), null);
         assertEquals(new ArraySet<>(Arrays.asList(new UidRange[] {
@@ -283,7 +283,7 @@
             new UidRange(user.start + PKG_UIDS[1], user.start + PKG_UIDS[2])
         })), allow);
 
-        // Blacklist
+        // Denied list
         final Set<UidRange> disallow = vpn.createUserAndRestrictedProfilesRanges(primaryUser.id,
                 null, Arrays.asList(packages));
         assertEquals(new ArraySet<>(Arrays.asList(new UidRange[] {
@@ -354,11 +354,11 @@
     }
 
     @Test
-    public void testLockdownWhitelist() throws Exception {
+    public void testLockdownAllowlist() throws Exception {
         final Vpn vpn = createVpn(primaryUser.id);
         final UidRange user = UidRange.createForUser(primaryUser.id);
 
-        // Set always-on with lockdown and whitelist app PKGS[2] from lockdown.
+        // Set always-on with lockdown and allow app PKGS[2] from lockdown.
         assertTrue(vpn.setAlwaysOnPackage(
                 PKGS[1], true, Collections.singletonList(PKGS[2]), mKeyStore));
         verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
@@ -368,7 +368,7 @@
         assertBlocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[3]);
         assertUnblocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[2]);
 
-        // Change whitelisted app to PKGS[3].
+        // Change allowed app list to PKGS[3].
         assertTrue(vpn.setAlwaysOnPackage(
                 PKGS[1], true, Collections.singletonList(PKGS[3]), mKeyStore));
         verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
@@ -395,7 +395,7 @@
         assertBlocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[2]);
         assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[3]);
 
-        // Remove the whitelist.
+        // Remove the list of allowed packages.
         assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, null, mKeyStore));
         verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
                 new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[3] - 1),
@@ -408,7 +408,7 @@
                 user.start + PKG_UIDS[3]);
         assertUnblocked(vpn, user.start + PKG_UIDS[0]);
 
-        // Add the whitelist.
+        // Add the list of allowed packages.
         assertTrue(vpn.setAlwaysOnPackage(
                 PKGS[0], true, Collections.singletonList(PKGS[1]), mKeyStore));
         verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
@@ -421,12 +421,12 @@
         assertBlocked(vpn, user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
         assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1]);
 
-        // Try whitelisting a package with a comma, should be rejected.
+        // Try allowing a package with a comma, should be rejected.
         assertFalse(vpn.setAlwaysOnPackage(
                 PKGS[0], true, Collections.singletonList("a.b,c.d"), mKeyStore));
 
-        // Pass a non-existent packages in the whitelist, they (and only they) should be ignored.
-        // Whitelisted package should change from PGKS[1] to PKGS[2].
+        // Pass a non-existent packages in the allowlist, they (and only they) should be ignored.
+        // allowed package should change from PGKS[1] to PKGS[2].
         assertTrue(vpn.setAlwaysOnPackage(
                 PKGS[0], true, Arrays.asList("com.foo.app", PKGS[2], "com.bar.app"), mKeyStore));
         verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[]{
diff --git a/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java b/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java
index e83d2a9..fb0cfc0 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java
@@ -28,7 +28,7 @@
 import static android.text.format.DateUtils.HOUR_IN_MILLIS;
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
 
-import static com.android.testutils.MiscAssertsKt.assertThrows;
+import static com.android.testutils.MiscAsserts.assertThrows;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
diff --git a/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java b/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java
index a6f7a36..291efc7 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java
@@ -53,7 +53,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.net.NetworkStatsServiceTest.LatchedHandler;
-import com.android.testutils.HandlerUtilsKt;
+import com.android.testutils.HandlerUtils;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -440,7 +440,7 @@
     }
 
     private void waitForObserverToIdle() {
-        HandlerUtilsKt.waitForIdle(mObserverHandlerThread, WAIT_TIMEOUT_MS);
-        HandlerUtilsKt.waitForIdle(mHandler, WAIT_TIMEOUT_MS);
+        HandlerUtils.waitForIdle(mObserverHandlerThread, WAIT_TIMEOUT_MS);
+        HandlerUtils.waitForIdle(mHandler, WAIT_TIMEOUT_MS);
     }
 }
diff --git a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
index 1307a84..7abe189 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -109,7 +109,7 @@
 import com.android.internal.util.test.BroadcastInterceptingContext;
 import com.android.server.net.NetworkStatsService.NetworkStatsSettings;
 import com.android.server.net.NetworkStatsService.NetworkStatsSettings.Config;
-import com.android.testutils.HandlerUtilsKt;
+import com.android.testutils.HandlerUtils;
 import com.android.testutils.TestableNetworkStatsProviderBinder;
 
 import libcore.io.IoUtils;
@@ -700,7 +700,7 @@
         when(mNetworkStatsSubscriptionsMonitor.getRatTypeForSubscriberId(anyString()))
                 .thenReturn(ratType);
         mService.handleOnCollapsedRatTypeChanged();
-        HandlerUtilsKt.waitForIdle(mHandlerThread, WAIT_TIMEOUT);
+        HandlerUtils.waitForIdle(mHandlerThread, WAIT_TIMEOUT);
     }
 
     @Test
@@ -1065,7 +1065,7 @@
         long minThresholdInBytes = 2 * 1024 * 1024; // 2 MB
         assertEquals(minThresholdInBytes, request.thresholdInBytes);
 
-        HandlerUtilsKt.waitForIdle(mHandlerThread, WAIT_TIMEOUT);
+        HandlerUtils.waitForIdle(mHandlerThread, WAIT_TIMEOUT);
 
         // Make sure that the caller binder gets connected
         verify(mBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
@@ -1203,7 +1203,7 @@
 
         // Simulates alert quota of the provider has been reached.
         cb.notifyAlertReached();
-        HandlerUtilsKt.waitForIdle(mHandlerThread, WAIT_TIMEOUT);
+        HandlerUtils.waitForIdle(mHandlerThread, WAIT_TIMEOUT);
 
         // Verifies that polling is triggered by alert reached.
         provider.expectOnRequestStatsUpdate(0 /* unused */);
@@ -1264,7 +1264,7 @@
         // Call handleOnCollapsedRatTypeChanged manually to simulate the callback fired
         // when stopping monitor, this is needed by NetworkStatsService to trigger updateIfaces.
         mService.handleOnCollapsedRatTypeChanged();
-        HandlerUtilsKt.waitForIdle(mHandlerThread, WAIT_TIMEOUT);
+        HandlerUtils.waitForIdle(mHandlerThread, WAIT_TIMEOUT);
         // Create some traffic.
         incrementCurrentTime(MINUTE_IN_MILLIS);
         // Append more traffic on existing snapshot.
@@ -1286,7 +1286,7 @@
         setCombineSubtypeEnabled(false);
 
         mService.handleOnCollapsedRatTypeChanged();
-        HandlerUtilsKt.waitForIdle(mHandlerThread, WAIT_TIMEOUT);
+        HandlerUtils.waitForIdle(mHandlerThread, WAIT_TIMEOUT);
         // Create some traffic.
         incrementCurrentTime(MINUTE_IN_MILLIS);
         // Append more traffic on existing snapshot.
@@ -1520,7 +1520,7 @@
     }
 
     private void waitForIdle() {
-        HandlerUtilsKt.waitForIdle(mHandlerThread, WAIT_TIMEOUT);
+        HandlerUtils.waitForIdle(mHandlerThread, WAIT_TIMEOUT);
     }
 
     static class LatchedHandler extends Handler {
diff --git a/tests/utils/hostutils/src/com/android/internal/util/test/SystemPreparer.java b/tests/utils/hostutils/src/com/android/internal/util/test/SystemPreparer.java
index c2a5459..f80af03 100644
--- a/tests/utils/hostutils/src/com/android/internal/util/test/SystemPreparer.java
+++ b/tests/utils/hostutils/src/com/android/internal/util/test/SystemPreparer.java
@@ -62,12 +62,22 @@
     private final RebootStrategy mRebootStrategy;
     private final TearDownRule mTearDownRule;
 
+    // When debugging, it may be useful to run a test case without rebooting the device afterwards,
+    // to manually verify the device state.
+    private boolean mDebugSkipAfterReboot;
+
     public SystemPreparer(TemporaryFolder hostTempFolder, DeviceProvider deviceProvider) {
         this(hostTempFolder, RebootStrategy.FULL, null, deviceProvider);
     }
 
     public SystemPreparer(TemporaryFolder hostTempFolder, RebootStrategy rebootStrategy,
             @Nullable TestRuleDelegate testRuleDelegate, DeviceProvider deviceProvider) {
+        this(hostTempFolder, rebootStrategy, testRuleDelegate, false, deviceProvider);
+    }
+
+    public SystemPreparer(TemporaryFolder hostTempFolder, RebootStrategy rebootStrategy,
+            @Nullable TestRuleDelegate testRuleDelegate, boolean debugSkipAfterReboot,
+            DeviceProvider deviceProvider) {
         mHostTempFolder = hostTempFolder;
         mDeviceProvider = deviceProvider;
         mRebootStrategy = rebootStrategy;
@@ -75,6 +85,7 @@
         if (testRuleDelegate != null) {
             testRuleDelegate.setDelegate(mTearDownRule);
         }
+        mDebugSkipAfterReboot = debugSkipAfterReboot;
     }
 
     /** Copies a file within the host test jar to a path on device. */
@@ -172,7 +183,9 @@
             case USERSPACE_UNTIL_ONLINE:
                 device.rebootUserspaceUntilOnline();
                 break;
-            case START_STOP:
+            // TODO(b/159540015): Make this START_STOP instead of default once it's fixed. Can't
+            //  currently be done because START_STOP is commented out.
+            default:
                 device.executeShellCommand("stop");
                 device.executeShellCommand("start");
                 ITestDevice.RecoveryMode cachedRecoveryMode = device.getRecoveryMode();
@@ -228,7 +241,9 @@
             for (final String packageName : mInstalledPackages) {
                 device.uninstallPackage(packageName);
             }
-            reboot();
+            if (!mDebugSkipAfterReboot) {
+                reboot();
+            }
         } catch (DeviceNotAvailableException e) {
             Assert.fail(e.toString());
         }
@@ -355,6 +370,7 @@
     /**
      * How to reboot the device. Ordered from slowest to fastest.
      */
+    @SuppressWarnings("DanglingJavadoc")
     public enum RebootStrategy {
         /** @see ITestDevice#reboot() */
         FULL,
@@ -374,7 +390,15 @@
          *
          * TODO(b/159540015): There's a bug with this causing unnecessary disk space usage, which
          *  can eventually lead to an insufficient storage space error.
+         *
+         * This can be uncommented for local development, but should be left out when merging.
+         * It is done this way to hopefully be caught by code review, since merging this will
+         * break all of postsubmit. But the nearly 50% reduction in test runtime is worth having
+         * this option exist.
+         *
+         * @deprecated do not use this in merged code until bug is resolved
          */
-        START_STOP
+//        @Deprecated
+//        START_STOP
     }
 }
diff --git a/tools/hiddenapi/generate_hiddenapi_lists.py b/tools/hiddenapi/generate_hiddenapi_lists.py
index 8a282e5..da64402 100755
--- a/tools/hiddenapi/generate_hiddenapi_lists.py
+++ b/tools/hiddenapi/generate_hiddenapi_lists.py
@@ -34,26 +34,6 @@
 FLAG_SYSTEM_API = 'system-api'
 FLAG_TEST_API = 'test-api'
 
-OLD_FLAG_SDK = "whitelist"
-OLD_FLAG_UNSUPPORTED = "greylist"
-OLD_FLAG_BLOCKED = "blacklist"
-OLD_FLAG_MAX_TARGET_O = "greylist-max-o"
-OLD_FLAG_MAX_TARGET_P = "greylist-max-p"
-OLD_FLAG_MAX_TARGET_Q = "greylist-max-q"
-OLD_FLAG_MAX_TARGET_R = "greylist-max-r"
-
-OLD_FLAGS_TO_NEW = {
-    OLD_FLAG_SDK: FLAG_SDK,
-    OLD_FLAG_UNSUPPORTED: FLAG_UNSUPPORTED,
-    OLD_FLAG_BLOCKED: FLAG_BLOCKED,
-    OLD_FLAG_MAX_TARGET_O: FLAG_MAX_TARGET_O,
-    OLD_FLAG_MAX_TARGET_P: FLAG_MAX_TARGET_P,
-    OLD_FLAG_MAX_TARGET_Q: FLAG_MAX_TARGET_Q,
-    OLD_FLAG_MAX_TARGET_R: FLAG_MAX_TARGET_R,
-}
-
-NEW_FLAGS_TO_OLD = dict(zip(OLD_FLAGS_TO_NEW.values(), OLD_FLAGS_TO_NEW.keys()))
-
 # List of all known flags.
 FLAGS_API_LIST = [
     FLAG_SDK,
@@ -205,36 +185,6 @@
             "Please visit go/hiddenapi for more information.").format(
                 source, "\n".join(flags_subset - ALL_FLAGS_SET))
 
-    def convert_to_new_flag(self, flag):
-      """Converts old flag to a new variant.
-
-      Flags that are considered old are replaced with new versions.
-      Otherwise, it is a no-op.
-
-      Args:
-        flag: a string, representing SDK flag.
-
-      Returns:
-         A string. Result of conversion.
-
-      """
-      return OLD_FLAGS_TO_NEW.get(flag, flag)
-
-    def convert_to_old_flag(self, flag):
-      """Converts a new flag to a old variant.
-
-      No-op if there is no suitable old flag.
-      Only used to support backwards compatibility.
-
-      Args:
-        flag: a string, representing SDK flag.
-
-      Returns:
-         A string. Result of conversion.
-
-      """
-      return NEW_FLAGS_TO_OLD.get(flag, flag)
-
     def filter_apis(self, filter_fn):
         """Returns APIs which match a given predicate.
 
@@ -272,7 +222,7 @@
         """
         lines = []
         for api in self._dict:
-          flags = sorted([self.convert_to_old_flag(flag) for flag in self._dict[api]])
+          flags = sorted(self._dict[api])
           lines.append(",".join([api] + flags))
         return sorted(lines)
 
@@ -298,12 +248,12 @@
         # Check that all flags are known.
         csv_flags = set()
         for csv in csv_values:
-          csv_flags.update([self.convert_to_new_flag(flag) for flag in csv[1:]])
+          csv_flags.update(csv[1:])
         self._check_flags_set(csv_flags, source)
 
         # Iterate over all CSV lines, find entry in dict and append flags to it.
         for csv in csv_values:
-            flags = [self.convert_to_new_flag(flag) for flag in csv[1:]]
+            flags = csv[1:]
             if (FLAG_PUBLIC_API in flags) or (FLAG_SYSTEM_API in flags):
                 flags.append(FLAG_SDK)
             self._dict[csv[0]].update(flags)
diff --git a/tools/hiddenapi/generate_hiddenapi_lists_test.py b/tools/hiddenapi/generate_hiddenapi_lists_test.py
index 321c400..82d117f 100755
--- a/tools/hiddenapi/generate_hiddenapi_lists_test.py
+++ b/tools/hiddenapi/generate_hiddenapi_lists_test.py
@@ -35,7 +35,7 @@
         flags.parse_and_merge_csv(['A,' + FLAG_SDK, 'B', 'C'])
         flags.assign_flag(FLAG_UNSUPPORTED, set(['C']))
         self.assertEqual(flags.generate_csv(),
-            [ 'A,' + OLD_FLAG_SDK, 'B', 'C,' + OLD_FLAG_UNSUPPORTED ])
+            [ 'A,' + FLAG_SDK, 'B', 'C,' + FLAG_UNSUPPORTED ])
 
         # Check three things:
         # (1) B is selected as valid unassigned
@@ -50,8 +50,7 @@
         # Test empty CSV entry.
         self.assertEqual(flags.generate_csv(), [])
 
-        # Test new additions. CSV generator produces values with old flags
-        # to be backwards compatible.
+        # Test new additions.
         flags.parse_and_merge_csv([
             'A,' + FLAG_UNSUPPORTED,
             'B,' + FLAG_BLOCKED + ',' + FLAG_MAX_TARGET_O,
@@ -60,11 +59,11 @@
             'E,' + FLAG_BLOCKED + ',' + FLAG_TEST_API,
         ])
         self.assertEqual(flags.generate_csv(), [
-            'A,' + OLD_FLAG_UNSUPPORTED,
-            'B,' + OLD_FLAG_BLOCKED + "," + OLD_FLAG_MAX_TARGET_O,
-            'C,' + FLAG_SYSTEM_API + ',' + OLD_FLAG_SDK,
-            'D,' + OLD_FLAG_UNSUPPORTED + ',' + FLAG_TEST_API,
-            'E,' + OLD_FLAG_BLOCKED + ',' + FLAG_TEST_API,
+            'A,' + FLAG_UNSUPPORTED,
+            'B,' + FLAG_BLOCKED + "," + FLAG_MAX_TARGET_O,
+            'C,' + FLAG_SYSTEM_API + ',' + FLAG_SDK,
+            'D,' + FLAG_UNSUPPORTED + ',' + FLAG_TEST_API,
+            'E,' + FLAG_BLOCKED + ',' + FLAG_TEST_API,
         ])
 
         # Test unknown flag.
@@ -78,7 +77,7 @@
         # Test new additions.
         flags.assign_flag(FLAG_UNSUPPORTED, set([ 'A', 'B' ]))
         self.assertEqual(flags.generate_csv(),
-            [ 'A,' + OLD_FLAG_UNSUPPORTED + "," + OLD_FLAG_SDK, 'B,' + OLD_FLAG_UNSUPPORTED ])
+            [ 'A,' + FLAG_UNSUPPORTED + "," + FLAG_SDK, 'B,' + FLAG_UNSUPPORTED ])
 
         # Test invalid API signature.
         with self.assertRaises(AssertionError):
diff --git a/wifi/java/android/net/wifi/SoftApConfiguration.java b/wifi/java/android/net/wifi/SoftApConfiguration.java
index f919ea4..393fe8d 100644
--- a/wifi/java/android/net/wifi/SoftApConfiguration.java
+++ b/wifi/java/android/net/wifi/SoftApConfiguration.java
@@ -582,6 +582,7 @@
                 wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
                 break;
             case SECURITY_TYPE_WPA2_PSK:
+            case SECURITY_TYPE_WPA3_SAE_TRANSITION:
                 wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA2_PSK);
                 break;
             default:
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
index fa806e7..282757a 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
+++ b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
@@ -448,6 +448,16 @@
                     return new UserCredential[size];
                 }
             };
+
+        /**
+         * Get a unique identifier for UserCredential.
+         *
+         * @hide
+         * @return a Unique identifier for a UserCredential object
+         */
+        public int getUniqueId() {
+            return Objects.hash(mUsername);
+        }
     }
     private UserCredential mUserCredential = null;
     /**
@@ -1037,7 +1047,8 @@
      * @return a Unique identifier for a Credential object
      */
     public int getUniqueId() {
-        return Objects.hash(mUserCredential, mCertCredential, mSimCredential, mRealm);
+        return Objects.hash(mUserCredential != null ? mUserCredential.getUniqueId() : 0,
+                mCertCredential, mSimCredential, mRealm);
     }
 
     @Override
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java b/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java
index 224c4be..8f34579 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java
+++ b/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java
@@ -313,9 +313,7 @@
      * @return a Unique identifier for a HomeSp object
      */
     public int getUniqueId() {
-        return Objects.hash(mFqdn, mFriendlyName, mHomeNetworkIds, Arrays.hashCode(mMatchAllOis),
-                Arrays.hashCode(mMatchAnyOis), Arrays.hashCode(mOtherHomePartners),
-                Arrays.hashCode(mRoamingConsortiumOis));
+        return Objects.hash(mFqdn);
     }
 
 
diff --git a/wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl b/wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl
index bfdd45d..fd89d3b 100644
--- a/wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl
+++ b/wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl
@@ -25,7 +25,7 @@
  */
 interface IWifiP2pManager
 {
-    Messenger getMessenger(in IBinder binder);
+    Messenger getMessenger(in IBinder binder, in String packageName);
     Messenger getP2pStateMachineMessenger();
     oneway void close(in IBinder binder);
     void setMiracastMode(int mode);
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
index 724ccf0..ad38c5a 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
@@ -1156,8 +1156,8 @@
      */
     public Channel initialize(Context srcContext, Looper srcLooper, ChannelListener listener) {
         Binder binder = new Binder();
-        Channel channel = initalizeChannel(srcContext, srcLooper, listener, getMessenger(binder),
-                binder);
+        Channel channel = initializeChannel(srcContext, srcLooper, listener,
+                getMessenger(binder, srcContext.getOpPackageName()), binder);
         return channel;
     }
 
@@ -1167,12 +1167,12 @@
      */
     public Channel initializeInternal(Context srcContext, Looper srcLooper,
                                       ChannelListener listener) {
-        return initalizeChannel(srcContext, srcLooper, listener, getP2pStateMachineMessenger(),
+        return initializeChannel(srcContext, srcLooper, listener, getP2pStateMachineMessenger(),
                 null);
     }
 
-    private Channel initalizeChannel(Context srcContext, Looper srcLooper, ChannelListener listener,
-                                     Messenger messenger, Binder binder) {
+    private Channel initializeChannel(Context srcContext, Looper srcLooper,
+            ChannelListener listener, Messenger messenger, Binder binder) {
         if (messenger == null) return null;
 
         Channel c = new Channel(srcContext, srcLooper, listener, binder, this);
@@ -1814,6 +1814,14 @@
         }
     }
 
+    private Messenger getMessenger(@NonNull Binder binder, @Nullable String packageName) {
+        try {
+            return mService.getMessenger(binder, packageName);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     /**
      * Get a reference to WifiP2pService handler. This is used to establish
      * an AsyncChannel communication with WifiService
@@ -1824,11 +1832,8 @@
      * @hide
      */
     public Messenger getMessenger(Binder binder) {
-        try {
-            return mService.getMessenger(binder);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        // No way to determine package name in this case.
+        return getMessenger(binder, null);
     }
 
     /**
diff --git a/wifi/tests/src/android/net/wifi/FakeKeys.java b/wifi/tests/src/android/net/wifi/FakeKeys.java
index c0d60c3..641b891 100644
--- a/wifi/tests/src/android/net/wifi/FakeKeys.java
+++ b/wifi/tests/src/android/net/wifi/FakeKeys.java
@@ -214,6 +214,35 @@
     };
     public static final PrivateKey RSA_KEY1 = loadPrivateRSAKey(FAKE_RSA_KEY_1);
 
+    private static final String CLIENT_SUITE_B_RSA3072_CERT_STRING =
+            "-----BEGIN CERTIFICATE-----\n"
+                    + "MIIERzCCAq8CFDopjyNgaj+c2TN2k06h7okEWpHJMA0GCSqGSIb3DQEBDAUAMF4x\n"
+                    + "CzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEMMAoGA1UEBwwDTVRWMRAwDgYDVQQK\n"
+                    + "DAdBbmRyb2lkMQ4wDAYDVQQLDAVXaS1GaTESMBAGA1UEAwwJdW5pdGVzdENBMB4X\n"
+                    + "DTIwMDcyMTAyMjkxMVoXDTMwMDUzMDAyMjkxMVowYjELMAkGA1UEBhMCVVMxCzAJ\n"
+                    + "BgNVBAgMAkNBMQwwCgYDVQQHDANNVFYxEDAOBgNVBAoMB0FuZHJvaWQxDjAMBgNV\n"
+                    + "BAsMBVdpLUZpMRYwFAYDVQQDDA11bml0ZXN0Q2xpZW50MIIBojANBgkqhkiG9w0B\n"
+                    + "AQEFAAOCAY8AMIIBigKCAYEAwSK3C5K5udtCKTnE14e8z2cZvwmB4Xe+a8+7QLud\n"
+                    + "Hooc/lQzClgK4MbVUC0D3FE+U32C78SxKoTaRWtvPmNm+UaFT8KkwyUno/dv+2XD\n"
+                    + "pd/zARQ+3FwAfWopAhEyCVSxwsCa+slQ4juRIMIuUC1Mm0NaptZyM3Tj/ICQEfpk\n"
+                    + "o9qVIbiK6eoJMTkY8EWfAn7RTFdfR1OLuO0mVOjgLW9/+upYv6hZ19nAMAxw4QTJ\n"
+                    + "x7lLwALX7B+tDYNEZHDqYL2zyvQWAj2HClere8QYILxkvktgBg2crEJJe4XbDH7L\n"
+                    + "A3rrXmsiqf1ZbfFFEzK9NFqovL+qGh+zIP+588ShJFO9H/RDnDpiTnAFTWXQdTwg\n"
+                    + "szSS0Vw2PB+JqEABAa9DeMvXT1Oy+NY3ItPHyy63nQZVI2rXANw4NhwS0Z6DF+Qs\n"
+                    + "TNrj+GU7e4SG/EGR8SvldjYfQTWFLg1l/UT1hOOkQZwdsaW1zgKyeuiFB2KdMmbA\n"
+                    + "Sq+Ux1L1KICo0IglwWcB/8nnAgMBAAEwDQYJKoZIhvcNAQEMBQADggGBAMYwJkNw\n"
+                    + "BaCviKFmReDTMwWPRy4AMNViEeqAXgERwDEKwM7efjsaj5gctWfKsxX6UdLzkhgg\n"
+                    + "6S/T6PxVWKzJ6l7SoOuTa6tMQOZp+h3R1mdfEQbw8B5cXBxZ+batzAai6Fiy1FKS\n"
+                    + "/ka3INbcGfYuIYghfTrb4/NJKN06ZaQ1bpPwq0e4gN7800T2nbawvSf7r+8ZLcG3\n"
+                    + "6bGCjRMwDSIipNvOwoj3TG315XC7TccX5difQ4sKOY+d2MkVJ3RiO0Ciw2ZbEW8d\n"
+                    + "1FH5vUQJWnBUfSFznosGzLwH3iWfqlP+27jNE+qB2igEwCRFgVAouURx5ou43xuX\n"
+                    + "qf6JkdI3HTJGLIWxkp7gOeln4dEaYzKjYw+P0VqJvKVqQ0IXiLjHgE0J9p0vgyD6\n"
+                    + "HVVcP7U8RgqrbIjL1QgHU4KBhGi+WSUh/mRplUCNvHgcYdcHi/gHpj/j6ubwqIGV\n"
+                    + "z4iSolAHYTmBWcLyE0NgpzE6ntp+53r2KaUJA99l2iGVzbWTwqPSm0XAVw==\n"
+                    + "-----END CERTIFICATE-----\n";
+    public static final X509Certificate CLIENT_SUITE_B_RSA3072_CERT =
+            loadCertificate(CLIENT_SUITE_B_RSA3072_CERT_STRING);
+
     private static X509Certificate loadCertificate(String blob) {
         try {
             final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
diff --git a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
index c2d0d6d..254434b 100644
--- a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
@@ -310,12 +310,6 @@
                 .build();
 
         assertNull(band_6g_config.toWifiConfiguration());
-        SoftApConfiguration sae_transition_config = new SoftApConfiguration.Builder()
-                .setPassphrase("secretsecret",
-                        SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION)
-                .build();
-
-        assertNull(sae_transition_config.toWifiConfiguration());
     }
 
     @Test
@@ -358,5 +352,16 @@
         assertThat(wifiConfig_2g5g.apBand).isEqualTo(WifiConfiguration.AP_BAND_ANY);
         assertThat(wifiConfig_2g5g.apChannel).isEqualTo(0);
         assertThat(wifiConfig_2g5g.hiddenSSID).isEqualTo(true);
+
+        SoftApConfiguration softApConfig_sae_transition = new SoftApConfiguration.Builder()
+                .setPassphrase("secretsecret",
+                SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION)
+                .build();
+
+        WifiConfiguration wifiConfig_sae_transition =
+                softApConfig_sae_transition.toWifiConfiguration();
+        assertThat(wifiConfig_sae_transition.getAuthType())
+                .isEqualTo(WifiConfiguration.KeyMgmt.WPA2_PSK);
+        assertThat(wifiConfig_sae_transition.preSharedKey).isEqualTo("secretsecret");
     }
 }
diff --git a/wifi/tests/src/android/net/wifi/WifiClientTest.java b/wifi/tests/src/android/net/wifi/WifiClientTest.java
index 42cab55..7a3baf9 100644
--- a/wifi/tests/src/android/net/wifi/WifiClientTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiClientTest.java
@@ -16,8 +16,8 @@
 
 package android.net.wifi;
 
-import static com.android.testutils.MiscAssertsKt.assertFieldCountEquals;
-import static com.android.testutils.ParcelUtilsKt.assertParcelSane;
+import static com.android.testutils.MiscAsserts.assertFieldCountEquals;
+import static com.android.testutils.ParcelUtils.assertParcelSane;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
index 638efb9..8270d64 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
@@ -23,6 +23,8 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
+import android.net.wifi.EAPConstants;
+import android.net.wifi.FakeKeys;
 import android.net.wifi.hotspot2.pps.Credential;
 import android.net.wifi.hotspot2.pps.HomeSp;
 import android.os.Parcel;
@@ -32,6 +34,11 @@
 import org.junit.Test;
 
 import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
@@ -383,19 +390,39 @@
     }
 
     /**
-     * Verify that the unique identifier generated is different for two instances with different
-     * HomeSp node
+     * Verify that the unique identifier generated is the same for two instances with different
+     * HomeSp node but same FQDN
      *
      * @throws Exception
      */
     @Test
-    public void validateUniqueIdDifferentHomeSp() throws Exception {
+    public void validateUniqueIdDifferentHomeSpSameFqdn() throws Exception {
         PasspointConfiguration config1 = PasspointTestUtils.createConfig();
 
-        // Modify config2's RCOIs to a different set of values
+        // Modify config2's RCOIs and friendly name to a different set of values
         PasspointConfiguration config2 = PasspointTestUtils.createConfig();
         HomeSp homeSp = config2.getHomeSp();
         homeSp.setRoamingConsortiumOis(new long[] {0xaa, 0xbb});
+        homeSp.setFriendlyName("Some other name");
+        config2.setHomeSp(homeSp);
+
+        assertEquals(config1.getUniqueId(), config2.getUniqueId());
+    }
+
+    /**
+     * Verify that the unique identifier generated is different for two instances with the same
+     * HomeSp node but different FQDN
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUniqueIdSameHomeSpDifferentFqdn() throws Exception {
+        PasspointConfiguration config1 = PasspointTestUtils.createConfig();
+
+        // Modify config2's FQDN to a different value
+        PasspointConfiguration config2 = PasspointTestUtils.createConfig();
+        HomeSp homeSp = config2.getHomeSp();
+        homeSp.setFqdn("fqdn2.com");
         config2.setHomeSp(homeSp);
 
         assertNotEquals(config1.getUniqueId(), config2.getUniqueId());
@@ -403,15 +430,15 @@
 
     /**
      * Verify that the unique identifier generated is different for two instances with different
-     * Credential node
+     * SIM Credential node
      *
      * @throws Exception
      */
     @Test
-    public void validateUniqueIdDifferentCredential() throws Exception {
+    public void validateUniqueIdDifferentSimCredential() throws Exception {
         PasspointConfiguration config1 = PasspointTestUtils.createConfig();
 
-        // Modify config2's RCOIs to a different set of values
+        // Modify config2's realm and SIM credential to a different set of values
         PasspointConfiguration config2 = PasspointTestUtils.createConfig();
         Credential credential = config2.getCredential();
         credential.setRealm("realm2.example.com");
@@ -422,6 +449,157 @@
     }
 
     /**
+     * Verify that the unique identifier generated is different for two instances with different
+     * Realm in the Credential node
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUniqueIdDifferentRealm() throws Exception {
+        PasspointConfiguration config1 = PasspointTestUtils.createConfig();
+
+        // Modify config2's realm to a different set of values
+        PasspointConfiguration config2 = PasspointTestUtils.createConfig();
+        Credential credential = config2.getCredential();
+        credential.setRealm("realm2.example.com");
+        config2.setCredential(credential);
+
+        assertNotEquals(config1.getUniqueId(), config2.getUniqueId());
+    }
+
+    /**
+     * Verify that the unique identifier generated is the same for two instances with different
+     * password and same username in the User Credential node
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUniqueIdSameUserInUserCredential() throws Exception {
+        PasspointConfiguration config1 = PasspointTestUtils.createConfig();
+        Credential credential = createCredentialWithUserCredential("user", "passwd");
+        config1.setCredential(credential);
+
+        // Modify config2's Passpowrd to a different set of values
+        PasspointConfiguration config2 = PasspointTestUtils.createConfig();
+        credential = createCredentialWithUserCredential("user", "newpasswd");
+        config2.setCredential(credential);
+
+        assertEquals(config1.getUniqueId(), config2.getUniqueId());
+    }
+
+    /**
+     * Verify that the unique identifier generated is different for two instances with different
+     * username in the User Credential node
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUniqueIdDifferentUserCredential() throws Exception {
+        PasspointConfiguration config1 = PasspointTestUtils.createConfig();
+        Credential credential = createCredentialWithUserCredential("user", "passwd");
+        config1.setCredential(credential);
+
+        // Modify config2's username to a different value
+        PasspointConfiguration config2 = PasspointTestUtils.createConfig();
+        credential = createCredentialWithUserCredential("user2", "passwd");
+        config2.setCredential(credential);
+
+        assertNotEquals(config1.getUniqueId(), config2.getUniqueId());
+    }
+
+    /**
+     * Verify that the unique identifier generated is different for two instances with different
+     * Cert Credential node
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUniqueIdDifferentCertCredential() throws Exception {
+        PasspointConfiguration config1 = PasspointTestUtils.createConfig();
+        Credential credential = createCredentialWithCertificateCredential(true, true);
+        config1.setCredential(credential);
+
+        // Modify config2's cert credential to a different set of values
+        PasspointConfiguration config2 = PasspointTestUtils.createConfig();
+        credential = createCredentialWithCertificateCredential(false, false);
+        config2.setCredential(credential);
+
+        assertNotEquals(config1.getUniqueId(), config2.getUniqueId());
+    }
+
+    /**
+     * Helper function for generating certificate credential for testing.
+     *
+     * @return {@link Credential}
+     */
+    private static Credential createCredentialWithCertificateCredential(Boolean useCaCert0,
+            Boolean useCert0)
+            throws NoSuchAlgorithmException, CertificateEncodingException {
+        Credential.CertificateCredential certCred = new Credential.CertificateCredential();
+        certCred.setCertType("x509v3");
+        if (useCert0) {
+            certCred.setCertSha256Fingerprint(
+                    MessageDigest.getInstance("SHA-256").digest(FakeKeys.CLIENT_CERT.getEncoded()));
+        } else {
+            certCred.setCertSha256Fingerprint(MessageDigest.getInstance("SHA-256")
+                    .digest(FakeKeys.CLIENT_SUITE_B_RSA3072_CERT.getEncoded()));
+        }
+        return createCredential(null, certCred, null, new X509Certificate[] {FakeKeys.CLIENT_CERT},
+                FakeKeys.RSA_KEY1, useCaCert0 ? FakeKeys.CA_CERT0 : FakeKeys.CA_CERT1);
+    }
+
+    /**
+     * Helper function for generating user credential for testing.
+     *
+     * @return {@link Credential}
+     */
+    private static Credential createCredentialWithUserCredential(String username, String password) {
+        Credential.UserCredential userCred = new Credential.UserCredential();
+        userCred.setUsername(username);
+        userCred.setPassword(password);
+        userCred.setMachineManaged(true);
+        userCred.setAbleToShare(true);
+        userCred.setSoftTokenApp("TestApp");
+        userCred.setEapType(EAPConstants.EAP_TTLS);
+        userCred.setNonEapInnerMethod("MS-CHAP");
+        return createCredential(userCred, null, null, null, null, FakeKeys.CA_CERT0);
+    }
+
+    /**
+     * Helper function for generating Credential for testing.
+     *
+     * @param userCred Instance of UserCredential
+     * @param certCred Instance of CertificateCredential
+     * @param simCred Instance of SimCredential
+     * @param clientCertificateChain Chain of client certificates
+     * @param clientPrivateKey Client private key
+     * @param caCerts CA certificates
+     * @return {@link Credential}
+     */
+    private static Credential createCredential(Credential.UserCredential userCred,
+            Credential.CertificateCredential certCred,
+            Credential.SimCredential simCred,
+            X509Certificate[] clientCertificateChain, PrivateKey clientPrivateKey,
+            X509Certificate... caCerts) {
+        Credential cred = new Credential();
+        cred.setCreationTimeInMillis(123455L);
+        cred.setExpirationTimeInMillis(2310093L);
+        cred.setRealm("realm");
+        cred.setCheckAaaServerCertStatus(true);
+        cred.setUserCredential(userCred);
+        cred.setCertCredential(certCred);
+        cred.setSimCredential(simCred);
+        if (caCerts != null && caCerts.length == 1) {
+            cred.setCaCertificate(caCerts[0]);
+        } else {
+            cred.setCaCertificates(caCerts);
+        }
+        cred.setClientCertificateChain(clientCertificateChain);
+        cred.setClientPrivateKey(clientPrivateKey);
+        return cred;
+    }
+
+    /**
      * Verify that the unique identifier API generates an exception if HomeSP is not initialized.
      *
      * @throws Exception
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java b/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
index 829d8f0..a44df40 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
@@ -593,10 +593,10 @@
     }
 
     /**
-     * Verify that unique identifiers are different for a credential with different values
+     * Verify that unique identifiers are different for a credential with different username
      */
     @Test
-    public void testUniqueIdDifferentForUserCredentialsWithDifferentValues() throws Exception {
+    public void testUniqueIdDifferentForUserCredentialsWithDifferentUsername() throws Exception {
         Credential userCred1 = createCredentialWithUserCredential();
         Credential userCred2 = createCredentialWithUserCredential();
         userCred2.getUserCredential().setUsername("anotheruser");
@@ -605,7 +605,24 @@
     }
 
     /**
-     * Verify that unique identifiers are different for a credential with different values
+     * Verify that unique identifiers are different for a credential with different password and
+     * other values other than username
+     */
+    @Test
+    public void testUniqueIdSameForUserCredentialsWithDifferentPassword() throws Exception {
+        Credential userCred1 = createCredentialWithUserCredential();
+        Credential userCred2 = createCredentialWithUserCredential();
+        userCred2.getUserCredential().setPassword("someotherpassword!");
+        userCred2.getUserCredential().setMachineManaged(false);
+        userCred2.getUserCredential().setAbleToShare(false);
+        userCred2.getUserCredential().setSoftTokenApp("TestApp2");
+        userCred2.getUserCredential().setNonEapInnerMethod("PAP");
+
+        assertEquals(userCred1.getUniqueId(), userCred2.getUniqueId());
+    }
+
+    /**
+     * Verify that unique identifiers are different for a cert credential with different values
      */
     @Test
     public void testUniqueIdDifferentForCertCredentialsWithDifferentValues() throws Exception {