Merge "Add IntentFilter verification tests"
diff --git a/Android.bp b/Android.bp
index 0780f88..e756b34 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1415,3 +1415,30 @@
     name: "framework-telephony-jarjar-rules",
     srcs: ["telephony/framework-telephony-jarjar-rules.txt"],
 }
+
+// protolog start
+filegroup {
+    name: "protolog-common-src",
+    srcs: [
+        "core/java/com/android/internal/protolog/common/**/*.java",
+    ],
+}
+
+java_library {
+    name: "protolog-lib",
+    platform_apis: true,
+    srcs: [
+        "core/java/com/android/internal/protolog/ProtoLogImpl.java",
+        "core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java",
+        ":protolog-common-src",
+    ],
+}
+
+java_library {
+    name: "protolog-groups",
+    srcs: [
+        "core/java/com/android/internal/protolog/ProtoLogGroup.java",
+        ":protolog-common-src",
+    ],
+}
+// protolog end
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 0fe34fb..2bd5aee 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -311,15 +311,6 @@
     compile_dex: true,
 }
 
-java_defaults {
-    name: "android_stubs_dists_default",
-    dist: {
-        targets: ["sdk", "win_sdk"],
-        tag: ".jar",
-        dest: "android.jar",
-    },
-}
-
 java_library_static {
     name: "android_monolith_stubs_current",
     srcs: [ ":api-stubs-docs" ],
@@ -355,21 +346,7 @@
     name: "android_system_monolith_stubs_current",
     srcs: [ ":system-api-stubs-docs" ],
     static_libs: [ "private-stub-annotations-jar" ],
-    defaults: [
-        "android_defaults_stubs_current",
-        "android_stubs_dists_default",
-    ],
-    dist: {
-        dir: "apistubs/android/system",
-    },
-    dists: [
-        {
-            // Legacy dist path
-            targets: ["sdk", "win_sdk"],
-            tag: ".jar",
-            dest: "android_system.jar",
-        },
-    ],
+    defaults: ["android_defaults_stubs_current"],
 }
 
 java_library_static {
@@ -401,34 +378,14 @@
     name: "android_test_stubs_current",
     srcs: [ ":test-api-stubs-docs" ],
     static_libs: [ "private-stub-annotations-jar" ],
-    defaults: [
-        "android_defaults_stubs_current",
-        "android_stubs_dists_default",
-    ],
-    dist: {
-        dir: "apistubs/android/test",
-    },
-    dists: [
-        {
-            // Legacy dist path
-            targets: ["sdk", "win_sdk"],
-            tag: ".jar",
-            dest: "android_test.jar",
-        },
-    ],
+    defaults: ["android_defaults_stubs_current"],
 }
 
 java_library_static {
     name: "android_module_lib_stubs_current",
     srcs: [ ":module-lib-api-stubs-docs-non-updatable" ],
-    defaults: [
-        "android_defaults_stubs_current",
-        "android_stubs_dists_default",
-    ],
+    defaults: ["android_defaults_stubs_current"],
     libs: ["sdk_system_29_android"],
-    dist: {
-        dir: "apistubs/android/module-lib",
-    },
 }
 
 java_library_static {
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/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/api/current.txt b/api/current.txt
index b70103b..532c3d9 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5596,6 +5596,7 @@
     method public android.graphics.drawable.Icon getIcon();
     method public android.app.RemoteInput[] getRemoteInputs();
     method public int getSemanticAction();
+    method public boolean isAuthenticationRequired();
     method public boolean isContextual();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.Notification.Action> CREATOR;
@@ -5625,6 +5626,7 @@
     method @NonNull public android.app.Notification.Action.Builder extend(android.app.Notification.Action.Extender);
     method @NonNull public android.os.Bundle getExtras();
     method @NonNull public android.app.Notification.Action.Builder setAllowGeneratedReplies(boolean);
+    method @NonNull public android.app.Notification.Action.Builder setAuthenticationRequired(boolean);
     method @NonNull public android.app.Notification.Action.Builder setContextual(boolean);
     method @NonNull public android.app.Notification.Action.Builder setSemanticAction(int);
   }
@@ -24065,6 +24067,8 @@
     method public boolean isSink();
     method public boolean isSource();
     field public static final int TYPE_AUX_LINE = 19; // 0x13
+    field public static final int TYPE_BLE_HEADSET = 26; // 0x1a
+    field public static final int TYPE_BLE_SPEAKER = 27; // 0x1b
     field public static final int TYPE_BLUETOOTH_A2DP = 8; // 0x8
     field public static final int TYPE_BLUETOOTH_SCO = 7; // 0x7
     field public static final int TYPE_BUILTIN_EARPIECE = 1; // 0x1
@@ -45972,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();
@@ -46203,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();
@@ -65720,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[]);
@@ -65807,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);
@@ -65830,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);
@@ -65848,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 f53ac8c..7cd8a62 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";
   }
 
 }
@@ -94,6 +95,7 @@
   }
 
   public interface Parcelable {
+    method public default int getStability();
     field public static final int PARCELABLE_STABILITY_LOCAL = 0; // 0x0
     field public static final int PARCELABLE_STABILITY_VINTF = 1; // 0x1
   }
diff --git a/api/system-current.txt b/api/system-current.txt
index 9447427..ea16238 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -301,6 +301,7 @@
     field public static final int config_helpIntentNameKey = 17039390; // 0x104001e
     field public static final int config_helpPackageNameKey = 17039387; // 0x104001b
     field public static final int config_helpPackageNameValue = 17039388; // 0x104001c
+    field public static final int config_systemAutomotiveCluster = 17039400; // 0x1040028
     field public static final int config_systemGallery = 17039399; // 0x1040027
   }
 
@@ -4195,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);
@@ -4207,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;
@@ -4223,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);
@@ -4246,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 {
@@ -7141,6 +7150,8 @@
     field @Deprecated public static final int METERED_OVERRIDE_METERED = 1; // 0x1
     field @Deprecated public static final int METERED_OVERRIDE_NONE = 0; // 0x0
     field @Deprecated public static final int METERED_OVERRIDE_NOT_METERED = 2; // 0x2
+    field @Deprecated public static final int RANDOMIZATION_AUTO = 3; // 0x3
+    field @Deprecated public static final int RANDOMIZATION_ENHANCED = 2; // 0x2
     field @Deprecated public static final int RANDOMIZATION_NONE = 0; // 0x0
     field @Deprecated public static final int RANDOMIZATION_PERSISTENT = 1; // 0x1
     field @Deprecated public static final int RECENT_FAILURE_AP_UNABLE_TO_HANDLE_NEW_STA = 17; // 0x11
diff --git a/api/test-current.txt b/api/test-current.txt
index 963ef7c..a1d1fa7 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -42,6 +42,7 @@
   public static final class R.string {
     field public static final int config_defaultAssistant = 17039393; // 0x1040021
     field public static final int config_defaultDialer = 17039395; // 0x1040023
+    field public static final int config_systemAutomotiveCluster = 17039400; // 0x1040028
     field public static final int config_systemGallery = 17039399; // 0x1040027
   }
 
@@ -969,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 {
@@ -1016,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
@@ -1779,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
   }
 
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp
index a6c402c..15e22a3 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.cpp
+++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp
@@ -166,7 +166,7 @@
 Status Idmap2Service::createIdmap(const std::string& target_apk_path,
                                   const std::string& overlay_apk_path, int32_t fulfilled_policies,
                                   bool enforce_overlayable, int32_t user_id ATTRIBUTE_UNUSED,
-                                  aidl::nullable<std::string>* _aidl_return) {
+                                  std::optional<std::string>* _aidl_return) {
   assert(_aidl_return);
   SYSTRACE << "Idmap2Service::createIdmap " << target_apk_path << " " << overlay_apk_path;
   _aidl_return->reset();
@@ -214,7 +214,7 @@
     return error("failed to write to idmap path " + idmap_path);
   }
 
-  *_aidl_return = aidl::make_nullable<std::string>(idmap_path);
+  *_aidl_return = idmap_path;
   return ok();
 }
 
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.h b/cmds/idmap2/idmap2d/Idmap2Service.h
index ea931c9..1a44519 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.h
+++ b/cmds/idmap2/idmap2d/Idmap2Service.h
@@ -21,7 +21,6 @@
 
 #include <android-base/unique_fd.h>
 #include <binder/BinderService.h>
-#include <binder/Nullable.h>
 
 #include "android/os/BnIdmap2.h"
 
@@ -47,7 +46,7 @@
   binder::Status createIdmap(const std::string& target_apk_path,
                              const std::string& overlay_apk_path, int32_t fulfilled_policies,
                              bool enforce_overlayable, int32_t user_id,
-                             aidl::nullable<std::string>* _aidl_return) override;
+                             std::optional<std::string>* _aidl_return) override;
 
  private:
   // Cache the crc of the android framework package since the crc cannot change without a reboot.
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index 1579715..7c41951 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -85,8 +85,9 @@
         "src/metrics/EventMetricProducer.cpp",
         "src/metrics/GaugeMetricProducer.cpp",
         "src/metrics/MetricProducer.cpp",
-        "src/metrics/metrics_manager_util.cpp",
         "src/metrics/MetricsManager.cpp",
+        "src/metrics/parsing_utils/config_update_utils.cpp",
+        "src/metrics/parsing_utils/metrics_manager_util.cpp",
         "src/metrics/ValueMetricProducer.cpp",
         "src/packages/UidMap.cpp",
         "src/shell/shell_config.proto",
@@ -322,6 +323,8 @@
         "tests/metrics/metrics_test_helper.cpp",
         "tests/metrics/OringDurationTracker_test.cpp",
         "tests/metrics/ValueMetricProducer_test.cpp",
+        "tests/metrics/parsing_utils/config_update_utils_test.cpp",
+        "tests/metrics/parsing_utils/metrics_manager_util_test.cpp",
         "tests/MetricsManager_test.cpp",
         "tests/shell/ShellSubscriber_test.cpp",
         "tests/state/StateTracker_test.cpp",
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index 6327490..7bee4e2 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -546,7 +546,8 @@
         }
     } else {
         // Preserve the existing MetricsManager, update necessary components and metadata in place.
-        configValid = it->second->updateConfig(timestampNs, config);
+        configValid = it->second->updateConfig(config, mTimeBaseNs, timestampNs,
+                                               mAnomalyAlarmMonitor, mPeriodicAlarmMonitor);
         if (configValid) {
             // TODO(b/162323476): refresh TTL, ensure init() is handled properly.
             mUidMap->OnConfigUpdated(key);
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/hash.h b/cmds/statsd/src/hash.h
index cfe869f..bd6b0cd 100644
--- a/cmds/statsd/src/hash.h
+++ b/cmds/statsd/src/hash.h
@@ -22,6 +22,7 @@
 namespace os {
 namespace statsd {
 
+// Uses murmur2 hashing algorithm.
 extern uint32_t Hash32(const char *data, size_t n, uint32_t seed);
 extern uint64_t Hash64(const char* data, size_t n, uint64_t seed);
 
diff --git a/cmds/statsd/src/matchers/CombinationLogMatchingTracker.cpp b/cmds/statsd/src/matchers/CombinationLogMatchingTracker.cpp
index b94a957..60bcc26 100644
--- a/cmds/statsd/src/matchers/CombinationLogMatchingTracker.cpp
+++ b/cmds/statsd/src/matchers/CombinationLogMatchingTracker.cpp
@@ -27,8 +27,9 @@
 using std::unordered_map;
 using std::vector;
 
-CombinationLogMatchingTracker::CombinationLogMatchingTracker(const int64_t& id, const int index)
-    : LogMatchingTracker(id, index) {
+CombinationLogMatchingTracker::CombinationLogMatchingTracker(const int64_t& id, const int index,
+                                                             const uint64_t protoHash)
+    : LogMatchingTracker(id, index, protoHash) {
 }
 
 CombinationLogMatchingTracker::~CombinationLogMatchingTracker() {
diff --git a/cmds/statsd/src/matchers/CombinationLogMatchingTracker.h b/cmds/statsd/src/matchers/CombinationLogMatchingTracker.h
index 55bc4605..6b8a7fb 100644
--- a/cmds/statsd/src/matchers/CombinationLogMatchingTracker.h
+++ b/cmds/statsd/src/matchers/CombinationLogMatchingTracker.h
@@ -28,7 +28,7 @@
 // Represents a AtomMatcher_Combination in the StatsdConfig.
 class CombinationLogMatchingTracker : public virtual LogMatchingTracker {
 public:
-    CombinationLogMatchingTracker(const int64_t& id, const int index);
+    CombinationLogMatchingTracker(const int64_t& id, const int index, const uint64_t protoHash);
 
     bool init(const std::vector<AtomMatcher>& allLogMatchers,
               const std::vector<sp<LogMatchingTracker>>& allTrackers,
diff --git a/cmds/statsd/src/matchers/LogMatchingTracker.h b/cmds/statsd/src/matchers/LogMatchingTracker.h
index 88ab4e6..49a41ad 100644
--- a/cmds/statsd/src/matchers/LogMatchingTracker.h
+++ b/cmds/statsd/src/matchers/LogMatchingTracker.h
@@ -33,8 +33,8 @@
 
 class LogMatchingTracker : public virtual RefBase {
 public:
-    LogMatchingTracker(const int64_t& id, const int index)
-        : mId(id), mIndex(index), mInitialized(false){};
+    LogMatchingTracker(const int64_t& id, const int index, const uint64_t protoHash)
+        : mId(id), mIndex(index), mInitialized(false), mProtoHash(protoHash){};
 
     virtual ~LogMatchingTracker(){};
 
@@ -69,10 +69,14 @@
         return mAtomIds;
     }
 
-    const int64_t& getId() const {
+    int64_t getId() const {
         return mId;
     }
 
+    uint64_t getProtoHash() const {
+        return mProtoHash;
+    }
+
 protected:
     // Name of this matching. We don't really need the name, but it makes log message easy to debug.
     const int64_t mId;
@@ -87,6 +91,14 @@
     // return kNotMatched when we receive an event with an id not in the list. This is especially
     // useful when we have a complex CombinationLogMatcherTracker.
     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(ConfigUpdateTest, TestUpdateMatchers);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp b/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp
index 082daf5..ff47d35 100644
--- a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp
+++ b/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp
@@ -26,11 +26,11 @@
 using std::unordered_map;
 using std::vector;
 
-
 SimpleLogMatchingTracker::SimpleLogMatchingTracker(const int64_t& id, const int index,
+                                                   const uint64_t protoHash,
                                                    const SimpleAtomMatcher& matcher,
-                                                   const UidMap& uidMap)
-    : LogMatchingTracker(id, index), mMatcher(matcher), mUidMap(uidMap) {
+                                                   const sp<UidMap>& uidMap)
+    : LogMatchingTracker(id, index, protoHash), mMatcher(matcher), mUidMap(uidMap) {
     if (!matcher.has_atom_id()) {
         mInitialized = false;
     } else {
diff --git a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.h b/cmds/statsd/src/matchers/SimpleLogMatchingTracker.h
index a0f6a88..e58e012 100644
--- a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.h
+++ b/cmds/statsd/src/matchers/SimpleLogMatchingTracker.h
@@ -29,9 +29,8 @@
 
 class SimpleLogMatchingTracker : public virtual LogMatchingTracker {
 public:
-    SimpleLogMatchingTracker(const int64_t& id, const int index,
-                             const SimpleAtomMatcher& matcher,
-                             const UidMap& uidMap);
+    SimpleLogMatchingTracker(const int64_t& id, const int index, const uint64_t protoHash,
+                             const SimpleAtomMatcher& matcher, const sp<UidMap>& uidMap);
 
     ~SimpleLogMatchingTracker();
 
@@ -46,7 +45,7 @@
 
 private:
     const SimpleAtomMatcher mMatcher;
-    const UidMap& mUidMap;
+    const sp<UidMap> mUidMap;
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/matchers/matcher_util.cpp b/cmds/statsd/src/matchers/matcher_util.cpp
index 2b4c6a3..e6d9122 100644
--- a/cmds/statsd/src/matchers/matcher_util.cpp
+++ b/cmds/statsd/src/matchers/matcher_util.cpp
@@ -81,14 +81,15 @@
     return matched;
 }
 
-bool tryMatchString(const UidMap& uidMap, const FieldValue& fieldValue, const string& str_match) {
+bool tryMatchString(const sp<UidMap>& uidMap, const FieldValue& fieldValue,
+                    const string& str_match) {
     if (isAttributionUidField(fieldValue) || isUidField(fieldValue)) {
         int uid = fieldValue.mValue.int_value;
         auto aidIt = UidMap::sAidToUidMapping.find(str_match);
         if (aidIt != UidMap::sAidToUidMapping.end()) {
             return ((int)aidIt->second) == uid;
         }
-        std::set<string> packageNames = uidMap.getAppNamesFromUid(uid, true /* normalize*/);
+        std::set<string> packageNames = uidMap->getAppNamesFromUid(uid, true /* normalize*/);
         return packageNames.find(str_match) != packageNames.end();
     } else if (fieldValue.mValue.getType() == STRING) {
         return fieldValue.mValue.str_value == str_match;
@@ -96,7 +97,7 @@
     return false;
 }
 
-bool matchesSimple(const UidMap& uidMap, const FieldValueMatcher& matcher,
+bool matchesSimple(const sp<UidMap>& uidMap, const FieldValueMatcher& matcher,
                    const vector<FieldValue>& values, int start, int end, int depth) {
     if (depth > 2) {
         ALOGE("Depth > 3 not supported");
@@ -353,7 +354,7 @@
     }
 }
 
-bool matchesSimple(const UidMap& uidMap, const SimpleAtomMatcher& simpleMatcher,
+bool matchesSimple(const sp<UidMap>& uidMap, const SimpleAtomMatcher& simpleMatcher,
                    const LogEvent& event) {
     if (event.GetTagId() != simpleMatcher.atom_id()) {
         return false;
diff --git a/cmds/statsd/src/matchers/matcher_util.h b/cmds/statsd/src/matchers/matcher_util.h
index 1ab3e87..130b606 100644
--- a/cmds/statsd/src/matchers/matcher_util.h
+++ b/cmds/statsd/src/matchers/matcher_util.h
@@ -36,8 +36,8 @@
 bool combinationMatch(const std::vector<int>& children, const LogicalOperation& operation,
                       const std::vector<MatchingState>& matcherResults);
 
-bool matchesSimple(const UidMap& uidMap,
-    const SimpleAtomMatcher& simpleMatcher, const LogEvent& wrapper);
+bool matchesSimple(const sp<UidMap>& uidMap, const SimpleAtomMatcher& simpleMatcher,
+                   const LogEvent& wrapper);
 
 }  // namespace statsd
 }  // namespace os
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index 189d811..2c3deca 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -26,7 +26,8 @@
 #include "guardrail/StatsdStats.h"
 #include "matchers/CombinationLogMatchingTracker.h"
 #include "matchers/SimpleLogMatchingTracker.h"
-#include "metrics_manager_util.h"
+#include "parsing_utils/config_update_utils.h"
+#include "parsing_utils/metrics_manager_util.h"
 #include "state/StateManager.h"
 #include "stats_log_util.h"
 #include "stats_util.h"
@@ -76,13 +77,14 @@
     // Init the ttl end timestamp.
     refreshTtl(timeBaseNs);
 
-    mConfigValid = initStatsdConfig(
-            key, config, *uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
-            timeBaseNs, currentTimeNs, mTagIds, mAllAtomMatchers, mAllConditionTrackers,
-            mAllMetricProducers, mAllAnomalyTrackers, mAllPeriodicAlarmTrackers,
-            mConditionToMetricMap, mTrackerToMetricMap, mTrackerToConditionMap,
-            mActivationAtomTrackerToMetricMap, mDeactivationAtomTrackerToMetricMap,
-            mAlertTrackerMap, mMetricIndexesWithActivation, mNoReportMetricIds);
+    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);
 
     mHashStringsInReport = config.hash_strings_in_metric_report();
     mVersionStringsInReport = config.version_strings_in_metric_report();
@@ -195,7 +197,19 @@
     VLOG("~MetricsManager()");
 }
 
-bool MetricsManager::updateConfig(const int64_t currentTimeNs, const StatsdConfig& config) {
+bool MetricsManager::updateConfig(const StatsdConfig& config, const int64_t timeBaseNs,
+                                  const int64_t currentTimeNs,
+                                  const sp<AlarmMonitor>& anomalyAlarmMonitor,
+                                  const sp<AlarmMonitor>& periodicAlarmMonitor) {
+    vector<sp<LogMatchingTracker>> newAtomMatchers;
+    unordered_map<int64_t, int> newLogTrackerMap;
+    mTagIds.clear();
+    mConfigValid =
+            updateStatsdConfig(mConfigKey, config, mUidMap, mPullerManager, anomalyAlarmMonitor,
+                               periodicAlarmMonitor, timeBaseNs, currentTimeNs, mAllAtomMatchers,
+                               mLogTrackerMap, mTagIds, newAtomMatchers, newLogTrackerMap);
+    mAllAtomMatchers = newAtomMatchers;
+    mLogTrackerMap = newLogTrackerMap;
     return mConfigValid;
 }
 
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index 042de29..6f9a2336 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -46,7 +46,9 @@
 
     virtual ~MetricsManager();
 
-    bool updateConfig(const int64_t currentTimeNs, const StatsdConfig& config);
+    bool updateConfig(const StatsdConfig& config, const int64_t timeBaseNs,
+                      const int64_t currentTimeNs, const sp<AlarmMonitor>& anomalyAlarmMonitor,
+                      const sp<AlarmMonitor>& periodicAlarmMonitor);
 
     // Return whether the configuration is valid.
     bool isConfigValid() const;
@@ -237,6 +239,12 @@
     // Hold all periodic alarm trackers.
     std::vector<sp<AlarmTracker>> mAllPeriodicAlarmTrackers;
 
+    // To make updating configs faster, we map the id of a LogMatchingTracker, 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;
+
     // 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.
 
diff --git a/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp
new file mode 100644
index 0000000..562e291
--- /dev/null
+++ b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp
@@ -0,0 +1,203 @@
+/*
+ * 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.
+ */
+
+#define DEBUG false  // STOPSHIP if true
+
+#include "config_update_utils.h"
+
+#include "external/StatsPullerManager.h"
+#include "hash.h"
+#include "metrics_manager_util.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+// 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,
+                                  vector<UpdateStatus>& matchersToUpdate,
+                                  vector<bool>& cycleTracker) {
+    // Have already examined this matcher.
+    if (matchersToUpdate[matcherIdx] != UPDATE_UNKNOWN) {
+        return true;
+    }
+
+    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()) {
+        matchersToUpdate[matcherIdx] = UPDATE_REPLACE;
+        return true;
+    }
+
+    // This is an existing matcher. Check if it has changed.
+    string serializedMatcher;
+    if (!matcher.SerializeToString(&serializedMatcher)) {
+        ALOGE("Unable to serialize matcher %lld", (long long)id);
+        return false;
+    }
+    uint64_t newProtoHash = Hash64(serializedMatcher);
+    if (newProtoHash != oldAtomMatchers[oldLogTrackerIt->second]->getProtoHash()) {
+        matchersToUpdate[matcherIdx] = UPDATE_REPLACE;
+        return true;
+    }
+
+    switch (matcher.contents_case()) {
+        case AtomMatcher::ContentsCase::kSimpleAtomMatcher: {
+            matchersToUpdate[matcherIdx] = UPDATE_PRESERVE;
+            return true;
+        }
+        case AtomMatcher::ContentsCase::kCombination: {
+            // Recurse to check if children have changed.
+            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()) {
+                    ALOGW("Matcher %lld not found in the config", (long long)childMatcherId);
+                    return false;
+                }
+                const int childIdx = childIt->second;
+                if (cycleTracker[childIdx]) {
+                    ALOGE("Cycle detected in matcher config");
+                    return false;
+                }
+                if (!determineMatcherUpdateStatus(config, childIdx, oldLogTrackerMap,
+                                                  oldAtomMatchers, newLogTrackerMap,
+                                                  matchersToUpdate, cycleTracker)) {
+                    return false;
+                }
+
+                if (matchersToUpdate[childIdx] == UPDATE_REPLACE) {
+                    status = UPDATE_REPLACE;
+                    break;
+                }
+            }
+            matchersToUpdate[matcherIdx] = status;
+            cycleTracker[matcherIdx] = false;
+            return true;
+        }
+        default: {
+            ALOGE("Matcher \"%lld\" malformed", (long long)id);
+            return false;
+        }
+    }
+    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) {
+    const int atomMatcherCount = config.atom_matcher_size();
+
+    vector<AtomMatcher> matcherProtos;
+    matcherProtos.reserve(atomMatcherCount);
+    newAtomMatchers.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()) {
+            ALOGE("Duplicate atom matcher found for id %lld", (long long)matcher.id());
+            return false;
+        }
+        newLogTrackerMap[matcher.id()] = i;
+        matcherProtos.push_back(matcher);
+    }
+
+    // For combination matchers, we need to determine if any children need to be updated.
+    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)) {
+            return false;
+        }
+    }
+
+    for (int i = 0; i < atomMatcherCount; i++) {
+        const AtomMatcher& matcher = config.atom_matcher(i);
+        const int64_t id = matcher.id();
+        switch (matchersToUpdate[i]) {
+            case UPDATE_PRESERVE: {
+                const auto& oldLogTrackerIt = oldLogTrackerMap.find(id);
+                if (oldLogTrackerIt == oldLogTrackerMap.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]);
+                break;
+            }
+            case UPDATE_REPLACE: {
+                sp<LogMatchingTracker> tracker = createLogTracker(matcher, i, uidMap);
+                if (tracker == nullptr) {
+                    return false;
+                }
+                newAtomMatchers.push_back(tracker);
+                break;
+            }
+            default: {
+                ALOGE("Matcher \"%lld\" update state is unknown. This should never happen",
+                      (long long)id);
+                return false;
+            }
+        }
+    }
+
+    std::fill(cycleTracker.begin(), cycleTracker.end(), false);
+    for (auto& matcher : newAtomMatchers) {
+        if (!matcher->init(matcherProtos, newAtomMatchers, newLogTrackerMap, cycleTracker)) {
+            return false;
+        }
+        // Collect all the tag ids that are interesting. TagIds exist in leaf nodes only.
+        const set<int>& tagIds = matcher->getAtomIds();
+        allTagIds.insert(tagIds.begin(), tagIds.end());
+    }
+
+    return true;
+}
+
+bool updateStatsdConfig(const ConfigKey& key, const StatsdConfig& config, const sp<UidMap>& uidMap,
+                        const sp<StatsPullerManager>& pullerManager,
+                        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");
+        return false;
+    }
+
+    return true;
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/metrics/parsing_utils/config_update_utils.h b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.h
new file mode 100644
index 0000000..951ab03
--- /dev/null
+++ b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.h
@@ -0,0 +1,89 @@
+/*
+ * 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 <vector>
+
+#include "anomaly/AlarmMonitor.h"
+#include "external/StatsPullerManager.h"
+#include "matchers/LogMatchingTracker.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+// Helper functions for MetricsManager to update itself from a new StatsdConfig.
+// *Note*: only updateStatsdConfig() should be called from outside this file.
+// All other functions are intermediate steps, created to make unit testing easier.
+
+// Possible update states for a component. PRESERVE means we should keep the existing one.
+// REPLACE means we should create a new one, either because it didn't exist or it changed.
+enum UpdateStatus {
+    UPDATE_UNKNOWN = 0,
+    UPDATE_PRESERVE = 1,
+    UPDATE_REPLACE = 2,
+};
+
+// Recursive function to determine if a matcher needs to be updated.
+// 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
+// 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,
+                                  vector<UpdateStatus>& matchersToUpdate,
+                                  vector<bool>& cycleTracker);
+
+// Updates the LogMatchingTrackers.
+// input:
+// [config]: the input StatsdConfig
+// [oldLogTrackerMap]: existing matcher id to index mapping
+// [oldAtomMatchers]: stores the existing LogMatchingTrackers
+// 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);
+
+// Updates the existing MetricsManager from a new StatsdConfig.
+// Parameters are the members of MetricsManager. See MetricsManager for declaration.
+bool updateStatsdConfig(const ConfigKey& key, const StatsdConfig& config, const sp<UidMap>& uidMap,
+                        const sp<StatsPullerManager>& pullerManager,
+                        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,
+                        std::set<int>& allTagIds,
+                        std::vector<sp<LogMatchingTracker>>& newAtomMatchers,
+                        unordered_map<int64_t, int>& newLogTrackerMap);
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
similarity index 89%
rename from cmds/statsd/src/metrics/metrics_manager_util.cpp
rename to cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
index 8917c36..9d3943f 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
@@ -22,10 +22,10 @@
 #include <inttypes.h>
 
 #include "FieldValue.h"
-#include "MetricProducer.h"
 #include "condition/CombinationConditionTracker.h"
 #include "condition/SimpleConditionTracker.h"
 #include "external/StatsPullerManager.h"
+#include "hash.h"
 #include "matchers/CombinationLogMatchingTracker.h"
 #include "matchers/EventMatcherWizard.h"
 #include "matchers/SimpleLogMatchingTracker.h"
@@ -33,6 +33,7 @@
 #include "metrics/DurationMetricProducer.h"
 #include "metrics/EventMetricProducer.h"
 #include "metrics/GaugeMetricProducer.h"
+#include "metrics/MetricProducer.h"
 #include "metrics/ValueMetricProducer.h"
 #include "state/StateManager.h"
 #include "stats_util.h"
@@ -61,6 +62,28 @@
 
 }  // namespace
 
+sp<LogMatchingTracker> createLogTracker(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());
+        return nullptr;
+    }
+    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);
+            break;
+        case AtomMatcher::ContentsCase::kCombination:
+            return new CombinationLogMatchingTracker(logMatcher.id(), index, protoHash);
+            break;
+        default:
+            ALOGE("Matcher \"%lld\" malformed", (long long)logMatcher.id());
+            return nullptr;
+    }
+}
+
 bool handleMetricWithLogTrackers(const int64_t what, const int metricIndex,
                                  const bool usedForDimension,
                                  const vector<sp<LogMatchingTracker>>& allAtomMatchers,
@@ -125,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;
 
@@ -184,9 +205,7 @@
 //      to provide the producer with state about its activators and deactivators.
 // Returns false if there are errors.
 bool handleMetricActivation(
-        const StatsdConfig& config,
-        const int64_t metricId,
-        const int metricIndex,
+        const StatsdConfig& config, const int64_t metricId, const int metricIndex,
         const unordered_map<int64_t, int>& metricToActivationMap,
         const unordered_map<int64_t, int>& logTrackerMap,
         unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap,
@@ -210,10 +229,11 @@
             return false;
         }
 
-        ActivationType activationType = (activation.has_activation_type()) ?
-                activation.activation_type() : metricActivation.activation_type();
-        std::shared_ptr<Activation> activationWrapper = std::make_shared<Activation>(
-                activationType, activation.ttl_seconds() * NS_PER_SEC);
+        ActivationType activationType = (activation.has_activation_type())
+                                                ? activation.activation_type()
+                                                : metricActivation.activation_type();
+        std::shared_ptr<Activation> activationWrapper =
+                std::make_shared<Activation>(activationType, activation.ttl_seconds() * NS_PER_SEC);
 
         int atomMatcherIndex = itr->second;
         activationAtomTrackerToMetricMap[atomMatcherIndex].push_back(metricIndex);
@@ -235,7 +255,7 @@
     return true;
 }
 
-bool initLogTrackers(const StatsdConfig& config, const UidMap& uidMap,
+bool initLogTrackers(const StatsdConfig& config, const sp<UidMap>& uidMap,
                      unordered_map<int64_t, int>& logTrackerMap,
                      vector<sp<LogMatchingTracker>>& allAtomMatchers, set<int>& allTagIds) {
     vector<AtomMatcher> matcherConfigs;
@@ -245,22 +265,12 @@
 
     for (int i = 0; i < atomMatcherCount; i++) {
         const AtomMatcher& logMatcher = config.atom_matcher(i);
-
         int index = allAtomMatchers.size();
-        switch (logMatcher.contents_case()) {
-            case AtomMatcher::ContentsCase::kSimpleAtomMatcher:
-                allAtomMatchers.push_back(new SimpleLogMatchingTracker(
-                        logMatcher.id(), index, logMatcher.simple_atom_matcher(), uidMap));
-                break;
-            case AtomMatcher::ContentsCase::kCombination:
-                allAtomMatchers.push_back(
-                        new CombinationLogMatchingTracker(logMatcher.id(), index));
-                break;
-            default:
-                ALOGE("Matcher \"%lld\" malformed", (long long)logMatcher.id());
-                return false;
-                // continue;
+        sp<LogMatchingTracker> tracker = createLogTracker(logMatcher, index, uidMap);
+        if (tracker == nullptr) {
+            return false;
         }
+        allAtomMatchers.push_back(tracker);
         if (logTrackerMap.find(logMatcher.id()) != logTrackerMap.end()) {
             ALOGE("Duplicate AtomMatcher found!");
             return false;
@@ -383,7 +393,7 @@
         const MetricActivation& metricActivation = config.metric_activation(i);
         int64_t metricId = metricActivation.metric_id();
         if (metricToActivationMap.find(metricId) != metricToActivationMap.end()) {
-            ALOGE("Metric %lld has multiple MetricActivations", (long long) metricId);
+            ALOGE("Metric %lld has multiple MetricActivations", (long long)metricId);
             return false;
         }
         metricToActivationMap.insert({metricId, i});
@@ -402,9 +412,8 @@
         metricMap.insert({metric.id(), metricIndex});
         int trackerIndex;
         if (!handleMetricWithLogTrackers(metric.what(), metricIndex,
-                                         metric.has_dimensions_in_what(),
-                                         allAtomMatchers, logTrackerMap, trackerToMetricMap,
-                                         trackerIndex)) {
+                                         metric.has_dimensions_in_what(), allAtomMatchers,
+                                         logTrackerMap, trackerToMetricMap, trackerIndex)) {
             return false;
         }
 
@@ -438,10 +447,10 @@
 
         unordered_map<int, shared_ptr<Activation>> eventActivationMap;
         unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
-        bool success = handleMetricActivation(config, metric.id(), metricIndex,
-                metricToActivationMap, logTrackerMap, activationAtomTrackerToMetricMap,
-                deactivationAtomTrackerToMetricMap, metricsWithActivation, eventActivationMap,
-                eventDeactivationMap);
+        bool success = handleMetricActivation(
+                config, metric.id(), metricIndex, metricToActivationMap, logTrackerMap,
+                activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
+                metricsWithActivation, eventActivationMap, eventDeactivationMap);
         if (!success) return false;
 
         sp<MetricProducer> countProducer =
@@ -544,10 +553,10 @@
 
         unordered_map<int, shared_ptr<Activation>> eventActivationMap;
         unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
-        bool success = handleMetricActivation(config, metric.id(), metricIndex,
-                metricToActivationMap, logTrackerMap, activationAtomTrackerToMetricMap,
-                deactivationAtomTrackerToMetricMap, metricsWithActivation, eventActivationMap,
-                eventDeactivationMap);
+        bool success = handleMetricActivation(
+                config, metric.id(), metricIndex, metricToActivationMap, logTrackerMap,
+                activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
+                metricsWithActivation, eventActivationMap, eventDeactivationMap);
         if (!success) return false;
 
         sp<MetricProducer> durationMetric = new DurationMetricProducer(
@@ -591,10 +600,10 @@
 
         unordered_map<int, shared_ptr<Activation>> eventActivationMap;
         unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
-        bool success = handleMetricActivation(config, metric.id(), metricIndex,
-                metricToActivationMap, logTrackerMap, activationAtomTrackerToMetricMap,
-                deactivationAtomTrackerToMetricMap, metricsWithActivation, eventActivationMap,
-                eventDeactivationMap);
+        bool success = handleMetricActivation(
+                config, metric.id(), metricIndex, metricToActivationMap, logTrackerMap,
+                activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
+                metricsWithActivation, eventActivationMap, eventDeactivationMap);
         if (!success) return false;
 
         sp<MetricProducer> eventMetric =
@@ -626,9 +635,8 @@
         metricMap.insert({metric.id(), metricIndex});
         int trackerIndex;
         if (!handleMetricWithLogTrackers(metric.what(), metricIndex,
-                                         metric.has_dimensions_in_what(),
-                                         allAtomMatchers, logTrackerMap, trackerToMetricMap,
-                                         trackerIndex)) {
+                                         metric.has_dimensions_in_what(), allAtomMatchers,
+                                         logTrackerMap, trackerToMetricMap, trackerIndex)) {
             return false;
         }
 
@@ -718,9 +726,8 @@
         metricMap.insert({metric.id(), metricIndex});
         int trackerIndex;
         if (!handleMetricWithLogTrackers(metric.what(), metricIndex,
-                                         metric.has_dimensions_in_what(),
-                                         allAtomMatchers, logTrackerMap, trackerToMetricMap,
-                                         trackerIndex)) {
+                                         metric.has_dimensions_in_what(), allAtomMatchers,
+                                         logTrackerMap, trackerToMetricMap, trackerIndex)) {
             return false;
         }
 
@@ -775,10 +782,10 @@
 
         unordered_map<int, shared_ptr<Activation>> eventActivationMap;
         unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
-        bool success = handleMetricActivation(config, metric.id(), metricIndex,
-                metricToActivationMap, logTrackerMap, activationAtomTrackerToMetricMap,
-                deactivationAtomTrackerToMetricMap, metricsWithActivation, eventActivationMap,
-                eventDeactivationMap);
+        bool success = handleMetricActivation(
+                config, metric.id(), metricIndex, metricToActivationMap, logTrackerMap,
+                activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
+                metricsWithActivation, eventActivationMap, eventDeactivationMap);
         if (!success) return false;
 
         sp<MetricProducer> gaugeProducer = new GaugeMetricProducer(
@@ -813,8 +820,7 @@
     return true;
 }
 
-bool initAlerts(const StatsdConfig& config,
-                const unordered_map<int64_t, int>& metricProducerMap,
+bool initAlerts(const StatsdConfig& config, const unordered_map<int64_t, int>& metricProducerMap,
                 unordered_map<int64_t, int>& alertTrackerMap,
                 const sp<AlarmMonitor>& anomalyAlarmMonitor,
                 vector<sp<MetricProducer>>& allMetricProducers,
@@ -832,8 +838,8 @@
             return false;
         }
         if (alert.trigger_if_sum_gt() < 0 || alert.num_buckets() <= 0) {
-            ALOGW("invalid alert: threshold=%f num_buckets= %d",
-                  alert.trigger_if_sum_gt(), alert.num_buckets());
+            ALOGW("invalid alert: threshold=%f num_buckets= %d", alert.trigger_if_sum_gt(),
+                  alert.num_buckets());
             return false;
         }
         const int metricIndex = itr->second;
@@ -853,14 +859,13 @@
         }
         if (subscription.subscriber_information_case() ==
             Subscription::SubscriberInformationCase::SUBSCRIBER_INFORMATION_NOT_SET) {
-            ALOGW("subscription \"%lld\" has no subscriber info.\"",
-                (long long)subscription.id());
+            ALOGW("subscription \"%lld\" has no subscriber info.\"", (long long)subscription.id());
             return false;
         }
         const auto& itr = alertTrackerMap.find(subscription.rule_id());
         if (itr == alertTrackerMap.end()) {
             ALOGW("subscription \"%lld\" has unknown rule id: \"%lld\"",
-                (long long)subscription.id(), (long long)subscription.rule_id());
+                  (long long)subscription.id(), (long long)subscription.rule_id());
             return false;
         }
         const int anomalyTrackerIndex = itr->second;
@@ -870,12 +875,11 @@
 }
 
 bool initAlarms(const StatsdConfig& config, const ConfigKey& key,
-                const sp<AlarmMonitor>& periodicAlarmMonitor,
-                const int64_t timeBaseNs, const int64_t currentTimeNs,
-                vector<sp<AlarmTracker>>& allAlarmTrackers) {
+                const sp<AlarmMonitor>& periodicAlarmMonitor, const int64_t timeBaseNs,
+                const int64_t currentTimeNs, vector<sp<AlarmTracker>>& allAlarmTrackers) {
     unordered_map<int64_t, int> alarmTrackerMap;
     int64_t startMillis = timeBaseNs / 1000 / 1000;
-    int64_t currentTimeMillis = currentTimeNs / 1000 /1000;
+    int64_t currentTimeMillis = currentTimeNs / 1000 / 1000;
     for (int i = 0; i < config.alarm_size(); i++) {
         const Alarm& alarm = config.alarm(i);
         if (alarm.offset_millis() <= 0) {
@@ -888,8 +892,7 @@
         }
         alarmTrackerMap.insert(std::make_pair(alarm.id(), allAlarmTrackers.size()));
         allAlarmTrackers.push_back(
-            new AlarmTracker(startMillis, currentTimeMillis,
-                             alarm, key, periodicAlarmMonitor));
+                new AlarmTracker(startMillis, currentTimeMillis, alarm, key, periodicAlarmMonitor));
     }
     for (int i = 0; i < config.subscription_size(); ++i) {
         const Subscription& subscription = config.subscription(i);
@@ -898,14 +901,13 @@
         }
         if (subscription.subscriber_information_case() ==
             Subscription::SubscriberInformationCase::SUBSCRIBER_INFORMATION_NOT_SET) {
-            ALOGW("subscription \"%lld\" has no subscriber info.\"",
-                (long long)subscription.id());
+            ALOGW("subscription \"%lld\" has no subscriber info.\"", (long long)subscription.id());
             return false;
         }
         const auto& itr = alarmTrackerMap.find(subscription.rule_id());
         if (itr == alarmTrackerMap.end()) {
             ALOGW("subscription \"%lld\" has unknown rule id: \"%lld\"",
-                (long long)subscription.id(), (long long)subscription.rule_id());
+                  (long long)subscription.id(), (long long)subscription.rule_id());
             return false;
         }
         const int trackerIndex = itr->second;
@@ -914,12 +916,13 @@
     return true;
 }
 
-bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, UidMap& uidMap,
+bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, const sp<UidMap>& uidMap,
                       const sp<StatsPullerManager>& pullerManager,
                       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<ConditionTracker>>& allConditionTrackers,
                       vector<sp<MetricProducer>>& allMetricProducers,
                       vector<sp<AnomalyTracker>>& allAnomalyTrackers,
@@ -930,9 +933,7 @@
                       unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap,
                       unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
                       unordered_map<int64_t, int>& alertTrackerMap,
-                      vector<int>& metricsWithActivation,
-                      std::set<int64_t>& noReportMetricIds) {
-    unordered_map<int64_t, int> logTrackerMap;
+                      vector<int>& metricsWithActivation, std::set<int64_t>& noReportMetricIds) {
     unordered_map<int64_t, int> conditionTrackerMap;
     vector<ConditionState> initialConditionCache;
     unordered_map<int64_t, int> metricProducerMap;
@@ -969,8 +970,8 @@
         ALOGE("initAlerts failed");
         return false;
     }
-    if (!initAlarms(config, key, periodicAlarmMonitor,
-                    timeBaseNs, currentTimeNs, allPeriodicAlarmTrackers)) {
+    if (!initAlarms(config, key, periodicAlarmMonitor, timeBaseNs, currentTimeNs,
+                    allPeriodicAlarmTrackers)) {
         ALOGE("initAlarms failed");
         return false;
     }
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.h b/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.h
similarity index 86%
rename from cmds/statsd/src/metrics/metrics_manager_util.h
rename to cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.h
index 96b5c26..ed9951f 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.h
+++ b/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.h
@@ -20,16 +20,28 @@
 #include <unordered_map>
 #include <vector>
 
-#include "../anomaly/AlarmTracker.h"
-#include "../condition/ConditionTracker.h"
-#include "../external/StatsPullerManager.h"
-#include "../matchers/LogMatchingTracker.h"
-#include "../metrics/MetricProducer.h"
+#include "anomaly/AlarmTracker.h"
+#include "condition/ConditionTracker.h"
+#include "external/StatsPullerManager.h"
+#include "matchers/LogMatchingTracker.h"
+#include "metrics/MetricProducer.h"
 
 namespace android {
 namespace os {
 namespace statsd {
 
+// Helper functions for creating individual config components from StatsdConfig.
+// Should only be called from metrics_manager_util and config_update_utils.
+
+// Create a LogMatchingTracker.
+// 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);
+
 // Helper functions for MetricsManager to initialize from StatsdConfig.
 // *Note*: only initStatsdConfig() should be called from outside.
 // All other functions are intermediate
@@ -44,8 +56,7 @@
 // [logTrackerMap]: this map should contain matcher name to index mapping
 // [allAtomMatchers]: should store the sp to all the LogMatchingTracker
 // [allTagIds]: contains the set of all interesting tag ids to this config.
-bool initLogTrackers(const StatsdConfig& config,
-                     const UidMap& uidMap,
+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);
@@ -97,7 +108,7 @@
 // [trackerToMetricMap]: contains the mapping from log tracker to MetricProducer index.
 bool initMetrics(
         const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseTimeNs,
-        const int64_t currentTimeNs, UidMap& uidMap, const sp<StatsPullerManager>& pullerManager,
+        const int64_t currentTimeNs, const sp<StatsPullerManager>& pullerManager,
         const std::unordered_map<int64_t, int>& logTrackerMap,
         const std::unordered_map<int64_t, int>& conditionTrackerMap,
         const std::unordered_map<int, std::vector<MetricConditionLink>>& eventConditionLinks,
@@ -116,12 +127,13 @@
 
 // Initialize MetricsManager from StatsdConfig.
 // Parameters are the members of MetricsManager. See MetricsManager for declaration.
-bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, UidMap& uidMap,
+bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, const sp<UidMap>& uidMap,
                       const sp<StatsPullerManager>& pullerManager,
                       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<ConditionTracker>>& allConditionTrackers,
                       std::vector<sp<MetricProducer>>& allMetricProducers,
                       vector<sp<AnomalyTracker>>& allAnomalyTrackers,
@@ -132,8 +144,7 @@
                       unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap,
                       unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
                       std::unordered_map<int64_t, int>& alertTrackerMap,
-                      vector<int>& metricsWithActivation,
-                      std::set<int64_t>& noReportMetricIds);
+                      vector<int>& metricsWithActivation, std::set<int64_t>& noReportMetricIds);
 
 }  // namespace statsd
 }  // namespace os
diff --git a/cmds/statsd/src/shell/ShellSubscriber.cpp b/cmds/statsd/src/shell/ShellSubscriber.cpp
index fd883c2..9d8f0c2 100644
--- a/cmds/statsd/src/shell/ShellSubscriber.cpp
+++ b/cmds/statsd/src/shell/ShellSubscriber.cpp
@@ -191,7 +191,7 @@
     mProto.clear();
     int count = 0;
     for (const auto& event : data) {
-        if (matchesSimple(*mUidMap, matcher, *event)) {
+        if (matchesSimple(mUidMap, matcher, *event)) {
             count++;
             uint64_t atomToken = mProto.start(util::FIELD_TYPE_MESSAGE |
                                               util::FIELD_COUNT_REPEATED | FIELD_ID_ATOM);
@@ -209,7 +209,7 @@
 
     mProto.clear();
     for (const auto& matcher : mSubscriptionInfo->mPushedMatchers) {
-        if (matchesSimple(*mUidMap, matcher, event)) {
+        if (matchesSimple(mUidMap, matcher, event)) {
             uint64_t atomToken = mProto.start(util::FIELD_TYPE_MESSAGE |
                                               util::FIELD_COUNT_REPEATED | FIELD_ID_ATOM);
             event.ToProto(mProto);
diff --git a/cmds/statsd/tests/LogEntryMatcher_test.cpp b/cmds/statsd/tests/LogEntryMatcher_test.cpp
index 6264c07..92cd04f 100644
--- a/cmds/statsd/tests/LogEntryMatcher_test.cpp
+++ b/cmds/statsd/tests/LogEntryMatcher_test.cpp
@@ -110,7 +110,7 @@
 }  // anonymous namespace
 
 TEST(AtomMatcherTest, TestSimpleMatcher) {
-    UidMap uidMap;
+    sp<UidMap> uidMap = new UidMap();
 
     // Set up the matcher
     AtomMatcher matcher;
@@ -129,7 +129,7 @@
 }
 
 TEST(AtomMatcherTest, TestAttributionMatcher) {
-    UidMap uidMap;
+    sp<UidMap> uidMap = new UidMap();
     std::vector<int> attributionUids = {1111, 2222, 3333};
     std::vector<string> attributionTags = {"location1", "location2", "location3"};
 
@@ -204,7 +204,7 @@
             "pkg0");
     EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
 
-    uidMap.updateMap(
+    uidMap->updateMap(
             1, {1111, 1111, 2222, 3333, 3333} /* uid list */, {1, 1, 2, 1, 2} /* version list */,
             {android::String16("v1"), android::String16("v1"), android::String16("v2"),
              android::String16("v1"), android::String16("v2")},
@@ -356,8 +356,8 @@
 }
 
 TEST(AtomMatcherTest, TestUidFieldMatcher) {
-    UidMap uidMap;
-    uidMap.updateMap(
+    sp<UidMap> uidMap = new UidMap();
+    uidMap->updateMap(
             1, {1111, 1111, 2222, 3333, 3333} /* uid list */, {1, 1, 2, 1, 2} /* version list */,
             {android::String16("v1"), android::String16("v1"), android::String16("v2"),
              android::String16("v1"), android::String16("v2")},
@@ -392,8 +392,8 @@
 }
 
 TEST(AtomMatcherTest, TestNeqAnyStringMatcher) {
-    UidMap uidMap;
-    uidMap.updateMap(
+    sp<UidMap> uidMap = new UidMap();
+    uidMap->updateMap(
             1, {1111, 1111, 2222, 3333, 3333} /* uid list */, {1, 1, 2, 1, 2} /* version list */,
             {android::String16("v1"), android::String16("v1"), android::String16("v2"),
              android::String16("v1"), android::String16("v2")},
@@ -453,8 +453,8 @@
 }
 
 TEST(AtomMatcherTest, TestEqAnyStringMatcher) {
-    UidMap uidMap;
-    uidMap.updateMap(
+    sp<UidMap> uidMap = new UidMap();
+    uidMap->updateMap(
             1, {1111, 1111, 2222, 3333, 3333} /* uid list */, {1, 1, 2, 1, 2} /* version list */,
             {android::String16("v1"), android::String16("v1"), android::String16("v2"),
              android::String16("v1"), android::String16("v2")},
@@ -517,7 +517,7 @@
 }
 
 TEST(AtomMatcherTest, TestBoolMatcher) {
-    UidMap uidMap;
+    sp<UidMap> uidMap = new UidMap();
     // Set up the matcher
     AtomMatcher matcher;
     auto simpleMatcher = matcher.mutable_simple_atom_matcher();
@@ -550,7 +550,7 @@
 }
 
 TEST(AtomMatcherTest, TestStringMatcher) {
-    UidMap uidMap;
+    sp<UidMap> uidMap = new UidMap();
     // Set up the matcher
     AtomMatcher matcher;
     auto simpleMatcher = matcher.mutable_simple_atom_matcher();
@@ -568,7 +568,7 @@
 }
 
 TEST(AtomMatcherTest, TestMultiFieldsMatcher) {
-    UidMap uidMap;
+    sp<UidMap> uidMap = new UidMap();
     // Set up the matcher
     AtomMatcher matcher;
     auto simpleMatcher = matcher.mutable_simple_atom_matcher();
@@ -597,7 +597,7 @@
 }
 
 TEST(AtomMatcherTest, TestIntComparisonMatcher) {
-    UidMap uidMap;
+    sp<UidMap> uidMap = new UidMap();
     // Set up the matcher
     AtomMatcher matcher;
     auto simpleMatcher = matcher.mutable_simple_atom_matcher();
@@ -654,7 +654,7 @@
 }
 
 TEST(AtomMatcherTest, TestFloatComparisonMatcher) {
-    UidMap uidMap;
+    sp<UidMap> uidMap = new UidMap();
     // Set up the matcher
     AtomMatcher matcher;
     auto simpleMatcher = matcher.mutable_simple_atom_matcher();
diff --git a/cmds/statsd/tests/MetricsManager_test.cpp b/cmds/statsd/tests/MetricsManager_test.cpp
index 6259757..8dd6083 100644
--- a/cmds/statsd/tests/MetricsManager_test.cpp
+++ b/cmds/statsd/tests/MetricsManager_test.cpp
@@ -28,7 +28,7 @@
 #include "src/metrics/GaugeMetricProducer.h"
 #include "src/metrics/MetricProducer.h"
 #include "src/metrics/ValueMetricProducer.h"
-#include "src/metrics/metrics_manager_util.h"
+#include "src/metrics/parsing_utils/metrics_manager_util.h"
 #include "src/state/StateManager.h"
 #include "statsd_test_util.h"
 
@@ -48,7 +48,6 @@
 
 namespace {
 const ConfigKey kConfigKey(0, 12345);
-const long kAlertId = 3;
 
 const long timeBaseSec = 1000;
 
@@ -90,287 +89,6 @@
     metric->set_bucket(ONE_MINUTE);
     metric->mutable_dimensions_in_what()->set_field(2 /*SCREEN_STATE_CHANGE*/);
     metric->mutable_dimensions_in_what()->add_child()->set_field(1);
-
-    config.add_no_report_metric(3);
-
-    auto alert = config.add_alert();
-    alert->set_id(kAlertId);
-    alert->set_metric_id(3);
-    alert->set_num_buckets(10);
-    alert->set_refractory_period_secs(100);
-    alert->set_trigger_if_sum_gt(100);
-    return config;
-}
-
-StatsdConfig buildCircleMatchers() {
-    StatsdConfig config;
-    config.set_id(12345);
-
-    AtomMatcher* eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_id(StringToId("SCREEN_IS_ON"));
-
-    SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
-    simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/);
-    simpleAtomMatcher->add_field_value_matcher()->set_field(
-            1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
-    simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int(
-            2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/);
-
-    eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_id(StringToId("SCREEN_ON_OR_OFF"));
-
-    AtomMatcher_Combination* combination = eventMatcher->mutable_combination();
-    combination->set_operation(LogicalOperation::OR);
-    combination->add_matcher(StringToId("SCREEN_IS_ON"));
-    // Circle dependency
-    combination->add_matcher(StringToId("SCREEN_ON_OR_OFF"));
-
-    return config;
-}
-
-StatsdConfig buildAlertWithUnknownMetric() {
-    StatsdConfig config;
-    config.set_id(12345);
-
-    AtomMatcher* eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_id(StringToId("SCREEN_IS_ON"));
-
-    CountMetric* metric = config.add_count_metric();
-    metric->set_id(3);
-    metric->set_what(StringToId("SCREEN_IS_ON"));
-    metric->set_bucket(ONE_MINUTE);
-    metric->mutable_dimensions_in_what()->set_field(2 /*SCREEN_STATE_CHANGE*/);
-    metric->mutable_dimensions_in_what()->add_child()->set_field(1);
-
-    auto alert = config.add_alert();
-    alert->set_id(3);
-    alert->set_metric_id(2);
-    alert->set_num_buckets(10);
-    alert->set_refractory_period_secs(100);
-    alert->set_trigger_if_sum_gt(100);
-    return config;
-}
-
-StatsdConfig buildMissingMatchers() {
-    StatsdConfig config;
-    config.set_id(12345);
-
-    AtomMatcher* eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_id(StringToId("SCREEN_IS_ON"));
-
-    SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
-    simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/);
-    simpleAtomMatcher->add_field_value_matcher()->set_field(
-            1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
-    simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int(
-            2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/);
-
-    eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_id(StringToId("SCREEN_ON_OR_OFF"));
-
-    AtomMatcher_Combination* combination = eventMatcher->mutable_combination();
-    combination->set_operation(LogicalOperation::OR);
-    combination->add_matcher(StringToId("SCREEN_IS_ON"));
-    // undefined matcher
-    combination->add_matcher(StringToId("ABC"));
-
-    return config;
-}
-
-StatsdConfig buildMissingPredicate() {
-    StatsdConfig config;
-    config.set_id(12345);
-
-    CountMetric* metric = config.add_count_metric();
-    metric->set_id(3);
-    metric->set_what(StringToId("SCREEN_EVENT"));
-    metric->set_bucket(ONE_MINUTE);
-    metric->set_condition(StringToId("SOME_CONDITION"));
-
-    AtomMatcher* eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_id(StringToId("SCREEN_EVENT"));
-
-    SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
-    simpleAtomMatcher->set_atom_id(2);
-
-    return config;
-}
-
-StatsdConfig buildDimensionMetricsWithMultiTags() {
-    StatsdConfig config;
-    config.set_id(12345);
-
-    AtomMatcher* eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_id(StringToId("BATTERY_VERY_LOW"));
-    SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
-    simpleAtomMatcher->set_atom_id(2);
-
-    eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_id(StringToId("BATTERY_VERY_VERY_LOW"));
-    simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
-    simpleAtomMatcher->set_atom_id(3);
-
-    eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_id(StringToId("BATTERY_LOW"));
-
-    AtomMatcher_Combination* combination = eventMatcher->mutable_combination();
-    combination->set_operation(LogicalOperation::OR);
-    combination->add_matcher(StringToId("BATTERY_VERY_LOW"));
-    combination->add_matcher(StringToId("BATTERY_VERY_VERY_LOW"));
-
-    // Count process state changes, slice by uid, while SCREEN_IS_OFF
-    CountMetric* metric = config.add_count_metric();
-    metric->set_id(3);
-    metric->set_what(StringToId("BATTERY_LOW"));
-    metric->set_bucket(ONE_MINUTE);
-    // This case is interesting. We want to dimension across two atoms.
-    metric->mutable_dimensions_in_what()->add_child()->set_field(1);
-
-    auto alert = config.add_alert();
-    alert->set_id(kAlertId);
-    alert->set_metric_id(3);
-    alert->set_num_buckets(10);
-    alert->set_refractory_period_secs(100);
-    alert->set_trigger_if_sum_gt(100);
-    return config;
-}
-
-StatsdConfig buildCirclePredicates() {
-    StatsdConfig config;
-    config.set_id(12345);
-
-    AtomMatcher* eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_id(StringToId("SCREEN_IS_ON"));
-
-    SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
-    simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/);
-    simpleAtomMatcher->add_field_value_matcher()->set_field(
-            1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
-    simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int(
-            2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/);
-
-    eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_id(StringToId("SCREEN_IS_OFF"));
-
-    simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
-    simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/);
-    simpleAtomMatcher->add_field_value_matcher()->set_field(
-            1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
-    simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int(
-            1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_OFF*/);
-
-    auto condition = config.add_predicate();
-    condition->set_id(StringToId("SCREEN_IS_ON"));
-    SimplePredicate* simplePredicate = condition->mutable_simple_predicate();
-    simplePredicate->set_start(StringToId("SCREEN_IS_ON"));
-    simplePredicate->set_stop(StringToId("SCREEN_IS_OFF"));
-
-    condition = config.add_predicate();
-    condition->set_id(StringToId("SCREEN_IS_EITHER_ON_OFF"));
-
-    Predicate_Combination* combination = condition->mutable_combination();
-    combination->set_operation(LogicalOperation::OR);
-    combination->add_predicate(StringToId("SCREEN_IS_ON"));
-    combination->add_predicate(StringToId("SCREEN_IS_EITHER_ON_OFF"));
-
-    return config;
-}
-
-StatsdConfig buildConfigWithDifferentPredicates() {
-    StatsdConfig config;
-    config.set_id(12345);
-
-    auto pulledAtomMatcher =
-            CreateSimpleAtomMatcher("SUBSYSTEM_SLEEP", util::SUBSYSTEM_SLEEP_STATE);
-    *config.add_atom_matcher() = pulledAtomMatcher;
-    auto screenOnAtomMatcher = CreateScreenTurnedOnAtomMatcher();
-    *config.add_atom_matcher() = screenOnAtomMatcher;
-    auto screenOffAtomMatcher = CreateScreenTurnedOffAtomMatcher();
-    *config.add_atom_matcher() = screenOffAtomMatcher;
-    auto batteryNoneAtomMatcher = CreateBatteryStateNoneMatcher();
-    *config.add_atom_matcher() = batteryNoneAtomMatcher;
-    auto batteryUsbAtomMatcher = CreateBatteryStateUsbMatcher();
-    *config.add_atom_matcher() = batteryUsbAtomMatcher;
-
-    // Simple condition with InitialValue set to default (unknown).
-    auto screenOnUnknownPredicate = CreateScreenIsOnPredicate();
-    *config.add_predicate() = screenOnUnknownPredicate;
-
-    // Simple condition with InitialValue set to false.
-    auto screenOnFalsePredicate = config.add_predicate();
-    screenOnFalsePredicate->set_id(StringToId("ScreenIsOnInitialFalse"));
-    SimplePredicate* simpleScreenOnFalsePredicate =
-            screenOnFalsePredicate->mutable_simple_predicate();
-    simpleScreenOnFalsePredicate->set_start(screenOnAtomMatcher.id());
-    simpleScreenOnFalsePredicate->set_stop(screenOffAtomMatcher.id());
-    simpleScreenOnFalsePredicate->set_initial_value(SimplePredicate_InitialValue_FALSE);
-
-    // Simple condition with InitialValue set to false.
-    auto onBatteryFalsePredicate = config.add_predicate();
-    onBatteryFalsePredicate->set_id(StringToId("OnBatteryInitialFalse"));
-    SimplePredicate* simpleOnBatteryFalsePredicate =
-            onBatteryFalsePredicate->mutable_simple_predicate();
-    simpleOnBatteryFalsePredicate->set_start(batteryNoneAtomMatcher.id());
-    simpleOnBatteryFalsePredicate->set_stop(batteryUsbAtomMatcher.id());
-    simpleOnBatteryFalsePredicate->set_initial_value(SimplePredicate_InitialValue_FALSE);
-
-    // Combination condition with both simple condition InitialValues set to false.
-    auto screenOnFalseOnBatteryFalsePredicate = config.add_predicate();
-    screenOnFalseOnBatteryFalsePredicate->set_id(StringToId("ScreenOnFalseOnBatteryFalse"));
-    screenOnFalseOnBatteryFalsePredicate->mutable_combination()->set_operation(
-            LogicalOperation::AND);
-    addPredicateToPredicateCombination(*screenOnFalsePredicate,
-                                       screenOnFalseOnBatteryFalsePredicate);
-    addPredicateToPredicateCombination(*onBatteryFalsePredicate,
-                                       screenOnFalseOnBatteryFalsePredicate);
-
-    // Combination condition with one simple condition InitialValue set to unknown and one set to
-    // false.
-    auto screenOnUnknownOnBatteryFalsePredicate = config.add_predicate();
-    screenOnUnknownOnBatteryFalsePredicate->set_id(StringToId("ScreenOnUnknowneOnBatteryFalse"));
-    screenOnUnknownOnBatteryFalsePredicate->mutable_combination()->set_operation(
-            LogicalOperation::AND);
-    addPredicateToPredicateCombination(screenOnUnknownPredicate,
-                                       screenOnUnknownOnBatteryFalsePredicate);
-    addPredicateToPredicateCombination(*onBatteryFalsePredicate,
-                                       screenOnUnknownOnBatteryFalsePredicate);
-
-    // Simple condition metric with initial value false.
-    ValueMetric* metric1 = config.add_value_metric();
-    metric1->set_id(StringToId("ValueSubsystemSleepWhileScreenOnInitialFalse"));
-    metric1->set_what(pulledAtomMatcher.id());
-    *metric1->mutable_value_field() =
-            CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */});
-    metric1->set_bucket(FIVE_MINUTES);
-    metric1->set_condition(screenOnFalsePredicate->id());
-
-    // Simple condition metric with initial value unknown.
-    ValueMetric* metric2 = config.add_value_metric();
-    metric2->set_id(StringToId("ValueSubsystemSleepWhileScreenOnInitialUnknown"));
-    metric2->set_what(pulledAtomMatcher.id());
-    *metric2->mutable_value_field() =
-            CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */});
-    metric2->set_bucket(FIVE_MINUTES);
-    metric2->set_condition(screenOnUnknownPredicate.id());
-
-    // Combination condition metric with initial values false and false.
-    ValueMetric* metric3 = config.add_value_metric();
-    metric3->set_id(StringToId("ValueSubsystemSleepWhileScreenOnFalseDeviceUnpluggedFalse"));
-    metric3->set_what(pulledAtomMatcher.id());
-    *metric3->mutable_value_field() =
-            CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */});
-    metric3->set_bucket(FIVE_MINUTES);
-    metric3->set_condition(screenOnFalseOnBatteryFalsePredicate->id());
-
-    // Combination condition metric with initial values unknown and false.
-    ValueMetric* metric4 = config.add_value_metric();
-    metric4->set_id(StringToId("ValueSubsystemSleepWhileScreenOnUnknownDeviceUnpluggedFalse"));
-    metric4->set_what(pulledAtomMatcher.id());
-    *metric4->mutable_value_field() =
-            CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */});
-    metric4->set_bucket(FIVE_MINUTES);
-    metric4->set_condition(screenOnUnknownOnBatteryFalsePredicate->id());
-
     return config;
 }
 
@@ -379,274 +97,6 @@
 }
 }  // anonymous namespace
 
-TEST(MetricsManagerTest, TestInitialConditions) {
-    UidMap uidMap;
-    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
-    sp<AlarmMonitor> anomalyAlarmMonitor;
-    sp<AlarmMonitor> periodicAlarmMonitor;
-    StatsdConfig config = buildConfigWithDifferentPredicates();
-    set<int> allTagIds;
-    vector<sp<LogMatchingTracker>> allAtomMatchers;
-    vector<sp<ConditionTracker>> allConditionTrackers;
-    vector<sp<MetricProducer>> allMetricProducers;
-    std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
-    std::vector<sp<AlarmTracker>> allAlarmTrackers;
-    unordered_map<int, std::vector<int>> conditionToMetricMap;
-    unordered_map<int, std::vector<int>> trackerToMetricMap;
-    unordered_map<int, std::vector<int>> trackerToConditionMap;
-    unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
-    unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
-    unordered_map<int64_t, int> alertTrackerMap;
-    vector<int> metricsWithActivation;
-    std::set<int64_t> noReportMetricIds;
-
-    EXPECT_TRUE(initStatsdConfig(
-            kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
-            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchers, allConditionTrackers,
-            allMetricProducers, allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap,
-            trackerToMetricMap, trackerToConditionMap, activationAtomTrackerToMetricMap,
-            deactivationAtomTrackerToMetricMap, alertTrackerMap, metricsWithActivation,
-            noReportMetricIds));
-    ASSERT_EQ(4u, allMetricProducers.size());
-    ASSERT_EQ(5u, allConditionTrackers.size());
-
-    ConditionKey queryKey;
-    vector<ConditionState> conditionCache(5, ConditionState::kNotEvaluated);
-
-    allConditionTrackers[3]->isConditionMet(queryKey, allConditionTrackers, false, conditionCache);
-    allConditionTrackers[4]->isConditionMet(queryKey, allConditionTrackers, false, conditionCache);
-    EXPECT_EQ(ConditionState::kUnknown, conditionCache[0]);
-    EXPECT_EQ(ConditionState::kFalse, conditionCache[1]);
-    EXPECT_EQ(ConditionState::kFalse, conditionCache[2]);
-    EXPECT_EQ(ConditionState::kFalse, conditionCache[3]);
-    EXPECT_EQ(ConditionState::kUnknown, conditionCache[4]);
-
-    EXPECT_EQ(ConditionState::kFalse, allMetricProducers[0]->mCondition);
-    EXPECT_EQ(ConditionState::kUnknown, allMetricProducers[1]->mCondition);
-    EXPECT_EQ(ConditionState::kFalse, allMetricProducers[2]->mCondition);
-    EXPECT_EQ(ConditionState::kUnknown, allMetricProducers[3]->mCondition);
-}
-
-TEST(MetricsManagerTest, TestGoodConfig) {
-    UidMap uidMap;
-    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
-    sp<AlarmMonitor> anomalyAlarmMonitor;
-    sp<AlarmMonitor> periodicAlarmMonitor;
-    StatsdConfig config = buildGoodConfig();
-    set<int> allTagIds;
-    vector<sp<LogMatchingTracker>> allAtomMatchers;
-    vector<sp<ConditionTracker>> allConditionTrackers;
-    vector<sp<MetricProducer>> allMetricProducers;
-    std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
-    std::vector<sp<AlarmTracker>> allAlarmTrackers;
-    unordered_map<int, std::vector<int>> conditionToMetricMap;
-    unordered_map<int, std::vector<int>> trackerToMetricMap;
-    unordered_map<int, std::vector<int>> trackerToConditionMap;
-    unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
-    unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
-    unordered_map<int64_t, int> alertTrackerMap;
-    vector<int> metricsWithActivation;
-    std::set<int64_t> noReportMetricIds;
-
-    EXPECT_TRUE(initStatsdConfig(kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor,
-                                 periodicAlarmMonitor, timeBaseSec, timeBaseSec, allTagIds,
-                                 allAtomMatchers, allConditionTrackers, allMetricProducers,
-                                 allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap,
-                                 trackerToMetricMap, trackerToConditionMap,
-                                 activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
-                                 alertTrackerMap, metricsWithActivation,
-                                 noReportMetricIds));
-    ASSERT_EQ(1u, allMetricProducers.size());
-    ASSERT_EQ(1u, allAnomalyTrackers.size());
-    ASSERT_EQ(1u, noReportMetricIds.size());
-    ASSERT_EQ(1u, alertTrackerMap.size());
-    EXPECT_NE(alertTrackerMap.find(kAlertId), alertTrackerMap.end());
-    EXPECT_EQ(alertTrackerMap.find(kAlertId)->second, 0);
-}
-
-TEST(MetricsManagerTest, TestDimensionMetricsWithMultiTags) {
-    UidMap uidMap;
-    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
-    sp<AlarmMonitor> anomalyAlarmMonitor;
-    sp<AlarmMonitor> periodicAlarmMonitor;
-    StatsdConfig config = buildDimensionMetricsWithMultiTags();
-    set<int> allTagIds;
-    vector<sp<LogMatchingTracker>> allAtomMatchers;
-    vector<sp<ConditionTracker>> allConditionTrackers;
-    vector<sp<MetricProducer>> allMetricProducers;
-    std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
-    std::vector<sp<AlarmTracker>> allAlarmTrackers;
-    unordered_map<int, std::vector<int>> conditionToMetricMap;
-    unordered_map<int, std::vector<int>> trackerToMetricMap;
-    unordered_map<int, std::vector<int>> trackerToConditionMap;
-    unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
-    unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
-    unordered_map<int64_t, int> alertTrackerMap;
-    vector<int> metricsWithActivation;
-    std::set<int64_t> noReportMetricIds;
-
-    EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor,
-                                  periodicAlarmMonitor, timeBaseSec, timeBaseSec, allTagIds,
-                                  allAtomMatchers, allConditionTrackers, allMetricProducers,
-                                  allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap,
-                                  trackerToMetricMap, trackerToConditionMap,
-                                  activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
-                                  alertTrackerMap, metricsWithActivation,
-                                  noReportMetricIds));
-}
-
-TEST(MetricsManagerTest, TestCircleLogMatcherDependency) {
-    UidMap uidMap;
-    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
-    sp<AlarmMonitor> anomalyAlarmMonitor;
-    sp<AlarmMonitor> periodicAlarmMonitor;
-    StatsdConfig config = buildCircleMatchers();
-    set<int> allTagIds;
-    vector<sp<LogMatchingTracker>> allAtomMatchers;
-    vector<sp<ConditionTracker>> allConditionTrackers;
-    vector<sp<MetricProducer>> allMetricProducers;
-    std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
-    std::vector<sp<AlarmTracker>> allAlarmTrackers;
-    unordered_map<int, std::vector<int>> conditionToMetricMap;
-    unordered_map<int, std::vector<int>> trackerToMetricMap;
-    unordered_map<int, std::vector<int>> trackerToConditionMap;
-    unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
-    unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
-    unordered_map<int64_t, int> alertTrackerMap;
-    vector<int> metricsWithActivation;
-    std::set<int64_t> noReportMetricIds;
-
-    EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor,
-                                  periodicAlarmMonitor, timeBaseSec, timeBaseSec, allTagIds,
-                                  allAtomMatchers, allConditionTrackers, allMetricProducers,
-                                  allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap,
-                                  trackerToMetricMap, trackerToConditionMap,
-                                  activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
-                                  alertTrackerMap, metricsWithActivation,
-                                  noReportMetricIds));
-}
-
-TEST(MetricsManagerTest, TestMissingMatchers) {
-    UidMap uidMap;
-    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
-    sp<AlarmMonitor> anomalyAlarmMonitor;
-    sp<AlarmMonitor> periodicAlarmMonitor;
-    StatsdConfig config = buildMissingMatchers();
-    set<int> allTagIds;
-    vector<sp<LogMatchingTracker>> allAtomMatchers;
-    vector<sp<ConditionTracker>> allConditionTrackers;
-    vector<sp<MetricProducer>> allMetricProducers;
-    std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
-    std::vector<sp<AlarmTracker>> allAlarmTrackers;
-    unordered_map<int, std::vector<int>> conditionToMetricMap;
-    unordered_map<int, std::vector<int>> trackerToMetricMap;
-    unordered_map<int, std::vector<int>> trackerToConditionMap;
-    unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
-    unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
-    unordered_map<int64_t, int> alertTrackerMap;
-    vector<int> metricsWithActivation;
-    std::set<int64_t> noReportMetricIds;
-    EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor,
-                                  periodicAlarmMonitor, timeBaseSec, timeBaseSec, allTagIds,
-                                  allAtomMatchers, allConditionTrackers, allMetricProducers,
-                                  allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap,
-                                  trackerToMetricMap, trackerToConditionMap,
-                                  activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
-                                  alertTrackerMap, metricsWithActivation,
-                                  noReportMetricIds));
-}
-
-TEST(MetricsManagerTest, TestMissingPredicate) {
-    UidMap uidMap;
-    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
-    sp<AlarmMonitor> anomalyAlarmMonitor;
-    sp<AlarmMonitor> periodicAlarmMonitor;
-    StatsdConfig config = buildMissingPredicate();
-    set<int> allTagIds;
-    vector<sp<LogMatchingTracker>> allAtomMatchers;
-    vector<sp<ConditionTracker>> allConditionTrackers;
-    vector<sp<MetricProducer>> allMetricProducers;
-    std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
-    std::vector<sp<AlarmTracker>> allAlarmTrackers;
-    unordered_map<int, std::vector<int>> conditionToMetricMap;
-    unordered_map<int, std::vector<int>> trackerToMetricMap;
-    unordered_map<int, std::vector<int>> trackerToConditionMap;
-    unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
-    unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
-    unordered_map<int64_t, int> alertTrackerMap;
-    vector<int> metricsWithActivation;
-    std::set<int64_t> noReportMetricIds;
-    EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor,
-                                  periodicAlarmMonitor, timeBaseSec, timeBaseSec, allTagIds,
-                                  allAtomMatchers, allConditionTrackers, allMetricProducers,
-                                  allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap,
-                                  trackerToMetricMap, trackerToConditionMap,
-                                  activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
-                                  alertTrackerMap, metricsWithActivation, noReportMetricIds));
-}
-
-TEST(MetricsManagerTest, TestCirclePredicateDependency) {
-    UidMap uidMap;
-    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
-    sp<AlarmMonitor> anomalyAlarmMonitor;
-    sp<AlarmMonitor> periodicAlarmMonitor;
-    StatsdConfig config = buildCirclePredicates();
-    set<int> allTagIds;
-    vector<sp<LogMatchingTracker>> allAtomMatchers;
-    vector<sp<ConditionTracker>> allConditionTrackers;
-    vector<sp<MetricProducer>> allMetricProducers;
-    std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
-    std::vector<sp<AlarmTracker>> allAlarmTrackers;
-    unordered_map<int, std::vector<int>> conditionToMetricMap;
-    unordered_map<int, std::vector<int>> trackerToMetricMap;
-    unordered_map<int, std::vector<int>> trackerToConditionMap;
-    unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
-    unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
-    unordered_map<int64_t, int> alertTrackerMap;
-    vector<int> metricsWithActivation;
-    std::set<int64_t> noReportMetricIds;
-
-    EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor,
-                                  periodicAlarmMonitor, timeBaseSec, timeBaseSec, allTagIds,
-                                  allAtomMatchers, allConditionTrackers, allMetricProducers,
-                                  allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap,
-                                  trackerToMetricMap, trackerToConditionMap,
-                                  activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
-                                  alertTrackerMap, metricsWithActivation,
-                                  noReportMetricIds));
-}
-
-TEST(MetricsManagerTest, testAlertWithUnknownMetric) {
-    UidMap uidMap;
-    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
-    sp<AlarmMonitor> anomalyAlarmMonitor;
-    sp<AlarmMonitor> periodicAlarmMonitor;
-    StatsdConfig config = buildAlertWithUnknownMetric();
-    set<int> allTagIds;
-    vector<sp<LogMatchingTracker>> allAtomMatchers;
-    vector<sp<ConditionTracker>> allConditionTrackers;
-    vector<sp<MetricProducer>> allMetricProducers;
-    std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
-    std::vector<sp<AlarmTracker>> allAlarmTrackers;
-    unordered_map<int, std::vector<int>> conditionToMetricMap;
-    unordered_map<int, std::vector<int>> trackerToMetricMap;
-    unordered_map<int, std::vector<int>> trackerToConditionMap;
-    unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
-    unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
-    unordered_map<int64_t, int> alertTrackerMap;
-    vector<int> metricsWithActivation;
-    std::set<int64_t> noReportMetricIds;
-
-    EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor,
-                                  periodicAlarmMonitor, timeBaseSec, timeBaseSec, allTagIds,
-                                  allAtomMatchers, allConditionTrackers, allMetricProducers,
-                                  allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap,
-                                  trackerToMetricMap, trackerToConditionMap,
-                                  activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
-                                  alertTrackerMap, metricsWithActivation,
-                                  noReportMetricIds));
-}
-
 TEST(MetricsManagerTest, TestLogSources) {
     string app1 = "app1";
     set<int32_t> app1Uids = {1111, 11111};
@@ -680,7 +130,7 @@
     sp<AlarmMonitor> anomalyAlarmMonitor;
     sp<AlarmMonitor> periodicAlarmMonitor;
 
-    StatsdConfig config = buildGoodConfig();
+    StatsdConfig config;
     config.add_allowed_log_source("AID_SYSTEM");
     config.add_allowed_log_source(app1);
     config.add_default_pull_packages("AID_SYSTEM");
@@ -744,7 +194,7 @@
     sp<AlarmMonitor> anomalyAlarmMonitor;
     sp<AlarmMonitor> periodicAlarmMonitor;
 
-    StatsdConfig config = buildGoodConfig();
+    StatsdConfig config;
     config.add_whitelisted_atom_ids(3);
     config.add_whitelisted_atom_ids(4);
 
diff --git a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
index caea42d..d96ff8a 100644
--- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
@@ -47,7 +47,6 @@
 const ConfigKey kConfigKey(0, 12345);
 const int tagId = 1;
 const int64_t metricId = 123;
-const int64_t atomMatcherId = 678;
 const int logEventMatcherIndex = 0;
 const int64_t bucketStartTimeNs = 10 * NS_PER_SEC;
 const int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
@@ -94,11 +93,8 @@
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
-    sp<EventMatcherWizard> eventMatcherWizard = new EventMatcherWizard({
-        new SimpleLogMatchingTracker(atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+    sp<EventMatcherWizard> eventMatcherWizard =
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
 
@@ -127,12 +123,8 @@
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
     EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return());
@@ -217,12 +209,8 @@
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
 
     GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
                                       wizard, logEventMatcherIndex, eventMatcherWizard,
@@ -301,12 +289,9 @@
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
+
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
     EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return());
     EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillOnce(Return());
@@ -378,12 +363,8 @@
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
     EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return());
@@ -428,12 +409,8 @@
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
 
     int64_t conditionChangeNs = bucketStartTimeNs + 8;
 
@@ -502,12 +479,8 @@
     dim->set_field(tagId);
     dim->add_child()->set_field(1);
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     EXPECT_CALL(*wizard, query(_, _, _))
@@ -577,12 +550,8 @@
     gaugeFieldMatcher->set_field(tagId);
     gaugeFieldMatcher->add_child()->set_field(2);
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
 
     GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
                                       wizard, logEventMatcherIndex, eventMatcherWizard, tagId, -1,
@@ -657,12 +626,8 @@
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
     EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _))
@@ -729,12 +694,8 @@
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
     EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _))
@@ -807,12 +768,8 @@
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
     EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 3, _))
diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
index 9889250..5524ebc 100644
--- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
@@ -46,7 +46,6 @@
 const ConfigKey kConfigKey(0, 12345);
 const int tagId = 1;
 const int64_t metricId = 123;
-const int64_t atomMatcherId = 678;
 const int logEventMatcherIndex = 0;
 const int64_t bucketStartTimeNs = 10000000000;
 const int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
@@ -99,12 +98,8 @@
 public:
     static sp<ValueMetricProducer> createValueProducerNoConditions(
             sp<MockStatsPullerManager>& pullerManager, ValueMetric& metric) {
-        UidMap uidMap;
-        SimpleAtomMatcher atomMatcher;
-        atomMatcher.set_atom_id(tagId);
         sp<EventMatcherWizard> eventMatcherWizard =
-                new EventMatcherWizard({new SimpleLogMatchingTracker(
-                        atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+                createEventMatcherWizard(tagId, logEventMatcherIndex);
         sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
         EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _))
                 .WillOnce(Return());
@@ -122,12 +117,8 @@
     static sp<ValueMetricProducer> createValueProducerWithCondition(
             sp<MockStatsPullerManager>& pullerManager, ValueMetric& metric,
             ConditionState conditionAfterFirstBucketPrepared) {
-        UidMap uidMap;
-        SimpleAtomMatcher atomMatcher;
-        atomMatcher.set_atom_id(tagId);
         sp<EventMatcherWizard> eventMatcherWizard =
-                new EventMatcherWizard({new SimpleLogMatchingTracker(
-                        atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+                createEventMatcherWizard(tagId, logEventMatcherIndex);
         sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
         EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _))
                 .WillOnce(Return());
@@ -147,12 +138,8 @@
             sp<MockStatsPullerManager>& pullerManager, ValueMetric& metric,
             vector<int32_t> slicedStateAtoms,
             unordered_map<int, unordered_map<int, int64_t>> stateGroupMap) {
-        UidMap uidMap;
-        SimpleAtomMatcher atomMatcher;
-        atomMatcher.set_atom_id(tagId);
         sp<EventMatcherWizard> eventMatcherWizard =
-                new EventMatcherWizard({new SimpleLogMatchingTracker(
-                        atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+                createEventMatcherWizard(tagId, logEventMatcherIndex);
         sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
         EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _))
                 .WillOnce(Return());
@@ -172,12 +159,8 @@
             vector<int32_t> slicedStateAtoms,
             unordered_map<int, unordered_map<int, int64_t>> stateGroupMap,
             ConditionState conditionAfterFirstBucketPrepared) {
-        UidMap uidMap;
-        SimpleAtomMatcher atomMatcher;
-        atomMatcher.set_atom_id(tagId);
         sp<EventMatcherWizard> eventMatcherWizard =
-                new EventMatcherWizard({new SimpleLogMatchingTracker(
-                        atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+                createEventMatcherWizard(tagId, logEventMatcherIndex);
         sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
         EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _))
                 .WillOnce(Return());
@@ -237,12 +220,8 @@
     ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
 
     int64_t startTimeBase = 11;
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
 
@@ -267,12 +246,8 @@
 TEST(ValueMetricProducerTest, TestFirstBucket) {
     ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
 
@@ -421,15 +396,11 @@
 TEST(ValueMetricProducerTest, TestPulledEventsWithFiltering) {
     ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
-    auto keyValue = atomMatcher.add_field_value_matcher();
-    keyValue->set_field(1);
-    keyValue->set_eq_int(3);
+    FieldValueMatcher fvm;
+    fvm.set_field(1);
+    fvm.set_eq_int(3);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex, {fvm});
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
     EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return());
@@ -698,12 +669,8 @@
 TEST_P(ValueMetricProducerTest_PartialBucket, TestPushedEvents) {
     ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
 
@@ -759,12 +726,8 @@
 TEST_P(ValueMetricProducerTest_PartialBucket, TestPulledValue) {
     ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
     int64_t partialBucketSplitTimeNs = bucket2StartTimeNs + 150;
@@ -820,12 +783,8 @@
     ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
     metric.set_split_bucket_for_app_upgrade(false);
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
     EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return());
@@ -900,12 +859,8 @@
 TEST(ValueMetricProducerTest, TestPushedEventsWithoutCondition) {
     ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
 
@@ -944,12 +899,8 @@
 TEST(ValueMetricProducerTest, TestPushedEventsWithCondition) {
     ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
 
@@ -1015,12 +966,8 @@
 
     ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
 
@@ -1360,12 +1307,8 @@
     ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
     metric.set_aggregation_type(ValueMetric::MIN);
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
 
@@ -1404,12 +1347,8 @@
     ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
     metric.set_aggregation_type(ValueMetric::MAX);
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
 
@@ -1447,12 +1386,8 @@
     ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
     metric.set_aggregation_type(ValueMetric::AVG);
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
 
@@ -1495,12 +1430,8 @@
     ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
     metric.set_aggregation_type(ValueMetric::SUM);
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
 
@@ -1539,12 +1470,8 @@
     metric.set_aggregation_type(ValueMetric::MIN);
     metric.set_use_diff(true);
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
 
@@ -1611,12 +1538,8 @@
     metric.set_aggregation_type(ValueMetric::MIN);
     metric.set_use_diff(true);
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
 
@@ -2140,12 +2063,8 @@
 TEST(ValueMetricProducerTest, TestResetBaseOnPullTooLate) {
     ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
     EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return());
@@ -2963,12 +2882,8 @@
 TEST(ValueMetricProducerTest, TestPullNeededFastDump) {
     ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
     EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return());
@@ -3001,12 +2916,8 @@
 TEST(ValueMetricProducerTest, TestFastDumpWithoutCurrentBucket) {
     ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
     EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return());
@@ -3045,12 +2956,8 @@
 TEST(ValueMetricProducerTest, TestPullNeededNoTimeConstraints) {
     ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
     EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return());
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
new file mode 100644
index 0000000..6b50fe5
--- /dev/null
+++ b/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp
@@ -0,0 +1,382 @@
+// 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 "src/metrics/parsing_utils/config_update_utils.h"
+
+#include <gtest/gtest.h>
+#include <private/android_filesystem_config.h>
+#include <stdio.h>
+
+#include <set>
+#include <unordered_map>
+#include <vector>
+
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "src/metrics/parsing_utils/metrics_manager_util.h"
+#include "tests/statsd_test_util.h"
+
+using namespace testing;
+using android::sp;
+using android::os::statsd::Predicate;
+using std::map;
+using std::set;
+using std::unordered_map;
+using std::vector;
+
+#ifdef __ANDROID__
+
+namespace android {
+namespace os {
+namespace statsd {
+
+namespace {
+
+ConfigKey key(123, 456);
+const int64_t timeBaseNs = 1000;
+sp<UidMap> uidMap = new UidMap();
+sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+sp<AlarmMonitor> anomalyAlarmMonitor;
+sp<AlarmMonitor> periodicAlarmMonitor;
+set<int> allTagIds;
+vector<sp<LogMatchingTracker>> oldAtomMatchers;
+unordered_map<int64_t, int> oldLogTrackerMap;
+vector<sp<ConditionTracker>> oldConditionTrackers;
+vector<sp<MetricProducer>> oldMetricProducers;
+std::vector<sp<AnomalyTracker>> oldAnomalyTrackers;
+std::vector<sp<AlarmTracker>> oldAlarmTrackers;
+unordered_map<int, std::vector<int>> conditionToMetricMap;
+unordered_map<int, std::vector<int>> trackerToMetricMap;
+unordered_map<int, std::vector<int>> trackerToConditionMap;
+unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
+unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
+unordered_map<int64_t, int> alertTrackerMap;
+vector<int> metricsWithActivation;
+std::set<int64_t> noReportMetricIds;
+
+class ConfigUpdateTest : public ::testing::Test {
+public:
+    ConfigUpdateTest() {
+    }
+
+    void SetUp() override {
+        allTagIds.clear();
+        oldAtomMatchers.clear();
+        oldLogTrackerMap.clear();
+        oldConditionTrackers.clear();
+        oldMetricProducers.clear();
+        oldAnomalyTrackers.clear();
+        oldAlarmTrackers.clear();
+        conditionToMetricMap.clear();
+        trackerToMetricMap.clear();
+        trackerToConditionMap.clear();
+        activationAtomTrackerToMetricMap.clear();
+        deactivationAtomTrackerToMetricMap.clear();
+        alertTrackerMap.clear();
+        metricsWithActivation.clear();
+        noReportMetricIds.clear();
+    }
+};
+
+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);
+}
+
+}  // anonymous namespace
+
+TEST_F(ConfigUpdateTest, TestSimpleMatcherPreserve) {
+    StatsdConfig config;
+    AtomMatcher matcher = CreateSimpleAtomMatcher("TEST", /*atom=*/10);
+    int64_t matcherId = matcher.id();
+    *config.add_atom_matcher() = matcher;
+
+    // Create an initial config.
+    EXPECT_TRUE(initConfig(config));
+
+    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));
+    EXPECT_EQ(matchersToUpdate[0], UPDATE_PRESERVE);
+}
+
+TEST_F(ConfigUpdateTest, TestSimpleMatcherReplace) {
+    StatsdConfig config;
+    AtomMatcher matcher = CreateSimpleAtomMatcher("TEST", /*atom=*/10);
+    *config.add_atom_matcher() = matcher;
+
+    EXPECT_TRUE(initConfig(config));
+
+    StatsdConfig newConfig;
+    // Same id, different atom, so should be replaced.
+    AtomMatcher newMatcher = CreateSimpleAtomMatcher("TEST", /*atom=*/11);
+    int64_t matcherId = newMatcher.id();
+    EXPECT_EQ(matcherId, matcher.id());
+    *newConfig.add_atom_matcher() = newMatcher;
+
+    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));
+    EXPECT_EQ(matchersToUpdate[0], UPDATE_REPLACE);
+}
+
+TEST_F(ConfigUpdateTest, TestCombinationMatcherPreserve) {
+    StatsdConfig config;
+    AtomMatcher matcher1 = CreateSimpleAtomMatcher("TEST1", /*atom=*/10);
+    int64_t matcher1Id = matcher1.id();
+    *config.add_atom_matcher() = matcher1;
+
+    AtomMatcher matcher2 = CreateSimpleAtomMatcher("TEST2", /*atom=*/11);
+    *config.add_atom_matcher() = matcher2;
+    int64_t matcher2Id = matcher2.id();
+
+    AtomMatcher matcher3;
+    matcher3.set_id(StringToId("TEST3"));
+    AtomMatcher_Combination* combination = matcher3.mutable_combination();
+    combination->set_operation(LogicalOperation::OR);
+    combination->add_matcher(matcher1Id);
+    combination->add_matcher(matcher2Id);
+    int64_t matcher3Id = matcher3.id();
+    *config.add_atom_matcher() = matcher3;
+
+    EXPECT_TRUE(initConfig(config));
+
+    StatsdConfig newConfig;
+    unordered_map<int64_t, int> newLogTrackerMap;
+    // Same matchers, different order, all should be preserved.
+    *newConfig.add_atom_matcher() = matcher2;
+    newLogTrackerMap[matcher2Id] = 0;
+    *newConfig.add_atom_matcher() = matcher3;
+    newLogTrackerMap[matcher3Id] = 1;
+    *newConfig.add_atom_matcher() = matcher1;
+    newLogTrackerMap[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_EQ(matchersToUpdate[0], UPDATE_PRESERVE);
+    EXPECT_EQ(matchersToUpdate[1], UPDATE_PRESERVE);
+    EXPECT_EQ(matchersToUpdate[2], UPDATE_PRESERVE);
+}
+
+TEST_F(ConfigUpdateTest, TestCombinationMatcherReplace) {
+    StatsdConfig config;
+    AtomMatcher matcher1 = CreateSimpleAtomMatcher("TEST1", /*atom=*/10);
+    int64_t matcher1Id = matcher1.id();
+    *config.add_atom_matcher() = matcher1;
+
+    AtomMatcher matcher2 = CreateSimpleAtomMatcher("TEST2", /*atom=*/11);
+    *config.add_atom_matcher() = matcher2;
+    int64_t matcher2Id = matcher2.id();
+
+    AtomMatcher matcher3;
+    matcher3.set_id(StringToId("TEST3"));
+    AtomMatcher_Combination* combination = matcher3.mutable_combination();
+    combination->set_operation(LogicalOperation::OR);
+    combination->add_matcher(matcher1Id);
+    combination->add_matcher(matcher2Id);
+    int64_t matcher3Id = matcher3.id();
+    *config.add_atom_matcher() = matcher3;
+
+    EXPECT_TRUE(initConfig(config));
+
+    // Change the logical operation of the combination matcher, causing a replacement.
+    matcher3.mutable_combination()->set_operation(LogicalOperation::AND);
+
+    StatsdConfig newConfig;
+    unordered_map<int64_t, int> newLogTrackerMap;
+    *newConfig.add_atom_matcher() = matcher2;
+    newLogTrackerMap[matcher2Id] = 0;
+    *newConfig.add_atom_matcher() = matcher3;
+    newLogTrackerMap[matcher3Id] = 1;
+    *newConfig.add_atom_matcher() = matcher1;
+    newLogTrackerMap[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_EQ(matchersToUpdate[0], UPDATE_UNKNOWN);
+    EXPECT_EQ(matchersToUpdate[1], UPDATE_REPLACE);
+    EXPECT_EQ(matchersToUpdate[2], UPDATE_UNKNOWN);
+}
+
+TEST_F(ConfigUpdateTest, TestCombinationMatcherDepsChange) {
+    StatsdConfig config;
+    AtomMatcher matcher1 = CreateSimpleAtomMatcher("TEST1", /*atom=*/10);
+    int64_t matcher1Id = matcher1.id();
+    *config.add_atom_matcher() = matcher1;
+
+    AtomMatcher matcher2 = CreateSimpleAtomMatcher("TEST2", /*atom=*/11);
+    *config.add_atom_matcher() = matcher2;
+    int64_t matcher2Id = matcher2.id();
+
+    AtomMatcher matcher3;
+    matcher3.set_id(StringToId("TEST3"));
+    AtomMatcher_Combination* combination = matcher3.mutable_combination();
+    combination->set_operation(LogicalOperation::OR);
+    combination->add_matcher(matcher1Id);
+    combination->add_matcher(matcher2Id);
+    int64_t matcher3Id = matcher3.id();
+    *config.add_atom_matcher() = matcher3;
+
+    EXPECT_TRUE(initConfig(config));
+
+    // Change a dependency of matcher 3.
+    matcher2.mutable_simple_atom_matcher()->set_atom_id(12);
+
+    StatsdConfig newConfig;
+    unordered_map<int64_t, int> newLogTrackerMap;
+    *newConfig.add_atom_matcher() = matcher2;
+    newLogTrackerMap[matcher2Id] = 0;
+    *newConfig.add_atom_matcher() = matcher3;
+    newLogTrackerMap[matcher3Id] = 1;
+    *newConfig.add_atom_matcher() = matcher1;
+    newLogTrackerMap[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));
+    // 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);
+}
+
+TEST_F(ConfigUpdateTest, TestUpdateMatchers) {
+    StatsdConfig config;
+    // Will be preserved.
+    AtomMatcher simple1 = CreateSimpleAtomMatcher("SIMPLE1", /*atom=*/10);
+    int64_t simple1Id = simple1.id();
+    *config.add_atom_matcher() = simple1;
+
+    // Will be replaced.
+    AtomMatcher simple2 = CreateSimpleAtomMatcher("SIMPLE2", /*atom=*/11);
+    *config.add_atom_matcher() = simple2;
+    int64_t simple2Id = simple2.id();
+
+    // Will be removed.
+    AtomMatcher simple3 = CreateSimpleAtomMatcher("SIMPLE3", /*atom=*/12);
+    *config.add_atom_matcher() = simple3;
+    int64_t simple3Id = simple3.id();
+
+    // Will be preserved.
+    AtomMatcher combination1;
+    combination1.set_id(StringToId("combination1"));
+    AtomMatcher_Combination* combination = combination1.mutable_combination();
+    combination->set_operation(LogicalOperation::NOT);
+    combination->add_matcher(simple1Id);
+    int64_t combination1Id = combination1.id();
+    *config.add_atom_matcher() = combination1;
+
+    // Will be replaced since it depends on simple2.
+    AtomMatcher combination2;
+    combination2.set_id(StringToId("combination2"));
+    combination = combination2.mutable_combination();
+    combination->set_operation(LogicalOperation::AND);
+    combination->add_matcher(simple1Id);
+    combination->add_matcher(simple2Id);
+    int64_t combination2Id = combination2.id();
+    *config.add_atom_matcher() = combination2;
+
+    EXPECT_TRUE(initConfig(config));
+
+    // Change simple2, causing simple2 and combination2 to be replaced.
+    simple2.mutable_simple_atom_matcher()->set_atom_id(111);
+
+    // 2 new matchers: simple4 and combination3:
+    AtomMatcher simple4 = CreateSimpleAtomMatcher("SIMPLE4", /*atom=*/13);
+    int64_t simple4Id = simple4.id();
+
+    AtomMatcher combination3;
+    combination3.set_id(StringToId("combination3"));
+    combination = combination3.mutable_combination();
+    combination->set_operation(LogicalOperation::AND);
+    combination->add_matcher(simple4Id);
+    combination->add_matcher(simple2Id);
+    int64_t combination3Id = combination3.id();
+
+    StatsdConfig newConfig;
+    *newConfig.add_atom_matcher() = combination3;
+    *newConfig.add_atom_matcher() = simple2;
+    *newConfig.add_atom_matcher() = combination2;
+    *newConfig.add_atom_matcher() = simple1;
+    *newConfig.add_atom_matcher() = simple4;
+    *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));
+
+    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(newAtomMatchers.size(), 6);
+    // Make sure all atom matchers are initialized:
+    for (const sp<LogMatchingTracker>& tracker : newAtomMatchers) {
+        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)]);
+    // 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)]);
+
+    // 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);
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
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
new file mode 100644
index 0000000..4e97eaf
--- /dev/null
+++ b/cmds/statsd/tests/metrics/parsing_utils/metrics_manager_util_test.cpp
@@ -0,0 +1,708 @@
+// 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 "src/metrics/parsing_utils/metrics_manager_util.h"
+
+#include <gtest/gtest.h>
+#include <private/android_filesystem_config.h>
+#include <stdio.h>
+
+#include <set>
+#include <unordered_map>
+#include <vector>
+
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "src/condition/ConditionTracker.h"
+#include "src/matchers/LogMatchingTracker.h"
+#include "src/metrics/CountMetricProducer.h"
+#include "src/metrics/GaugeMetricProducer.h"
+#include "src/metrics/MetricProducer.h"
+#include "src/metrics/ValueMetricProducer.h"
+#include "src/state/StateManager.h"
+#include "tests/metrics/metrics_test_helper.h"
+#include "tests/statsd_test_util.h"
+
+using namespace testing;
+using android::sp;
+using android::os::statsd::Predicate;
+using std::map;
+using std::set;
+using std::unordered_map;
+using std::vector;
+
+#ifdef __ANDROID__
+
+namespace android {
+namespace os {
+namespace statsd {
+
+namespace {
+const ConfigKey kConfigKey(0, 12345);
+const long kAlertId = 3;
+
+const long timeBaseSec = 1000;
+
+StatsdConfig buildGoodConfig() {
+    StatsdConfig config;
+    config.set_id(12345);
+
+    AtomMatcher* eventMatcher = config.add_atom_matcher();
+    eventMatcher->set_id(StringToId("SCREEN_IS_ON"));
+
+    SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
+    simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/);
+    simpleAtomMatcher->add_field_value_matcher()->set_field(
+            1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
+    simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int(
+            2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/);
+
+    eventMatcher = config.add_atom_matcher();
+    eventMatcher->set_id(StringToId("SCREEN_IS_OFF"));
+
+    simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
+    simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/);
+    simpleAtomMatcher->add_field_value_matcher()->set_field(
+            1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
+    simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int(
+            1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_OFF*/);
+
+    eventMatcher = config.add_atom_matcher();
+    eventMatcher->set_id(StringToId("SCREEN_ON_OR_OFF"));
+
+    AtomMatcher_Combination* combination = eventMatcher->mutable_combination();
+    combination->set_operation(LogicalOperation::OR);
+    combination->add_matcher(StringToId("SCREEN_IS_ON"));
+    combination->add_matcher(StringToId("SCREEN_IS_OFF"));
+
+    CountMetric* metric = config.add_count_metric();
+    metric->set_id(3);
+    metric->set_what(StringToId("SCREEN_IS_ON"));
+    metric->set_bucket(ONE_MINUTE);
+    metric->mutable_dimensions_in_what()->set_field(2 /*SCREEN_STATE_CHANGE*/);
+    metric->mutable_dimensions_in_what()->add_child()->set_field(1);
+
+    config.add_no_report_metric(3);
+
+    auto alert = config.add_alert();
+    alert->set_id(kAlertId);
+    alert->set_metric_id(3);
+    alert->set_num_buckets(10);
+    alert->set_refractory_period_secs(100);
+    alert->set_trigger_if_sum_gt(100);
+    return config;
+}
+
+StatsdConfig buildCircleMatchers() {
+    StatsdConfig config;
+    config.set_id(12345);
+
+    AtomMatcher* eventMatcher = config.add_atom_matcher();
+    eventMatcher->set_id(StringToId("SCREEN_IS_ON"));
+
+    SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
+    simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/);
+    simpleAtomMatcher->add_field_value_matcher()->set_field(
+            1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
+    simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int(
+            2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/);
+
+    eventMatcher = config.add_atom_matcher();
+    eventMatcher->set_id(StringToId("SCREEN_ON_OR_OFF"));
+
+    AtomMatcher_Combination* combination = eventMatcher->mutable_combination();
+    combination->set_operation(LogicalOperation::OR);
+    combination->add_matcher(StringToId("SCREEN_IS_ON"));
+    // Circle dependency
+    combination->add_matcher(StringToId("SCREEN_ON_OR_OFF"));
+
+    return config;
+}
+
+StatsdConfig buildAlertWithUnknownMetric() {
+    StatsdConfig config;
+    config.set_id(12345);
+
+    AtomMatcher* eventMatcher = config.add_atom_matcher();
+    eventMatcher->set_id(StringToId("SCREEN_IS_ON"));
+
+    CountMetric* metric = config.add_count_metric();
+    metric->set_id(3);
+    metric->set_what(StringToId("SCREEN_IS_ON"));
+    metric->set_bucket(ONE_MINUTE);
+    metric->mutable_dimensions_in_what()->set_field(2 /*SCREEN_STATE_CHANGE*/);
+    metric->mutable_dimensions_in_what()->add_child()->set_field(1);
+
+    auto alert = config.add_alert();
+    alert->set_id(3);
+    alert->set_metric_id(2);
+    alert->set_num_buckets(10);
+    alert->set_refractory_period_secs(100);
+    alert->set_trigger_if_sum_gt(100);
+    return config;
+}
+
+StatsdConfig buildMissingMatchers() {
+    StatsdConfig config;
+    config.set_id(12345);
+
+    AtomMatcher* eventMatcher = config.add_atom_matcher();
+    eventMatcher->set_id(StringToId("SCREEN_IS_ON"));
+
+    SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
+    simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/);
+    simpleAtomMatcher->add_field_value_matcher()->set_field(
+            1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
+    simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int(
+            2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/);
+
+    eventMatcher = config.add_atom_matcher();
+    eventMatcher->set_id(StringToId("SCREEN_ON_OR_OFF"));
+
+    AtomMatcher_Combination* combination = eventMatcher->mutable_combination();
+    combination->set_operation(LogicalOperation::OR);
+    combination->add_matcher(StringToId("SCREEN_IS_ON"));
+    // undefined matcher
+    combination->add_matcher(StringToId("ABC"));
+
+    return config;
+}
+
+StatsdConfig buildMissingPredicate() {
+    StatsdConfig config;
+    config.set_id(12345);
+
+    CountMetric* metric = config.add_count_metric();
+    metric->set_id(3);
+    metric->set_what(StringToId("SCREEN_EVENT"));
+    metric->set_bucket(ONE_MINUTE);
+    metric->set_condition(StringToId("SOME_CONDITION"));
+
+    AtomMatcher* eventMatcher = config.add_atom_matcher();
+    eventMatcher->set_id(StringToId("SCREEN_EVENT"));
+
+    SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
+    simpleAtomMatcher->set_atom_id(2);
+
+    return config;
+}
+
+StatsdConfig buildDimensionMetricsWithMultiTags() {
+    StatsdConfig config;
+    config.set_id(12345);
+
+    AtomMatcher* eventMatcher = config.add_atom_matcher();
+    eventMatcher->set_id(StringToId("BATTERY_VERY_LOW"));
+    SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
+    simpleAtomMatcher->set_atom_id(2);
+
+    eventMatcher = config.add_atom_matcher();
+    eventMatcher->set_id(StringToId("BATTERY_VERY_VERY_LOW"));
+    simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
+    simpleAtomMatcher->set_atom_id(3);
+
+    eventMatcher = config.add_atom_matcher();
+    eventMatcher->set_id(StringToId("BATTERY_LOW"));
+
+    AtomMatcher_Combination* combination = eventMatcher->mutable_combination();
+    combination->set_operation(LogicalOperation::OR);
+    combination->add_matcher(StringToId("BATTERY_VERY_LOW"));
+    combination->add_matcher(StringToId("BATTERY_VERY_VERY_LOW"));
+
+    // Count process state changes, slice by uid, while SCREEN_IS_OFF
+    CountMetric* metric = config.add_count_metric();
+    metric->set_id(3);
+    metric->set_what(StringToId("BATTERY_LOW"));
+    metric->set_bucket(ONE_MINUTE);
+    // This case is interesting. We want to dimension across two atoms.
+    metric->mutable_dimensions_in_what()->add_child()->set_field(1);
+
+    auto alert = config.add_alert();
+    alert->set_id(kAlertId);
+    alert->set_metric_id(3);
+    alert->set_num_buckets(10);
+    alert->set_refractory_period_secs(100);
+    alert->set_trigger_if_sum_gt(100);
+    return config;
+}
+
+StatsdConfig buildCirclePredicates() {
+    StatsdConfig config;
+    config.set_id(12345);
+
+    AtomMatcher* eventMatcher = config.add_atom_matcher();
+    eventMatcher->set_id(StringToId("SCREEN_IS_ON"));
+
+    SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
+    simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/);
+    simpleAtomMatcher->add_field_value_matcher()->set_field(
+            1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
+    simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int(
+            2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/);
+
+    eventMatcher = config.add_atom_matcher();
+    eventMatcher->set_id(StringToId("SCREEN_IS_OFF"));
+
+    simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
+    simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/);
+    simpleAtomMatcher->add_field_value_matcher()->set_field(
+            1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
+    simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int(
+            1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_OFF*/);
+
+    auto condition = config.add_predicate();
+    condition->set_id(StringToId("SCREEN_IS_ON"));
+    SimplePredicate* simplePredicate = condition->mutable_simple_predicate();
+    simplePredicate->set_start(StringToId("SCREEN_IS_ON"));
+    simplePredicate->set_stop(StringToId("SCREEN_IS_OFF"));
+
+    condition = config.add_predicate();
+    condition->set_id(StringToId("SCREEN_IS_EITHER_ON_OFF"));
+
+    Predicate_Combination* combination = condition->mutable_combination();
+    combination->set_operation(LogicalOperation::OR);
+    combination->add_predicate(StringToId("SCREEN_IS_ON"));
+    combination->add_predicate(StringToId("SCREEN_IS_EITHER_ON_OFF"));
+
+    return config;
+}
+
+StatsdConfig buildConfigWithDifferentPredicates() {
+    StatsdConfig config;
+    config.set_id(12345);
+
+    auto pulledAtomMatcher =
+            CreateSimpleAtomMatcher("SUBSYSTEM_SLEEP", util::SUBSYSTEM_SLEEP_STATE);
+    *config.add_atom_matcher() = pulledAtomMatcher;
+    auto screenOnAtomMatcher = CreateScreenTurnedOnAtomMatcher();
+    *config.add_atom_matcher() = screenOnAtomMatcher;
+    auto screenOffAtomMatcher = CreateScreenTurnedOffAtomMatcher();
+    *config.add_atom_matcher() = screenOffAtomMatcher;
+    auto batteryNoneAtomMatcher = CreateBatteryStateNoneMatcher();
+    *config.add_atom_matcher() = batteryNoneAtomMatcher;
+    auto batteryUsbAtomMatcher = CreateBatteryStateUsbMatcher();
+    *config.add_atom_matcher() = batteryUsbAtomMatcher;
+
+    // Simple condition with InitialValue set to default (unknown).
+    auto screenOnUnknownPredicate = CreateScreenIsOnPredicate();
+    *config.add_predicate() = screenOnUnknownPredicate;
+
+    // Simple condition with InitialValue set to false.
+    auto screenOnFalsePredicate = config.add_predicate();
+    screenOnFalsePredicate->set_id(StringToId("ScreenIsOnInitialFalse"));
+    SimplePredicate* simpleScreenOnFalsePredicate =
+            screenOnFalsePredicate->mutable_simple_predicate();
+    simpleScreenOnFalsePredicate->set_start(screenOnAtomMatcher.id());
+    simpleScreenOnFalsePredicate->set_stop(screenOffAtomMatcher.id());
+    simpleScreenOnFalsePredicate->set_initial_value(SimplePredicate_InitialValue_FALSE);
+
+    // Simple condition with InitialValue set to false.
+    auto onBatteryFalsePredicate = config.add_predicate();
+    onBatteryFalsePredicate->set_id(StringToId("OnBatteryInitialFalse"));
+    SimplePredicate* simpleOnBatteryFalsePredicate =
+            onBatteryFalsePredicate->mutable_simple_predicate();
+    simpleOnBatteryFalsePredicate->set_start(batteryNoneAtomMatcher.id());
+    simpleOnBatteryFalsePredicate->set_stop(batteryUsbAtomMatcher.id());
+    simpleOnBatteryFalsePredicate->set_initial_value(SimplePredicate_InitialValue_FALSE);
+
+    // Combination condition with both simple condition InitialValues set to false.
+    auto screenOnFalseOnBatteryFalsePredicate = config.add_predicate();
+    screenOnFalseOnBatteryFalsePredicate->set_id(StringToId("ScreenOnFalseOnBatteryFalse"));
+    screenOnFalseOnBatteryFalsePredicate->mutable_combination()->set_operation(
+            LogicalOperation::AND);
+    addPredicateToPredicateCombination(*screenOnFalsePredicate,
+                                       screenOnFalseOnBatteryFalsePredicate);
+    addPredicateToPredicateCombination(*onBatteryFalsePredicate,
+                                       screenOnFalseOnBatteryFalsePredicate);
+
+    // Combination condition with one simple condition InitialValue set to unknown and one set to
+    // false.
+    auto screenOnUnknownOnBatteryFalsePredicate = config.add_predicate();
+    screenOnUnknownOnBatteryFalsePredicate->set_id(StringToId("ScreenOnUnknowneOnBatteryFalse"));
+    screenOnUnknownOnBatteryFalsePredicate->mutable_combination()->set_operation(
+            LogicalOperation::AND);
+    addPredicateToPredicateCombination(screenOnUnknownPredicate,
+                                       screenOnUnknownOnBatteryFalsePredicate);
+    addPredicateToPredicateCombination(*onBatteryFalsePredicate,
+                                       screenOnUnknownOnBatteryFalsePredicate);
+
+    // Simple condition metric with initial value false.
+    ValueMetric* metric1 = config.add_value_metric();
+    metric1->set_id(StringToId("ValueSubsystemSleepWhileScreenOnInitialFalse"));
+    metric1->set_what(pulledAtomMatcher.id());
+    *metric1->mutable_value_field() =
+            CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */});
+    metric1->set_bucket(FIVE_MINUTES);
+    metric1->set_condition(screenOnFalsePredicate->id());
+
+    // Simple condition metric with initial value unknown.
+    ValueMetric* metric2 = config.add_value_metric();
+    metric2->set_id(StringToId("ValueSubsystemSleepWhileScreenOnInitialUnknown"));
+    metric2->set_what(pulledAtomMatcher.id());
+    *metric2->mutable_value_field() =
+            CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */});
+    metric2->set_bucket(FIVE_MINUTES);
+    metric2->set_condition(screenOnUnknownPredicate.id());
+
+    // Combination condition metric with initial values false and false.
+    ValueMetric* metric3 = config.add_value_metric();
+    metric3->set_id(StringToId("ValueSubsystemSleepWhileScreenOnFalseDeviceUnpluggedFalse"));
+    metric3->set_what(pulledAtomMatcher.id());
+    *metric3->mutable_value_field() =
+            CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */});
+    metric3->set_bucket(FIVE_MINUTES);
+    metric3->set_condition(screenOnFalseOnBatteryFalsePredicate->id());
+
+    // Combination condition metric with initial values unknown and false.
+    ValueMetric* metric4 = config.add_value_metric();
+    metric4->set_id(StringToId("ValueSubsystemSleepWhileScreenOnUnknownDeviceUnpluggedFalse"));
+    metric4->set_what(pulledAtomMatcher.id());
+    *metric4->mutable_value_field() =
+            CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */});
+    metric4->set_bucket(FIVE_MINUTES);
+    metric4->set_condition(screenOnUnknownOnBatteryFalsePredicate->id());
+
+    return config;
+}
+}  // anonymous namespace
+
+TEST(MetricsManagerTest, TestInitialConditions) {
+    sp<UidMap> uidMap = new UidMap();
+    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    sp<AlarmMonitor> periodicAlarmMonitor;
+    StatsdConfig config = buildConfigWithDifferentPredicates();
+    set<int> allTagIds;
+    vector<sp<LogMatchingTracker>> allAtomMatchers;
+    unordered_map<int64_t, int> logTrackerMap;
+    vector<sp<ConditionTracker>> allConditionTrackers;
+    vector<sp<MetricProducer>> allMetricProducers;
+    std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
+    std::vector<sp<AlarmTracker>> allAlarmTrackers;
+    unordered_map<int, std::vector<int>> conditionToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToConditionMap;
+    unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
+    unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
+    unordered_map<int64_t, int> alertTrackerMap;
+    vector<int> metricsWithActivation;
+    std::set<int64_t> noReportMetricIds;
+
+    EXPECT_TRUE(initStatsdConfig(
+            kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
+            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchers, logTrackerMap,
+            allConditionTrackers, allMetricProducers, allAnomalyTrackers, allAlarmTrackers,
+            conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
+            activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, alertTrackerMap,
+            metricsWithActivation, noReportMetricIds));
+    ASSERT_EQ(4u, allMetricProducers.size());
+    ASSERT_EQ(5u, allConditionTrackers.size());
+
+    ConditionKey queryKey;
+    vector<ConditionState> conditionCache(5, ConditionState::kNotEvaluated);
+
+    allConditionTrackers[3]->isConditionMet(queryKey, allConditionTrackers, false, conditionCache);
+    allConditionTrackers[4]->isConditionMet(queryKey, allConditionTrackers, false, conditionCache);
+    EXPECT_EQ(ConditionState::kUnknown, conditionCache[0]);
+    EXPECT_EQ(ConditionState::kFalse, conditionCache[1]);
+    EXPECT_EQ(ConditionState::kFalse, conditionCache[2]);
+    EXPECT_EQ(ConditionState::kFalse, conditionCache[3]);
+    EXPECT_EQ(ConditionState::kUnknown, conditionCache[4]);
+
+    EXPECT_EQ(ConditionState::kFalse, allMetricProducers[0]->mCondition);
+    EXPECT_EQ(ConditionState::kUnknown, allMetricProducers[1]->mCondition);
+    EXPECT_EQ(ConditionState::kFalse, allMetricProducers[2]->mCondition);
+    EXPECT_EQ(ConditionState::kUnknown, allMetricProducers[3]->mCondition);
+}
+
+TEST(MetricsManagerTest, TestGoodConfig) {
+    sp<UidMap> uidMap = new UidMap();
+    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    sp<AlarmMonitor> periodicAlarmMonitor;
+    StatsdConfig config = buildGoodConfig();
+    set<int> allTagIds;
+    vector<sp<LogMatchingTracker>> allAtomMatchers;
+    unordered_map<int64_t, int> logTrackerMap;
+    vector<sp<ConditionTracker>> allConditionTrackers;
+    vector<sp<MetricProducer>> allMetricProducers;
+    std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
+    std::vector<sp<AlarmTracker>> allAlarmTrackers;
+    unordered_map<int, std::vector<int>> conditionToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToConditionMap;
+    unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
+    unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
+    unordered_map<int64_t, int> alertTrackerMap;
+    vector<int> metricsWithActivation;
+    std::set<int64_t> noReportMetricIds;
+
+    EXPECT_TRUE(initStatsdConfig(
+            kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
+            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchers, logTrackerMap,
+            allConditionTrackers, allMetricProducers, allAnomalyTrackers, allAlarmTrackers,
+            conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
+            activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, alertTrackerMap,
+            metricsWithActivation, noReportMetricIds));
+    ASSERT_EQ(1u, allMetricProducers.size());
+    ASSERT_EQ(1u, allAnomalyTrackers.size());
+    ASSERT_EQ(1u, noReportMetricIds.size());
+    ASSERT_EQ(1u, alertTrackerMap.size());
+    EXPECT_NE(alertTrackerMap.find(kAlertId), alertTrackerMap.end());
+    EXPECT_EQ(alertTrackerMap.find(kAlertId)->second, 0);
+}
+
+TEST(MetricsManagerTest, TestDimensionMetricsWithMultiTags) {
+    sp<UidMap> uidMap = new UidMap();
+    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    sp<AlarmMonitor> periodicAlarmMonitor;
+    StatsdConfig config = buildDimensionMetricsWithMultiTags();
+    set<int> allTagIds;
+    vector<sp<LogMatchingTracker>> allAtomMatchers;
+    unordered_map<int64_t, int> logTrackerMap;
+    vector<sp<ConditionTracker>> allConditionTrackers;
+    vector<sp<MetricProducer>> allMetricProducers;
+    std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
+    std::vector<sp<AlarmTracker>> allAlarmTrackers;
+    unordered_map<int, std::vector<int>> conditionToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToConditionMap;
+    unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
+    unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
+    unordered_map<int64_t, int> alertTrackerMap;
+    vector<int> metricsWithActivation;
+    std::set<int64_t> noReportMetricIds;
+
+    EXPECT_FALSE(initStatsdConfig(
+            kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
+            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchers, logTrackerMap,
+            allConditionTrackers, allMetricProducers, allAnomalyTrackers, allAlarmTrackers,
+            conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
+            activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, alertTrackerMap,
+            metricsWithActivation, noReportMetricIds));
+}
+
+TEST(MetricsManagerTest, TestCircleLogMatcherDependency) {
+    sp<UidMap> uidMap = new UidMap();
+    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    sp<AlarmMonitor> periodicAlarmMonitor;
+    StatsdConfig config = buildCircleMatchers();
+    set<int> allTagIds;
+    vector<sp<LogMatchingTracker>> allAtomMatchers;
+    unordered_map<int64_t, int> logTrackerMap;
+    vector<sp<ConditionTracker>> allConditionTrackers;
+    vector<sp<MetricProducer>> allMetricProducers;
+    std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
+    std::vector<sp<AlarmTracker>> allAlarmTrackers;
+    unordered_map<int, std::vector<int>> conditionToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToConditionMap;
+    unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
+    unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
+    unordered_map<int64_t, int> alertTrackerMap;
+    vector<int> metricsWithActivation;
+    std::set<int64_t> noReportMetricIds;
+
+    EXPECT_FALSE(initStatsdConfig(
+            kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
+            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchers, logTrackerMap,
+            allConditionTrackers, allMetricProducers, allAnomalyTrackers, allAlarmTrackers,
+            conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
+            activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, alertTrackerMap,
+            metricsWithActivation, noReportMetricIds));
+}
+
+TEST(MetricsManagerTest, TestMissingMatchers) {
+    sp<UidMap> uidMap = new UidMap();
+    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    sp<AlarmMonitor> periodicAlarmMonitor;
+    StatsdConfig config = buildMissingMatchers();
+    set<int> allTagIds;
+    vector<sp<LogMatchingTracker>> allAtomMatchers;
+    unordered_map<int64_t, int> logTrackerMap;
+    vector<sp<ConditionTracker>> allConditionTrackers;
+    vector<sp<MetricProducer>> allMetricProducers;
+    std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
+    std::vector<sp<AlarmTracker>> allAlarmTrackers;
+    unordered_map<int, std::vector<int>> conditionToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToConditionMap;
+    unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
+    unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
+    unordered_map<int64_t, int> alertTrackerMap;
+    vector<int> metricsWithActivation;
+    std::set<int64_t> noReportMetricIds;
+    EXPECT_FALSE(initStatsdConfig(
+            kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
+            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchers, logTrackerMap,
+            allConditionTrackers, allMetricProducers, allAnomalyTrackers, allAlarmTrackers,
+            conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
+            activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, alertTrackerMap,
+            metricsWithActivation, noReportMetricIds));
+}
+
+TEST(MetricsManagerTest, TestMissingPredicate) {
+    sp<UidMap> uidMap = new UidMap();
+    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    sp<AlarmMonitor> periodicAlarmMonitor;
+    StatsdConfig config = buildMissingPredicate();
+    set<int> allTagIds;
+    vector<sp<LogMatchingTracker>> allAtomMatchers;
+    unordered_map<int64_t, int> logTrackerMap;
+    vector<sp<ConditionTracker>> allConditionTrackers;
+    vector<sp<MetricProducer>> allMetricProducers;
+    std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
+    std::vector<sp<AlarmTracker>> allAlarmTrackers;
+    unordered_map<int, std::vector<int>> conditionToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToConditionMap;
+    unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
+    unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
+    unordered_map<int64_t, int> alertTrackerMap;
+    vector<int> metricsWithActivation;
+    std::set<int64_t> noReportMetricIds;
+    EXPECT_FALSE(initStatsdConfig(
+            kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
+            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchers, logTrackerMap,
+            allConditionTrackers, allMetricProducers, allAnomalyTrackers, allAlarmTrackers,
+            conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
+            activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, alertTrackerMap,
+            metricsWithActivation, noReportMetricIds));
+}
+
+TEST(MetricsManagerTest, TestCirclePredicateDependency) {
+    sp<UidMap> uidMap = new UidMap();
+    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    sp<AlarmMonitor> periodicAlarmMonitor;
+    StatsdConfig config = buildCirclePredicates();
+    set<int> allTagIds;
+    vector<sp<LogMatchingTracker>> allAtomMatchers;
+    unordered_map<int64_t, int> logTrackerMap;
+    vector<sp<ConditionTracker>> allConditionTrackers;
+    vector<sp<MetricProducer>> allMetricProducers;
+    std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
+    std::vector<sp<AlarmTracker>> allAlarmTrackers;
+    unordered_map<int, std::vector<int>> conditionToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToConditionMap;
+    unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
+    unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
+    unordered_map<int64_t, int> alertTrackerMap;
+    vector<int> metricsWithActivation;
+    std::set<int64_t> noReportMetricIds;
+
+    EXPECT_FALSE(initStatsdConfig(
+            kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
+            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchers, logTrackerMap,
+            allConditionTrackers, allMetricProducers, allAnomalyTrackers, allAlarmTrackers,
+            conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
+            activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, alertTrackerMap,
+            metricsWithActivation, noReportMetricIds));
+}
+
+TEST(MetricsManagerTest, testAlertWithUnknownMetric) {
+    sp<UidMap> uidMap = new UidMap();
+    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    sp<AlarmMonitor> periodicAlarmMonitor;
+    StatsdConfig config = buildAlertWithUnknownMetric();
+    set<int> allTagIds;
+    vector<sp<LogMatchingTracker>> allAtomMatchers;
+    unordered_map<int64_t, int> logTrackerMap;
+    vector<sp<ConditionTracker>> allConditionTrackers;
+    vector<sp<MetricProducer>> allMetricProducers;
+    std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
+    std::vector<sp<AlarmTracker>> allAlarmTrackers;
+    unordered_map<int, std::vector<int>> conditionToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToConditionMap;
+    unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
+    unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
+    unordered_map<int64_t, int> alertTrackerMap;
+    vector<int> metricsWithActivation;
+    std::set<int64_t> noReportMetricIds;
+
+    EXPECT_FALSE(initStatsdConfig(
+            kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
+            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchers, logTrackerMap,
+            allConditionTrackers, allMetricProducers, allAnomalyTrackers, allAlarmTrackers,
+            conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
+            activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, alertTrackerMap,
+            metricsWithActivation, noReportMetricIds));
+}
+
+TEST(MetricsManagerTest, TestCreateLogTrackerInvalidMatcher) {
+    sp<UidMap> uidMap = new UidMap();
+    AtomMatcher matcher;
+    matcher.set_id(21);
+    EXPECT_EQ(createLogTracker(matcher, 0, uidMap), nullptr);
+}
+
+TEST(MetricsManagerTest, TestCreateLogTrackerSimple) {
+    int index = 1;
+    int64_t id = 123;
+    sp<UidMap> uidMap = new UidMap();
+    AtomMatcher matcher;
+    matcher.set_id(id);
+    SimpleAtomMatcher* simpleAtomMatcher = matcher.mutable_simple_atom_matcher();
+    simpleAtomMatcher->set_atom_id(util::SCREEN_STATE_CHANGED);
+    simpleAtomMatcher->add_field_value_matcher()->set_field(
+            1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
+    simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int(
+            android::view::DisplayStateEnum::DISPLAY_STATE_ON);
+
+    sp<LogMatchingTracker> tracker = createLogTracker(matcher, index, uidMap);
+    EXPECT_NE(tracker, nullptr);
+
+    EXPECT_TRUE(tracker->mInitialized);
+    EXPECT_EQ(tracker->getId(), id);
+    EXPECT_EQ(tracker->mIndex, index);
+    const set<int>& atomIds = tracker->getAtomIds();
+    ASSERT_EQ(atomIds.size(), 1);
+    EXPECT_EQ(atomIds.count(util::SCREEN_STATE_CHANGED), 1);
+}
+
+TEST(MetricsManagerTest, TestCreateLogTrackerCombination) {
+    int index = 1;
+    int64_t id = 123;
+    sp<UidMap> uidMap = new UidMap();
+    AtomMatcher matcher;
+    matcher.set_id(id);
+    AtomMatcher_Combination* combination = matcher.mutable_combination();
+    combination->set_operation(LogicalOperation::OR);
+    combination->add_matcher(123);
+    combination->add_matcher(223);
+
+    sp<LogMatchingTracker> tracker = createLogTracker(matcher, index, uidMap);
+    EXPECT_NE(tracker, nullptr);
+
+    // Combination matchers need to be initialized first.
+    EXPECT_FALSE(tracker->mInitialized);
+    EXPECT_EQ(tracker->getId(), id);
+    EXPECT_EQ(tracker->mIndex, index);
+    const set<int>& atomIds = tracker->getAtomIds();
+    ASSERT_EQ(atomIds.size(), 0);
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp
index cee8372..0be983f 100644
--- a/cmds/statsd/tests/statsd_test_util.cpp
+++ b/cmds/statsd/tests/statsd_test_util.cpp
@@ -15,6 +15,8 @@
 #include "statsd_test_util.h"
 
 #include <aidl/android/util/StatsEventParcel.h>
+
+#include "matchers/SimpleLogMatchingTracker.h"
 #include "stats_event.h"
 
 using aidl::android::util::StatsEventParcel;
@@ -996,6 +998,20 @@
     return static_cast<int64_t>(std::hash<std::string>()(str));
 }
 
+sp<EventMatcherWizard> createEventMatcherWizard(
+        int tagId, int matcherIndex, const vector<FieldValueMatcher>& fieldValueMatchers) {
+    sp<UidMap> uidMap = new UidMap();
+    SimpleAtomMatcher atomMatcher;
+    atomMatcher.set_atom_id(tagId);
+    for (const FieldValueMatcher& fvm : fieldValueMatchers) {
+        *atomMatcher.add_field_value_matcher() = fvm;
+    }
+    uint64_t matcherHash = 0x12345678;
+    int64_t matcherId = 678;
+    return new EventMatcherWizard({new SimpleLogMatchingTracker(matcherId, matcherIndex,
+                                                                matcherHash, atomMatcher, uidMap)});
+}
+
 void ValidateWakelockAttributionUidAndTagDimension(const DimensionsValue& value, const int atomId,
                                                    const int uid, const string& tag) {
     EXPECT_EQ(value.field(), atomId);
diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h
index 3dcf4ec..1220019 100644
--- a/cmds/statsd/tests/statsd_test_util.h
+++ b/cmds/statsd/tests/statsd_test_util.h
@@ -25,6 +25,7 @@
 #include "src/StatsLogProcessor.h"
 #include "src/hash.h"
 #include "src/logd/LogEvent.h"
+#include "src/matchers/EventMatcherWizard.h"
 #include "src/packages/UidMap.h"
 #include "src/stats_log_util.h"
 #include "stats_event.h"
@@ -335,6 +336,9 @@
 
 int64_t StringToId(const string& str);
 
+sp<EventMatcherWizard> createEventMatcherWizard(
+        int tagId, int matcherIndex, const std::vector<FieldValueMatcher>& fieldValueMatchers = {});
+
 void ValidateWakelockAttributionUidAndTagDimension(const DimensionsValue& value, const int atomId,
                                                    const int uid, const string& tag);
 void ValidateUidDimension(const DimensionsValue& value, int node_idx, int atomId, int uid);
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/Activity.java b/core/java/android/app/Activity.java
index 522b8cc..5c4951e 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -3827,6 +3827,12 @@
         } catch (RemoteException e) {
             finishAfterTransition();
         }
+
+        // Activity was launched when user tapped a link in the Autofill Save UI - Save UI must
+        // be restored now.
+        if (mIntent != null && mIntent.hasExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN)) {
+            restoreAutofillSaveUi();
+        }
     }
 
     /**
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 7cec717..267b818 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -424,9 +424,14 @@
         final String authority;
         final int userId;
 
+        @GuardedBy("mLock")
+        ContentProviderHolder mHolder; // Temp holder to be used between notifier and waiter
+        Object mLock; // The lock to be used to get notified when the provider is ready
+
         public ProviderKey(String authority, int userId) {
             this.authority = authority;
             this.userId = userId;
+            this.mLock = new Object();
         }
 
         @Override
@@ -440,7 +445,11 @@
 
         @Override
         public int hashCode() {
-            return ((authority != null) ? authority.hashCode() : 0) ^ userId;
+            return hashCode(authority, userId);
+        }
+
+        public static int hashCode(final String auth, final int userIdent) {
+            return ((auth != null) ? auth.hashCode() : 0) ^ userIdent;
         }
     }
 
@@ -461,9 +470,8 @@
     // Mitigation for b/74523247: Used to serialize calls to AM.getContentProvider().
     // Note we never removes items from this map but that's okay because there are only so many
     // users and so many authorities.
-    // TODO Remove it once we move CPR.wait() from AMS to the client side.
-    @GuardedBy("mGetProviderLocks")
-    final ArrayMap<ProviderKey, Object> mGetProviderLocks = new ArrayMap<>();
+    @GuardedBy("mGetProviderKeys")
+    final SparseArray<ProviderKey> mGetProviderKeys = new SparseArray<>();
 
     final ArrayMap<Activity, ArrayList<OnActivityPausedListener>> mOnPauseListeners
         = new ArrayMap<Activity, ArrayList<OnActivityPausedListener>>();
@@ -606,7 +614,7 @@
                     throw new IllegalStateException(
                             "Received config update for non-existing activity");
                 }
-                activity.mMainThread.handleActivityConfigurationChanged(token, overrideConfig,
+                activity.mMainThread.handleActivityConfigurationChanged(this, overrideConfig,
                         newDisplayId);
             };
         }
@@ -1756,6 +1764,16 @@
                     ActivityThread.this, activityToken, actionId, arguments,
                     cancellationSignal, resultCallback));
         }
+
+        @Override
+        public void notifyContentProviderPublishStatus(@NonNull ContentProviderHolder holder,
+                @NonNull String auth, int userId, boolean published) {
+            final ProviderKey key = getGetProviderKey(auth, userId);
+            synchronized (key.mLock) {
+                key.mHolder = holder;
+                key.mLock.notifyAll();
+            }
+        }
     }
 
     private @NonNull SafeCancellationTransport createSafeCancellationTransport(
@@ -3457,13 +3475,9 @@
     }
 
     @Override
-    public void handleStartActivity(IBinder token, PendingTransactionActions pendingActions) {
-        final ActivityClientRecord r = mActivities.get(token);
+    public void handleStartActivity(ActivityClientRecord r,
+            PendingTransactionActions pendingActions) {
         final Activity activity = r.activity;
-        if (r.activity == null) {
-            // TODO(lifecycler): What do we do in this case?
-            return;
-        }
         if (!r.stopped) {
             throw new IllegalStateException("Can't start activity that is not stopped.");
         }
@@ -3669,12 +3683,7 @@
     }
 
     @Override
-    public void handleNewIntent(IBinder token, List<ReferrerIntent> intents) {
-        final ActivityClientRecord r = mActivities.get(token);
-        if (r == null) {
-            return;
-        }
-
+    public void handleNewIntent(ActivityClientRecord r, List<ReferrerIntent> intents) {
         checkAndBlockForNetworkAccess();
         deliverNewIntents(r, intents);
     }
@@ -3863,13 +3872,7 @@
     }
 
     @Override
-    public void handlePictureInPictureRequested(IBinder token) {
-        final ActivityClientRecord r = mActivities.get(token);
-        if (r == null) {
-            Log.w(TAG, "Activity to request PIP to no longer exists");
-            return;
-        }
-
+    public void handlePictureInPictureRequested(ActivityClientRecord r) {
         final boolean receivedByApp = r.activity.onPictureInPictureRequested();
         if (!receivedByApp) {
             // Previous recommendation was for apps to enter picture-in-picture in
@@ -4399,22 +4402,21 @@
 
     /**
      * Resume the activity.
-     * @param token Target activity token.
+     * @param r Target activity record.
      * @param finalStateRequest Flag indicating if this is part of final state resolution for a
      *                          transaction.
      * @param reason Reason for performing the action.
      *
-     * @return The {@link ActivityClientRecord} that was resumed, {@code null} otherwise.
+     * @return {@code true} that was resumed, {@code false} otherwise.
      */
     @VisibleForTesting
-    public ActivityClientRecord performResumeActivity(IBinder token, boolean finalStateRequest,
+    public boolean performResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
             String reason) {
-        final ActivityClientRecord r = mActivities.get(token);
         if (localLOGV) {
             Slog.v(TAG, "Performing resume of " + r + " finished=" + r.activity.mFinished);
         }
-        if (r == null || r.activity.mFinished) {
-            return null;
+        if (r.activity.mFinished) {
+            return false;
         }
         if (r.getLifecycleState() == ON_RESUME) {
             if (!finalStateRequest) {
@@ -4428,7 +4430,7 @@
                 // handle two resume requests for the final state. For cases other than this
                 // one, we don't expect it to happen.
             }
-            return null;
+            return false;
         }
         if (finalStateRequest) {
             r.hideForNow = false;
@@ -4459,7 +4461,7 @@
                         + r.intent.getComponent().toShortString() + ": " + e.toString(), e);
             }
         }
-        return r;
+        return true;
     }
 
     static final void cleanUpPendingRemoveWindows(ActivityClientRecord r, boolean force) {
@@ -4480,20 +4482,19 @@
     }
 
     @Override
-    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
-            String reason) {
+    public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
+            boolean isForward, String reason) {
         // If we are getting ready to gc after going to the background, well
         // we are back active so skip it.
         unscheduleGcIdler();
         mSomeActivitiesChanged = true;
 
         // TODO Push resumeArgs into the activity for consideration
-        final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
-        if (r == null) {
-            // We didn't actually resume the activity, so skipping any follow-up actions.
+        // skip below steps for double-resume and r.mFinish = true case.
+        if (!performResumeActivity(r, finalStateRequest, reason)) {
             return;
         }
-        if (mActivitiesToBeDestroyed.containsKey(token)) {
+        if (mActivitiesToBeDestroyed.containsKey(r.token)) {
             // Although the activity is resumed, it is going to be destroyed. So the following
             // UI operations are unnecessary and also prevents exception because its token may
             // be gone that window manager cannot recognize it. All necessary cleanup actions
@@ -4611,13 +4612,8 @@
 
 
     @Override
-    public void handleTopResumedActivityChanged(IBinder token, boolean onTop, String reason) {
-        ActivityClientRecord r = mActivities.get(token);
-        if (r == null || r.activity == null) {
-            Slog.w(TAG, "Not found target activity to report position change for token: " + token);
-            return;
-        }
-
+    public void handleTopResumedActivityChanged(ActivityClientRecord r, boolean onTop,
+            String reason) {
         if (DEBUG_ORDER) {
             Slog.d(TAG, "Received position change to top: " + onTop + " for activity: " + r);
         }
@@ -4650,23 +4646,20 @@
     }
 
     @Override
-    public void handlePauseActivity(IBinder token, boolean finished, boolean userLeaving,
+    public void handlePauseActivity(ActivityClientRecord r, boolean finished, boolean userLeaving,
             int configChanges, PendingTransactionActions pendingActions, String reason) {
-        ActivityClientRecord r = mActivities.get(token);
-        if (r != null) {
-            if (userLeaving) {
-                performUserLeavingActivity(r);
-            }
-
-            r.activity.mConfigChangeFlags |= configChanges;
-            performPauseActivity(r, finished, reason, pendingActions);
-
-            // Make sure any pending writes are now committed.
-            if (r.isPreHoneycomb()) {
-                QueuedWork.waitToFinish();
-            }
-            mSomeActivitiesChanged = true;
+        if (userLeaving) {
+            performUserLeavingActivity(r);
         }
+
+        r.activity.mConfigChangeFlags |= configChanges;
+        performPauseActivity(r, finished, reason, pendingActions);
+
+        // Make sure any pending writes are now committed.
+        if (r.isPreHoneycomb()) {
+            QueuedWork.waitToFinish();
+        }
+        mSomeActivitiesChanged = true;
     }
 
     final void performUserLeavingActivity(ActivityClientRecord r) {
@@ -4763,8 +4756,11 @@
         r.setState(ON_PAUSE);
     }
 
+    // TODO(b/127877792): Make LocalActivityManager call performStopActivityInner. We cannot do this
+    // since it's a high usage hidden API.
     /** Called from {@link LocalActivityManager}. */
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 127877792,
+            publicAlternatives = "{@code N/A}")
     final void performStopActivity(IBinder token, boolean saveState, String reason) {
         ActivityClientRecord r = mActivities.get(token);
         performStopActivityInner(r, null /* stopInfo */, saveState, false /* finalStateRequest */,
@@ -4805,39 +4801,37 @@
     private void performStopActivityInner(ActivityClientRecord r, StopInfo info,
             boolean saveState, boolean finalStateRequest, String reason) {
         if (localLOGV) Slog.v(TAG, "Performing stop of " + r);
-        if (r != null) {
-            if (r.stopped) {
-                if (r.activity.mFinished) {
-                    // If we are finishing, we won't call onResume() in certain
-                    // cases.  So here we likewise don't want to call onStop()
-                    // if the activity isn't resumed.
-                    return;
-                }
-                if (!finalStateRequest) {
-                    final RuntimeException e = new RuntimeException(
-                            "Performing stop of activity that is already stopped: "
-                                    + r.intent.getComponent().toShortString());
-                    Slog.e(TAG, e.getMessage(), e);
-                    Slog.e(TAG, r.getStateString());
-                }
+        if (r.stopped) {
+            if (r.activity.mFinished) {
+                // If we are finishing, we won't call onResume() in certain
+                // cases.  So here we likewise don't want to call onStop()
+                // if the activity isn't resumed.
+                return;
             }
+            if (!finalStateRequest) {
+                final RuntimeException e = new RuntimeException(
+                        "Performing stop of activity that is already stopped: "
+                                + r.intent.getComponent().toShortString());
+                Slog.e(TAG, e.getMessage(), e);
+                Slog.e(TAG, r.getStateString());
+            }
+        }
 
-            // One must first be paused before stopped...
-            performPauseActivityIfNeeded(r, reason);
+        // One must first be paused before stopped...
+        performPauseActivityIfNeeded(r, reason);
 
-            if (info != null) {
-                try {
-                    // First create a thumbnail for the activity...
-                    // For now, don't create the thumbnail here; we are
-                    // doing that by doing a screen snapshot.
-                    info.setDescription(r.activity.onCreateDescription());
-                } catch (Exception e) {
-                    if (!mInstrumentation.onException(r.activity, e)) {
-                        throw new RuntimeException(
-                                "Unable to save state of activity "
-                                + r.intent.getComponent().toShortString()
-                                + ": " + e.toString(), e);
-                    }
+        if (info != null) {
+            try {
+                // First create a thumbnail for the activity...
+                // For now, don't create the thumbnail here; we are
+                // doing that by doing a screen snapshot.
+                info.setDescription(r.activity.onCreateDescription());
+            } catch (Exception e) {
+                if (!mInstrumentation.onException(r.activity, e)) {
+                    throw new RuntimeException(
+                            "Unable to save state of activity "
+                            + r.intent.getComponent().toShortString()
+                            + ": " + e.toString(), e);
                 }
             }
 
@@ -4909,9 +4903,8 @@
     }
 
     @Override
-    public void handleStopActivity(IBinder token, int configChanges,
+    public void handleStopActivity(ActivityClientRecord r, int configChanges,
             PendingTransactionActions pendingActions, boolean finalStateRequest, String reason) {
-        final ActivityClientRecord r = mActivities.get(token);
         r.activity.mConfigChangeFlags |= configChanges;
 
         final StopInfo stopInfo = new StopInfo();
@@ -4947,8 +4940,7 @@
     }
 
     @Override
-    public void performRestartActivity(IBinder token, boolean start) {
-        ActivityClientRecord r = mActivities.get(token);
+    public void performRestartActivity(ActivityClientRecord r, boolean start) {
         if (r.stopped) {
             r.activity.performRestart(start, "performRestartActivity");
             if (start) {
@@ -5035,107 +5027,101 @@
     }
 
     @Override
-    public void handleSendResult(IBinder token, List<ResultInfo> results, String reason) {
-        ActivityClientRecord r = mActivities.get(token);
+    public void handleSendResult(ActivityClientRecord r, List<ResultInfo> results, String reason) {
         if (DEBUG_RESULTS) Slog.v(TAG, "Handling send result to " + r);
-        if (r != null) {
-            final boolean resumed = !r.paused;
-            if (!r.activity.mFinished && r.activity.mDecor != null
-                    && r.hideForNow && resumed) {
-                // We had hidden the activity because it started another
-                // one...  we have gotten a result back and we are not
-                // paused, so make sure our window is visible.
-                updateVisibility(r, true);
-            }
-            if (resumed) {
-                try {
-                    // Now we are idle.
-                    r.activity.mCalled = false;
-                    mInstrumentation.callActivityOnPause(r.activity);
-                    if (!r.activity.mCalled) {
-                        throw new SuperNotCalledException(
-                            "Activity " + r.intent.getComponent().toShortString()
-                            + " did not call through to super.onPause()");
-                    }
-                } catch (SuperNotCalledException e) {
-                    throw e;
-                } catch (Exception e) {
-                    if (!mInstrumentation.onException(r.activity, e)) {
-                        throw new RuntimeException(
-                                "Unable to pause activity "
-                                + r.intent.getComponent().toShortString()
-                                + ": " + e.toString(), e);
-                    }
-                }
-            }
-            checkAndBlockForNetworkAccess();
-            deliverResults(r, results, reason);
-            if (resumed) {
-                r.activity.performResume(false, reason);
-            }
+        final boolean resumed = !r.paused;
+        if (!r.activity.mFinished && r.activity.mDecor != null
+                && r.hideForNow && resumed) {
+            // We had hidden the activity because it started another
+            // one...  we have gotten a result back and we are not
+            // paused, so make sure our window is visible.
+            updateVisibility(r, true);
         }
-    }
-
-    /** Core implementation of activity destroy call. */
-    ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
-            int configChanges, boolean getNonConfigInstance, String reason) {
-        ActivityClientRecord r = mActivities.get(token);
-        Class<? extends Activity> activityClass = null;
-        if (localLOGV) Slog.v(TAG, "Performing finish of " + r);
-        if (r != null) {
-            activityClass = r.activity.getClass();
-            r.activity.mConfigChangeFlags |= configChanges;
-            if (finishing) {
-                r.activity.mFinished = true;
-            }
-
-            performPauseActivityIfNeeded(r, "destroy");
-
-            if (!r.stopped) {
-                callActivityOnStop(r, false /* saveState */, "destroy");
-            }
-            if (getNonConfigInstance) {
-                try {
-                    r.lastNonConfigurationInstances
-                            = r.activity.retainNonConfigurationInstances();
-                } catch (Exception e) {
-                    if (!mInstrumentation.onException(r.activity, e)) {
-                        throw new RuntimeException(
-                                "Unable to retain activity "
-                                + r.intent.getComponent().toShortString()
-                                + ": " + e.toString(), e);
-                    }
-                }
-            }
+        if (resumed) {
             try {
+                // Now we are idle.
                 r.activity.mCalled = false;
-                mInstrumentation.callActivityOnDestroy(r.activity);
+                mInstrumentation.callActivityOnPause(r.activity);
                 if (!r.activity.mCalled) {
                     throw new SuperNotCalledException(
-                        "Activity " + safeToComponentShortString(r.intent) +
-                        " did not call through to super.onDestroy()");
-                }
-                if (r.window != null) {
-                    r.window.closeAllPanels();
+                        "Activity " + r.intent.getComponent().toShortString()
+                        + " did not call through to super.onPause()");
                 }
             } catch (SuperNotCalledException e) {
                 throw e;
             } catch (Exception e) {
                 if (!mInstrumentation.onException(r.activity, e)) {
                     throw new RuntimeException(
-                            "Unable to destroy activity " + safeToComponentShortString(r.intent)
+                            "Unable to pause activity "
+                            + r.intent.getComponent().toShortString()
+                            + ": " + e.toString(), e);
+                }
+            }
+        }
+        checkAndBlockForNetworkAccess();
+        deliverResults(r, results, reason);
+        if (resumed) {
+            r.activity.performResume(false, reason);
+        }
+    }
+
+    /** Core implementation of activity destroy call. */
+    ActivityClientRecord performDestroyActivity(ActivityClientRecord r, boolean finishing,
+            int configChanges, boolean getNonConfigInstance, String reason) {
+        Class<? extends Activity> activityClass = null;
+        if (localLOGV) Slog.v(TAG, "Performing finish of " + r);
+        activityClass = r.activity.getClass();
+        r.activity.mConfigChangeFlags |= configChanges;
+        if (finishing) {
+            r.activity.mFinished = true;
+        }
+
+        performPauseActivityIfNeeded(r, "destroy");
+
+        if (!r.stopped) {
+            callActivityOnStop(r, false /* saveState */, "destroy");
+        }
+        if (getNonConfigInstance) {
+            try {
+                r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances();
+            } catch (Exception e) {
+                if (!mInstrumentation.onException(r.activity, e)) {
+                    throw new RuntimeException(
+                            "Unable to retain activity "
+                            + r.intent.getComponent().toShortString()
                             + ": " + e.toString(), e);
                 }
             }
             r.setState(ON_DESTROY);
             mLastReportedWindowingMode.remove(r.activity.getActivityToken());
         }
+        try {
+            r.activity.mCalled = false;
+            mInstrumentation.callActivityOnDestroy(r.activity);
+            if (!r.activity.mCalled) {
+                throw new SuperNotCalledException(
+                    "Activity " + safeToComponentShortString(r.intent)
+                            + " did not call through to super.onDestroy()");
+            }
+            if (r.window != null) {
+                r.window.closeAllPanels();
+            }
+        } catch (SuperNotCalledException e) {
+            throw e;
+        } catch (Exception e) {
+            if (!mInstrumentation.onException(r.activity, e)) {
+                throw new RuntimeException(
+                        "Unable to destroy activity " + safeToComponentShortString(r.intent)
+                        + ": " + e.toString(), e);
+            }
+        }
+        r.setState(ON_DESTROY);
         schedulePurgeIdler();
         // updatePendingActivityConfiguration() reads from mActivities to update
         // ActivityClientRecord which runs in a different thread. Protect modifications to
         // mActivities to avoid race.
         synchronized (mResourcesManager) {
-            mActivities.remove(token);
+            mActivities.remove(r.token);
         }
         StrictMode.decrementExpectedActivityCount(activityClass);
         return r;
@@ -5152,70 +5138,67 @@
     }
 
     @Override
-    public void handleDestroyActivity(IBinder token, boolean finishing, int configChanges,
+    public void handleDestroyActivity(ActivityClientRecord r, boolean finishing, int configChanges,
             boolean getNonConfigInstance, String reason) {
-        ActivityClientRecord r = performDestroyActivity(token, finishing,
-                configChanges, getNonConfigInstance, reason);
-        if (r != null) {
-            cleanUpPendingRemoveWindows(r, finishing);
-            WindowManager wm = r.activity.getWindowManager();
-            View v = r.activity.mDecor;
-            if (v != null) {
-                if (r.activity.mVisibleFromServer) {
-                    mNumVisibleActivities--;
-                }
-                IBinder wtoken = v.getWindowToken();
-                if (r.activity.mWindowAdded) {
-                    if (r.mPreserveWindow) {
-                        // Hold off on removing this until the new activity's
-                        // window is being added.
-                        r.mPendingRemoveWindow = r.window;
-                        r.mPendingRemoveWindowManager = wm;
-                        // We can only keep the part of the view hierarchy that we control,
-                        // everything else must be removed, because it might not be able to
-                        // behave properly when activity is relaunching.
-                        r.window.clearContentView();
-                    } else {
-                        wm.removeViewImmediate(v);
-                    }
-                }
-                if (wtoken != null && r.mPendingRemoveWindow == null) {
-                    WindowManagerGlobal.getInstance().closeAll(wtoken,
-                            r.activity.getClass().getName(), "Activity");
-                } else if (r.mPendingRemoveWindow != null) {
-                    // We're preserving only one window, others should be closed so app views
-                    // will be detached before the final tear down. It should be done now because
-                    // some components (e.g. WebView) rely on detach callbacks to perform receiver
-                    // unregister and other cleanup.
-                    WindowManagerGlobal.getInstance().closeAllExceptView(token, v,
-                            r.activity.getClass().getName(), "Activity");
-                }
-                r.activity.mDecor = null;
+        r = performDestroyActivity(r, finishing, configChanges, getNonConfigInstance, reason);
+        cleanUpPendingRemoveWindows(r, finishing);
+        WindowManager wm = r.activity.getWindowManager();
+        View v = r.activity.mDecor;
+        if (v != null) {
+            if (r.activity.mVisibleFromServer) {
+                mNumVisibleActivities--;
             }
-            if (r.mPendingRemoveWindow == null) {
-                // If we are delaying the removal of the activity window, then
-                // we can't clean up all windows here.  Note that we can't do
-                // so later either, which means any windows that aren't closed
-                // by the app will leak.  Well we try to warning them a lot
-                // about leaking windows, because that is a bug, so if they are
-                // using this recreate facility then they get to live with leaks.
-                WindowManagerGlobal.getInstance().closeAll(token,
+            IBinder wtoken = v.getWindowToken();
+            if (r.activity.mWindowAdded) {
+                if (r.mPreserveWindow) {
+                    // Hold off on removing this until the new activity's
+                    // window is being added.
+                    r.mPendingRemoveWindow = r.window;
+                    r.mPendingRemoveWindowManager = wm;
+                    // We can only keep the part of the view hierarchy that we control,
+                    // everything else must be removed, because it might not be able to
+                    // behave properly when activity is relaunching.
+                    r.window.clearContentView();
+                } else {
+                    wm.removeViewImmediate(v);
+                }
+            }
+            if (wtoken != null && r.mPendingRemoveWindow == null) {
+                WindowManagerGlobal.getInstance().closeAll(wtoken,
+                        r.activity.getClass().getName(), "Activity");
+            } else if (r.mPendingRemoveWindow != null) {
+                // We're preserving only one window, others should be closed so app views
+                // will be detached before the final tear down. It should be done now because
+                // some components (e.g. WebView) rely on detach callbacks to perform receiver
+                // unregister and other cleanup.
+                WindowManagerGlobal.getInstance().closeAllExceptView(r.token, v,
                         r.activity.getClass().getName(), "Activity");
             }
+            r.activity.mDecor = null;
+        }
+        if (r.mPendingRemoveWindow == null) {
+            // If we are delaying the removal of the activity window, then
+            // we can't clean up all windows here.  Note that we can't do
+            // so later either, which means any windows that aren't closed
+            // by the app will leak.  Well we try to warning them a lot
+            // about leaking windows, because that is a bug, so if they are
+            // using this recreate facility then they get to live with leaks.
+            WindowManagerGlobal.getInstance().closeAll(r.token,
+                    r.activity.getClass().getName(), "Activity");
+        }
 
-            // Mocked out contexts won't be participating in the normal
-            // process lifecycle, but if we're running with a proper
-            // ApplicationContext we need to have it tear down things
-            // cleanly.
-            Context c = r.activity.getBaseContext();
-            if (c instanceof ContextImpl) {
-                ((ContextImpl) c).scheduleFinalCleanup(
-                        r.activity.getClass().getName(), "Activity");
-            }
+        // Mocked out contexts won't be participating in the normal
+        // process lifecycle, but if we're running with a proper
+        // ApplicationContext we need to have it tear down things
+        // cleanly.
+        Context c = r.activity.getBaseContext();
+        if (c instanceof ContextImpl) {
+            ((ContextImpl) c).scheduleFinalCleanup(
+                    r.activity.getClass().getName(), "Activity");
         }
         if (finishing) {
             try {
-                ActivityTaskManager.getService().activityDestroyed(token);
+                ActivityTaskManager.getService().activityDestroyed(r.token);
             } catch (RemoteException ex) {
                 throw ex.rethrowFromSystemServer();
             }
@@ -5438,7 +5421,7 @@
             callActivityOnStop(r, true /* saveState */, reason);
         }
 
-        handleDestroyActivity(r.token, false, configChanges, true, reason);
+        handleDestroyActivity(r, false, configChanges, true, reason);
 
         r.activity = null;
         r.window = null;
@@ -5466,12 +5449,10 @@
     }
 
     @Override
-    public void reportRelaunch(IBinder token, PendingTransactionActions pendingActions) {
+    public void reportRelaunch(ActivityClientRecord r, PendingTransactionActions pendingActions) {
         try {
-            ActivityTaskManager.getService().activityRelaunched(token);
-            final ActivityClientRecord r = mActivities.get(token);
-            if (pendingActions.shouldReportRelaunchToWindowManager() && r != null
-                    && r.window != null) {
+            ActivityTaskManager.getService().activityRelaunched(r.token);
+            if (pendingActions.shouldReportRelaunchToWindowManager() && r.window != null) {
                 r.window.reportActivityRelaunched();
             }
         } catch (RemoteException e) {
@@ -5630,13 +5611,7 @@
      */
     private Configuration performActivityConfigurationChanged(Activity activity,
             Configuration newConfig, Configuration amOverrideConfig, int displayId) {
-        if (activity == null) {
-            throw new IllegalArgumentException("No activity provided.");
-        }
         final IBinder activityToken = activity.getActivityToken();
-        if (activityToken == null) {
-            throw new IllegalArgumentException("Activity token not set. Is the activity attached?");
-        }
 
         final boolean movedToDifferentDisplay = isDifferentDisplay(activity, displayId);
         boolean shouldReportChange = false;
@@ -5936,20 +5911,8 @@
      * processing any configurations older than {@code overrideConfig}.
      */
     @Override
-    public void updatePendingActivityConfiguration(IBinder activityToken,
+    public void updatePendingActivityConfiguration(ActivityClientRecord r,
             Configuration overrideConfig) {
-        final ActivityClientRecord r;
-        synchronized (mResourcesManager) {
-            r = mActivities.get(activityToken);
-        }
-
-        if (r == null) {
-            if (DEBUG_CONFIGURATION) {
-                Slog.w(TAG, "Not found target activity to update its pending config.");
-            }
-            return;
-        }
-
         synchronized (r) {
             if (r.mPendingOverrideConfig != null
                     && !r.mPendingOverrideConfig.isOtherSeqNewer(overrideConfig)) {
@@ -5969,21 +5932,14 @@
      * if {@link #updatePendingActivityConfiguration(IBinder, Configuration)} has been called with
      * a newer config than {@code overrideConfig}.
      *
-     * @param activityToken Target activity token.
+     * @param r Target activity record.
      * @param overrideConfig Activity override config.
      * @param displayId Id of the display where activity was moved to, -1 if there was no move and
      *                  value didn't change.
      */
     @Override
-    public void handleActivityConfigurationChanged(IBinder activityToken,
+    public void handleActivityConfigurationChanged(ActivityClientRecord r,
             @NonNull Configuration overrideConfig, int displayId) {
-        ActivityClientRecord r = mActivities.get(activityToken);
-        // Check input params.
-        if (r == null || r.activity == null) {
-            if (DEBUG_CONFIGURATION) Slog.w(TAG, "Not found target activity to report to: " + r);
-            return;
-        }
-
         synchronized (r) {
             if (overrideConfig.isOtherSeqNewer(r.mPendingOverrideConfig)) {
                 if (DEBUG_CONFIGURATION) {
@@ -6807,13 +6763,33 @@
         // provider since it might take a long time to run and it could also potentially
         // be re-entrant in the case where the provider is in the same process.
         ContentProviderHolder holder = null;
+        final ProviderKey key = getGetProviderKey(auth, userId);
         try {
-            synchronized (getGetProviderLock(auth, userId)) {
+            synchronized (key) {
                 holder = ActivityManager.getService().getContentProvider(
                         getApplicationThread(), c.getOpPackageName(), auth, userId, stable);
+                // If the returned holder is non-null but its provider is null and it's not
+                // local, we'll need to wait for the publishing of the provider.
+                if (holder != null && holder.provider == null && !holder.mLocal) {
+                    synchronized (key.mLock) {
+                        key.mLock.wait(ContentResolver.CONTENT_PROVIDER_READY_TIMEOUT_MILLIS);
+                        holder = key.mHolder;
+                    }
+                    if (holder != null && holder.provider == null) {
+                        // probably timed out
+                        holder = null;
+                    }
+                }
             }
         } catch (RemoteException ex) {
             throw ex.rethrowFromSystemServer();
+        } catch (InterruptedException e) {
+            holder = null;
+        } finally {
+            // Clear the holder from the key since the key itself is never cleared.
+            synchronized (key.mLock) {
+                key.mHolder = null;
+            }
         }
         if (holder == null) {
             if (UserManager.get(c).isUserUnlocked(userId)) {
@@ -6831,13 +6807,13 @@
         return holder.provider;
     }
 
-    private Object getGetProviderLock(String auth, int userId) {
-        final ProviderKey key = new ProviderKey(auth, userId);
-        synchronized (mGetProviderLocks) {
-            Object lock = mGetProviderLocks.get(key);
+    private ProviderKey getGetProviderKey(String auth, int userId) {
+        final int key = ProviderKey.hashCode(auth, userId);
+        synchronized (mGetProviderKeys) {
+            ProviderKey lock = mGetProviderKeys.get(key);
             if (lock == null) {
-                lock = key;
-                mGetProviderLocks.put(key, lock);
+                lock = new ProviderKey(auth, userId);
+                mGetProviderKeys.put(key, lock);
             }
             return lock;
         }
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/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index 2df756e..ac50676 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -15,6 +15,8 @@
  */
 package android.app;
 
+import android.annotation.NonNull;
+import android.app.ActivityThread.ActivityClientRecord;
 import android.app.servertransaction.ClientTransaction;
 import android.app.servertransaction.ClientTransactionItem;
 import android.app.servertransaction.PendingTransactionActions;
@@ -89,37 +91,38 @@
     public abstract Map<IBinder, ClientTransactionItem> getActivitiesToBeDestroyed();
 
     /** Destroy the activity. */
-    public abstract void handleDestroyActivity(IBinder token, boolean finishing, int configChanges,
-            boolean getNonConfigInstance, String reason);
+    public abstract void handleDestroyActivity(@NonNull ActivityClientRecord r, boolean finishing,
+            int configChanges, boolean getNonConfigInstance, String reason);
 
     /** Pause the activity. */
-    public abstract void handlePauseActivity(IBinder token, boolean finished, boolean userLeaving,
-            int configChanges, PendingTransactionActions pendingActions, String reason);
+    public abstract void handlePauseActivity(@NonNull ActivityClientRecord r, boolean finished,
+            boolean userLeaving, int configChanges, PendingTransactionActions pendingActions,
+            String reason);
 
     /**
      * Resume the activity.
-     * @param token Target activity token.
+     * @param r Target activity record.
      * @param finalStateRequest Flag indicating if this call is handling final lifecycle state
      *                          request for a transaction.
      * @param isForward Flag indicating if next transition is forward.
      * @param reason Reason for performing this operation.
      */
-    public abstract void handleResumeActivity(IBinder token, boolean finalStateRequest,
-            boolean isForward, String reason);
+    public abstract void handleResumeActivity(@NonNull ActivityClientRecord r,
+            boolean finalStateRequest, boolean isForward, String reason);
 
     /**
      * Notify the activity about top resumed state change.
-     * @param token Target activity token.
+     * @param r Target activity record.
      * @param isTopResumedActivity Current state of the activity, {@code true} if it's the
      *                             topmost resumed activity in the system, {@code false} otherwise.
      * @param reason Reason for performing this operation.
      */
-    public abstract void handleTopResumedActivityChanged(IBinder token,
+    public abstract void handleTopResumedActivityChanged(@NonNull ActivityClientRecord r,
             boolean isTopResumedActivity, String reason);
 
     /**
      * Stop the activity.
-     * @param token Target activity token.
+     * @param r Target activity record.
      * @param configChanges Activity configuration changes.
      * @param pendingActions Pending actions to be used on this or later stages of activity
      *                       transaction.
@@ -127,38 +130,40 @@
      *                          request for a transaction.
      * @param reason Reason for performing this operation.
      */
-    public abstract void handleStopActivity(IBinder token, int configChanges,
+    public abstract void handleStopActivity(@NonNull ActivityClientRecord r, int configChanges,
             PendingTransactionActions pendingActions, boolean finalStateRequest, String reason);
 
     /** Report that activity was stopped to server. */
     public abstract void reportStop(PendingTransactionActions pendingActions);
 
     /** Restart the activity after it was stopped. */
-    public abstract void performRestartActivity(IBinder token, boolean start);
+    public abstract void performRestartActivity(@NonNull ActivityClientRecord r, boolean start);
 
     /** Set pending activity configuration in case it will be updated by other transaction item. */
-    public abstract void updatePendingActivityConfiguration(IBinder activityToken,
+    public abstract void updatePendingActivityConfiguration(@NonNull ActivityClientRecord r,
             Configuration overrideConfig);
 
     /** Deliver activity (override) configuration change. */
-    public abstract void handleActivityConfigurationChanged(IBinder activityToken,
+    public abstract void handleActivityConfigurationChanged(@NonNull ActivityClientRecord r,
             Configuration overrideConfig, int displayId);
 
     /** Deliver result from another activity. */
-    public abstract void handleSendResult(IBinder token, List<ResultInfo> results, String reason);
+    public abstract void handleSendResult(
+            @NonNull ActivityClientRecord r, List<ResultInfo> results, String reason);
 
     /** Deliver new intent. */
-    public abstract void handleNewIntent(IBinder token, List<ReferrerIntent> intents);
+    public abstract void handleNewIntent(
+            @NonNull ActivityClientRecord r, List<ReferrerIntent> intents);
 
     /** Request that an activity enter picture-in-picture. */
-    public abstract void handlePictureInPictureRequested(IBinder token);
+    public abstract void handlePictureInPictureRequested(@NonNull ActivityClientRecord r);
 
     /** Perform activity launch. */
-    public abstract Activity handleLaunchActivity(ActivityThread.ActivityClientRecord r,
+    public abstract Activity handleLaunchActivity(@NonNull ActivityClientRecord r,
             PendingTransactionActions pendingActions, Intent customIntent);
 
     /** Perform activity start. */
-    public abstract void handleStartActivity(IBinder token,
+    public abstract void handleStartActivity(@NonNull ActivityClientRecord r,
             PendingTransactionActions pendingActions);
 
     /** Get package info. */
@@ -176,7 +181,7 @@
      * Get {@link android.app.ActivityThread.ActivityClientRecord} instance that corresponds to the
      * provided token.
      */
-    public abstract ActivityThread.ActivityClientRecord getActivityClient(IBinder token);
+    public abstract ActivityClientRecord getActivityClient(IBinder token);
 
     /**
      * Prepare activity relaunch to update internal bookkeeping. This is used to track multiple
@@ -191,7 +196,7 @@
      * @return An initialized instance of {@link ActivityThread.ActivityClientRecord} to use during
      *         relaunch, or {@code null} if relaunch cancelled.
      */
-    public abstract ActivityThread.ActivityClientRecord prepareRelaunchActivity(IBinder token,
+    public abstract ActivityClientRecord prepareRelaunchActivity(IBinder token,
             List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
             int configChanges, MergedConfiguration config, boolean preserveWindow);
 
@@ -200,14 +205,15 @@
      * @param r Activity client record prepared for relaunch.
      * @param pendingActions Pending actions to be used on later stages of activity transaction.
      * */
-    public abstract void handleRelaunchActivity(ActivityThread.ActivityClientRecord r,
+    public abstract void handleRelaunchActivity(@NonNull ActivityClientRecord r,
             PendingTransactionActions pendingActions);
 
     /**
      * Report that relaunch request was handled.
-     * @param token Target activity token.
+     * @param r Target activity record.
      * @param pendingActions Pending actions initialized on earlier stages of activity transaction.
      *                       Used to check if we should report relaunch to WM.
      * */
-    public abstract void reportRelaunch(IBinder token, PendingTransactionActions pendingActions);
+    public abstract void reportRelaunch(@NonNull ActivityClientRecord r,
+            PendingTransactionActions pendingActions);
 }
diff --git a/core/java/android/app/ContentProviderHolder.java b/core/java/android/app/ContentProviderHolder.java
index 3d74583..e330a30 100644
--- a/core/java/android/app/ContentProviderHolder.java
+++ b/core/java/android/app/ContentProviderHolder.java
@@ -39,6 +39,11 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     public boolean noReleaseNeeded;
 
+    /**
+     * Whether the provider here is a local provider or not.
+     */
+    public boolean mLocal;
+
     @UnsupportedAppUsage
     public ContentProviderHolder(ProviderInfo _info) {
         info = _info;
@@ -59,6 +64,7 @@
         }
         dest.writeStrongBinder(connection);
         dest.writeInt(noReleaseNeeded ? 1 : 0);
+        dest.writeInt(mLocal ? 1 : 0);
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<ContentProviderHolder> CREATOR
@@ -81,5 +87,6 @@
                 source.readStrongBinder());
         connection = source.readStrongBinder();
         noReleaseNeeded = source.readInt() != 0;
+        mLocal = source.readInt() != 0;
     }
-}
\ No newline at end of file
+}
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/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 24da504..dc9918a 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -16,6 +16,7 @@
 
 package android.app;
 
+import android.app.ContentProviderHolder;
 import android.app.IInstrumentationWatcher;
 import android.app.IUiAutomationConnection;
 import android.app.ProfilerInfo;
@@ -147,4 +148,6 @@
     void performDirectAction(IBinder activityToken, String actionId,
             in Bundle arguments, in RemoteCallback cancellationCallback,
             in RemoteCallback resultCallback);
+    void notifyContentProviderPublishStatus(in ContentProviderHolder holder, String auth,
+            int userId, boolean published);
 }
diff --git a/core/java/android/app/IUiAutomationConnection.aidl b/core/java/android/app/IUiAutomationConnection.aidl
index 8c3180b..4c9e400 100644
--- a/core/java/android/app/IUiAutomationConnection.aidl
+++ b/core/java/android/app/IUiAutomationConnection.aidl
@@ -39,7 +39,7 @@
     boolean injectInputEvent(in InputEvent event, boolean sync);
     void syncInputTransactions();
     boolean setRotation(int rotation);
-    Bitmap takeScreenshot(in Rect crop, int rotation);
+    Bitmap takeScreenshot(in Rect crop);
     boolean clearWindowContentFrameStats(int windowId);
     WindowContentFrameStats getWindowContentFrameStats(int windowId);
     void clearWindowAnimationFrameStats();
diff --git a/core/java/android/app/LocalActivityManager.java b/core/java/android/app/LocalActivityManager.java
index 7cdf85e..d61c5d5 100644
--- a/core/java/android/app/LocalActivityManager.java
+++ b/core/java/android/app/LocalActivityManager.java
@@ -49,6 +49,7 @@
     private static final String TAG = "LocalActivityManager";
     private static final boolean localLOGV = false;
 
+    // TODO(b/127877792): try to remove this and use {@code ActivityClientRecord} instead.
     // Internal token for an Activity being managed by LocalActivityManager.
     private static class LocalActivityRecord extends Binder {
         LocalActivityRecord(String _id, Intent _intent) {
@@ -136,7 +137,7 @@
             // startActivity() has not yet been called, so nothing to do.
             return;
         }
-        
+
         if (r.curState == INITIALIZING) {
             // Get the lastNonConfigurationInstance for the activity
             HashMap<String, Object> lastNonConfigurationInstances =
@@ -177,12 +178,13 @@
                 pendingActions = null;
             }
 
-            mActivityThread.handleStartActivity(r, pendingActions);
+            mActivityThread.handleStartActivity(clientRecord, pendingActions);
             r.curState = STARTED;
             
             if (desiredState == RESUMED) {
                 if (localLOGV) Log.v(TAG, r.id + ": resuming");
-                mActivityThread.performResumeActivity(r, true, "moveToState-INITIALIZING");
+                mActivityThread.performResumeActivity(clientRecord, true,
+                        "moveToState-INITIALIZING");
                 r.curState = RESUMED;
             }
             
@@ -194,18 +196,21 @@
             // group's state catches up.
             return;
         }
-        
+
+        final ActivityClientRecord clientRecord = mActivityThread.getActivityClient(r);
+
         switch (r.curState) {
             case CREATED:
                 if (desiredState == STARTED) {
                     if (localLOGV) Log.v(TAG, r.id + ": restarting");
-                    mActivityThread.performRestartActivity(r, true /* start */);
+                    mActivityThread.performRestartActivity(clientRecord, true /* start */);
                     r.curState = STARTED;
                 }
                 if (desiredState == RESUMED) {
                     if (localLOGV) Log.v(TAG, r.id + ": restarting and resuming");
-                    mActivityThread.performRestartActivity(r, true /* start */);
-                    mActivityThread.performResumeActivity(r, true, "moveToState-CREATED");
+                    mActivityThread.performRestartActivity(clientRecord, true /* start */);
+                    mActivityThread.performResumeActivity(clientRecord, true,
+                            "moveToState-CREATED");
                     r.curState = RESUMED;
                 }
                 return;
@@ -214,7 +219,8 @@
                 if (desiredState == RESUMED) {
                     // Need to resume it...
                     if (localLOGV) Log.v(TAG, r.id + ": resuming");
-                    mActivityThread.performResumeActivity(r, true, "moveToState-STARTED");
+                    mActivityThread.performResumeActivity(clientRecord, true,
+                            "moveToState-STARTED");
                     r.instanceState = null;
                     r.curState = RESUMED;
                 }
@@ -352,7 +358,8 @@
                     ArrayList<ReferrerIntent> intents = new ArrayList<>(1);
                     intents.add(new ReferrerIntent(intent, mParent.getPackageName()));
                     if (localLOGV) Log.v(TAG, r.id + ": new intent");
-                    mActivityThread.handleNewIntent(r, intents);
+                    final ActivityClientRecord clientRecord = mActivityThread.getActivityClient(r);
+                    mActivityThread.handleNewIntent(clientRecord, intents);
                     r.intent = intent;
                     moveToState(r, mCurState);
                     if (mSingleMode) {
@@ -399,7 +406,8 @@
             performPause(r, finish);
         }
         if (localLOGV) Log.v(TAG, r.id + ": destroying");
-        mActivityThread.performDestroyActivity(r, finish, 0 /* configChanges */,
+        final ActivityClientRecord clientRecord = mActivityThread.getActivityClient(r);
+        mActivityThread.performDestroyActivity(clientRecord, finish, 0 /* configChanges */,
                 false /* getNonConfigInstance */, "LocalActivityManager::performDestroy");
         r.activity = null;
         r.window = null;
@@ -664,7 +672,8 @@
         for (int i=0; i<N; i++) {
             LocalActivityRecord r = mActivityArray.get(i);
             if (localLOGV) Log.v(TAG, r.id + ": destroying");
-            mActivityThread.performDestroyActivity(r, finishing, 0 /* configChanges */,
+            final ActivityClientRecord clientRecord = mActivityThread.getActivityClient(r);
+            mActivityThread.performDestroyActivity(clientRecord, finishing, 0 /* configChanges */,
                     false /* getNonConfigInstance */, "LocalActivityManager::dispatchDestroy");
         }
         mActivities.clear();
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 68e6561..6737972 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1492,6 +1492,7 @@
         private boolean mAllowGeneratedReplies = true;
         private final @SemanticAction int mSemanticAction;
         private final boolean mIsContextual;
+        private boolean mAuthenticationRequired;
 
         /**
          * Small icon representing the action.
@@ -1528,6 +1529,7 @@
             mAllowGeneratedReplies = in.readInt() == 1;
             mSemanticAction = in.readInt();
             mIsContextual = in.readInt() == 1;
+            mAuthenticationRequired = in.readInt() == 1;
         }
 
         /**
@@ -1536,13 +1538,14 @@
         @Deprecated
         public Action(int icon, CharSequence title, PendingIntent intent) {
             this(Icon.createWithResource("", icon), title, intent, new Bundle(), null, true,
-                    SEMANTIC_ACTION_NONE, false /* isContextual */);
+                    SEMANTIC_ACTION_NONE, false /* isContextual */, false /* requireAuth */);
         }
 
         /** Keep in sync with {@link Notification.Action.Builder#Builder(Action)}! */
         private Action(Icon icon, CharSequence title, PendingIntent intent, Bundle extras,
                 RemoteInput[] remoteInputs, boolean allowGeneratedReplies,
-                       @SemanticAction int semanticAction, boolean isContextual) {
+                @SemanticAction int semanticAction, boolean isContextual,
+                boolean requireAuth) {
             this.mIcon = icon;
             if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
                 this.icon = icon.getResId();
@@ -1554,6 +1557,7 @@
             this.mAllowGeneratedReplies = allowGeneratedReplies;
             this.mSemanticAction = semanticAction;
             this.mIsContextual = isContextual;
+            this.mAuthenticationRequired = requireAuth;
         }
 
         /**
@@ -1624,6 +1628,17 @@
         }
 
         /**
+         * Returns whether the OS should only send this action's {@link PendingIntent} on an
+         * unlocked device.
+         *
+         * If the device is locked when the action is invoked, the OS should show the keyguard and
+         * require successful authentication before invoking the intent.
+         */
+        public boolean isAuthenticationRequired() {
+            return mAuthenticationRequired;
+        }
+
+        /**
          * Builder class for {@link Action} objects.
          */
         public static final class Builder {
@@ -1635,6 +1650,7 @@
             @Nullable private ArrayList<RemoteInput> mRemoteInputs;
             private @SemanticAction int mSemanticAction;
             private boolean mIsContextual;
+            private boolean mAuthenticationRequired;
 
             /**
              * Construct a new builder for {@link Action} object.
@@ -1654,7 +1670,7 @@
              * @param intent the {@link PendingIntent} to fire when users trigger this action
              */
             public Builder(Icon icon, CharSequence title, PendingIntent intent) {
-                this(icon, title, intent, new Bundle(), null, true, SEMANTIC_ACTION_NONE);
+                this(icon, title, intent, new Bundle(), null, true, SEMANTIC_ACTION_NONE, false);
             }
 
             /**
@@ -1665,23 +1681,25 @@
             public Builder(Action action) {
                 this(action.getIcon(), action.title, action.actionIntent,
                         new Bundle(action.mExtras), action.getRemoteInputs(),
-                        action.getAllowGeneratedReplies(), action.getSemanticAction());
+                        action.getAllowGeneratedReplies(), action.getSemanticAction(),
+                        action.isAuthenticationRequired());
             }
 
             private Builder(@Nullable Icon icon, @Nullable CharSequence title,
                     @Nullable PendingIntent intent, @NonNull Bundle extras,
                     @Nullable RemoteInput[] remoteInputs, boolean allowGeneratedReplies,
-                    @SemanticAction int semanticAction) {
+                    @SemanticAction int semanticAction, boolean authRequired) {
                 mIcon = icon;
                 mTitle = title;
                 mIntent = intent;
                 mExtras = extras;
                 if (remoteInputs != null) {
-                    mRemoteInputs = new ArrayList<RemoteInput>(remoteInputs.length);
+                    mRemoteInputs = new ArrayList<>(remoteInputs.length);
                     Collections.addAll(mRemoteInputs, remoteInputs);
                 }
                 mAllowGeneratedReplies = allowGeneratedReplies;
                 mSemanticAction = semanticAction;
+                mAuthenticationRequired = authRequired;
             }
 
             /**
@@ -1776,6 +1794,21 @@
             }
 
             /**
+             * Sets whether the OS should only send this action's {@link PendingIntent} on an
+             * unlocked device.
+             *
+             * If this is true and the device is locked when the action is invoked, the OS will
+             * show the keyguard and require successful authentication before invoking the intent.
+             * If this is false and the device is locked, the OS will decide whether authentication
+             * should be required.
+             */
+            @NonNull
+            public Builder setAuthenticationRequired(boolean authenticationRequired) {
+                mAuthenticationRequired = authenticationRequired;
+                return this;
+            }
+
+            /**
              * Throws an NPE if we are building a contextual action missing one of the fields
              * necessary to display the action.
              */
@@ -1827,7 +1860,8 @@
                 RemoteInput[] textInputsArr = textInputs.isEmpty()
                         ? null : textInputs.toArray(new RemoteInput[textInputs.size()]);
                 return new Action(mIcon, mTitle, mIntent, mExtras, textInputsArr,
-                        mAllowGeneratedReplies, mSemanticAction, mIsContextual);
+                        mAllowGeneratedReplies, mSemanticAction, mIsContextual,
+                        mAuthenticationRequired);
             }
         }
 
@@ -1841,7 +1875,8 @@
                     getRemoteInputs(),
                     getAllowGeneratedReplies(),
                     getSemanticAction(),
-                    isContextual());
+                    isContextual(),
+                    isAuthenticationRequired());
         }
 
         @Override
@@ -1870,6 +1905,7 @@
             out.writeInt(mAllowGeneratedReplies ? 1 : 0);
             out.writeInt(mSemanticAction);
             out.writeInt(mIsContextual ? 1 : 0);
+            out.writeInt(mAuthenticationRequired ? 1 : 0);
         }
 
         public static final @android.annotation.NonNull Parcelable.Creator<Action> CREATOR =
@@ -5151,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/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/UiAutomation.java b/core/java/android/app/UiAutomation.java
index e0951bf..109205f 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -903,7 +903,7 @@
         try {
             // Calling out without a lock held.
             screenShot = mUiAutomationConnection.takeScreenshot(
-                    new Rect(0, 0, displaySize.x, displaySize.y), rotation);
+                    new Rect(0, 0, displaySize.x, displaySize.y));
             if (screenShot == null) {
                 return null;
             }
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index ce51dba..70d5201 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -180,7 +180,7 @@
     }
 
     @Override
-    public Bitmap takeScreenshot(Rect crop, int rotation) {
+    public Bitmap takeScreenshot(Rect crop) {
         synchronized (mLock) {
             throwIfCalledByNotTrustedUidLocked();
             throwIfShutdownLocked();
@@ -190,7 +190,15 @@
         try {
             int width = crop.width();
             int height = crop.height();
-            return SurfaceControl.screenshot(crop, width, height, rotation);
+            final IBinder displayToken = SurfaceControl.getInternalDisplayToken();
+            final SurfaceControl.DisplayCaptureArgs captureArgs =
+                    new SurfaceControl.DisplayCaptureArgs.Builder(displayToken)
+                            .setSourceCrop(crop)
+                            .setSize(width, height)
+                            .build();
+            final SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =
+                    SurfaceControl.captureDisplay(captureArgs);
+            return screenshotBuffer == null ? null : screenshotBuffer.asBitmap();
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index 16ddcd1..056cfc7 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -402,7 +402,7 @@
      */
     public void onFullBackup(FullBackupDataOutput data) throws IOException {
         FullBackup.BackupScheme backupScheme = FullBackup.getBackupScheme(this);
-        if (!backupScheme.isFullBackupContentEnabled()) {
+        if (!isDeviceToDeviceMigration() && !backupScheme.isFullBackupContentEnabled()) {
             return;
         }
 
@@ -430,24 +430,18 @@
         final Context ceContext = createCredentialProtectedStorageContext();
         final String rootDir = ceContext.getDataDir().getCanonicalPath();
         final String filesDir = ceContext.getFilesDir().getCanonicalPath();
-        final String noBackupDir = ceContext.getNoBackupFilesDir().getCanonicalPath();
         final String databaseDir = ceContext.getDatabasePath("foo").getParentFile()
                 .getCanonicalPath();
         final String sharedPrefsDir = ceContext.getSharedPreferencesPath("foo").getParentFile()
                 .getCanonicalPath();
-        final String cacheDir = ceContext.getCacheDir().getCanonicalPath();
-        final String codeCacheDir = ceContext.getCodeCacheDir().getCanonicalPath();
 
         final Context deContext = createDeviceProtectedStorageContext();
         final String deviceRootDir = deContext.getDataDir().getCanonicalPath();
         final String deviceFilesDir = deContext.getFilesDir().getCanonicalPath();
-        final String deviceNoBackupDir = deContext.getNoBackupFilesDir().getCanonicalPath();
         final String deviceDatabaseDir = deContext.getDatabasePath("foo").getParentFile()
                 .getCanonicalPath();
         final String deviceSharedPrefsDir = deContext.getSharedPreferencesPath("foo")
                 .getParentFile().getCanonicalPath();
-        final String deviceCacheDir = deContext.getCacheDir().getCanonicalPath();
-        final String deviceCodeCacheDir = deContext.getCodeCacheDir().getCanonicalPath();
 
         final String libDir = (appInfo.nativeLibraryDir != null)
                 ? new File(appInfo.nativeLibraryDir).getCanonicalPath()
@@ -460,33 +454,36 @@
 
         // Add the directories we always exclude.
         traversalExcludeSet.add(filesDir);
-        traversalExcludeSet.add(noBackupDir);
         traversalExcludeSet.add(databaseDir);
         traversalExcludeSet.add(sharedPrefsDir);
-        traversalExcludeSet.add(cacheDir);
-        traversalExcludeSet.add(codeCacheDir);
 
         traversalExcludeSet.add(deviceFilesDir);
-        traversalExcludeSet.add(deviceNoBackupDir);
         traversalExcludeSet.add(deviceDatabaseDir);
         traversalExcludeSet.add(deviceSharedPrefsDir);
-        traversalExcludeSet.add(deviceCacheDir);
-        traversalExcludeSet.add(deviceCodeCacheDir);
 
         if (libDir != null) {
             traversalExcludeSet.add(libDir);
         }
 
+        Set<String> extraExcludedDirs = getExtraExcludeDirsIfAny(ceContext);
+        Set<String> extraExcludedDeviceDirs = getExtraExcludeDirsIfAny(deContext);
+        traversalExcludeSet.addAll(extraExcludedDirs);
+        traversalExcludeSet.addAll(extraExcludedDeviceDirs);
+
         // Root dir first.
         applyXmlFiltersAndDoFullBackupForDomain(
                 packageName, FullBackup.ROOT_TREE_TOKEN, manifestIncludeMap,
                 manifestExcludeSet, traversalExcludeSet, data);
         traversalExcludeSet.add(rootDir);
+        // Exclude the extra directories anyway, since we've already covered them if it was needed.
+        traversalExcludeSet.addAll(extraExcludedDirs);
 
         applyXmlFiltersAndDoFullBackupForDomain(
                 packageName, FullBackup.DEVICE_ROOT_TREE_TOKEN, manifestIncludeMap,
                 manifestExcludeSet, traversalExcludeSet, data);
         traversalExcludeSet.add(deviceRootDir);
+        // Exclude the extra directories anyway, since we've already covered them if it was needed.
+        traversalExcludeSet.addAll(extraExcludedDeviceDirs);
 
         // Data dir next.
         traversalExcludeSet.remove(filesDir);
@@ -545,11 +542,28 @@
         }
     }
 
+    private Set<String> getExtraExcludeDirsIfAny(Context context) throws IOException {
+        if (isDeviceToDeviceMigration()) {
+            return Collections.emptySet();
+        }
+
+        // If this is not a migration, also exclude no-backup and cache dirs.
+        Set<String> excludedDirs = new HashSet<>();
+        excludedDirs.add(context.getCacheDir().getCanonicalPath());
+        excludedDirs.add(context.getCodeCacheDir().getCanonicalPath());
+        excludedDirs.add(context.getNoBackupFilesDir().getCanonicalPath());
+        return Collections.unmodifiableSet(excludedDirs);
+    }
+
+    private boolean isDeviceToDeviceMigration() {
+        return mOperationType == OperationType.MIGRATION;
+    }
+
     /** @hide */
     @VisibleForTesting
     public IncludeExcludeRules getIncludeExcludeRules(FullBackup.BackupScheme backupScheme)
             throws IOException, XmlPullParserException {
-        if (mOperationType == OperationType.MIGRATION) {
+        if (isDeviceToDeviceMigration()) {
             return IncludeExcludeRules.emptyRules();
         }
 
@@ -892,6 +906,11 @@
     }
 
     private boolean isFileEligibleForRestore(File destination) throws IOException {
+        if (isDeviceToDeviceMigration()) {
+            // Everything is eligible for device-to-device migration.
+            return true;
+        }
+
         FullBackup.BackupScheme bs = FullBackup.getBackupScheme(this);
         if (!bs.isFullBackupContentEnabled()) {
             if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
diff --git a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
index 8b52242..8a4bee9 100644
--- a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
+++ b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
@@ -20,6 +20,7 @@
 import static android.view.Display.INVALID_DISPLAY;
 
 import android.annotation.NonNull;
+import android.app.ActivityThread.ActivityClientRecord;
 import android.app.ClientTransactionHandler;
 import android.content.res.Configuration;
 import android.os.IBinder;
@@ -32,23 +33,24 @@
  * Activity configuration changed callback.
  * @hide
  */
-public class ActivityConfigurationChangeItem extends ClientTransactionItem {
+public class ActivityConfigurationChangeItem extends ActivityTransactionItem {
 
     private Configuration mConfiguration;
 
     @Override
     public void preExecute(android.app.ClientTransactionHandler client, IBinder token) {
+        final ActivityClientRecord r = getActivityClientRecord(client, token);
         // Notify the client of an upcoming change in the token configuration. This ensures that
         // batches of config change items only process the newest configuration.
-        client.updatePendingActivityConfiguration(token, mConfiguration);
+        client.updatePendingActivityConfiguration(r, mConfiguration);
     }
 
     @Override
-    public void execute(ClientTransactionHandler client, IBinder token,
+    public void execute(ClientTransactionHandler client, ActivityClientRecord r,
             PendingTransactionActions pendingActions) {
         // TODO(lifecycler): detect if PIP or multi-window mode changed and report it here.
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityConfigChanged");
-        client.handleActivityConfigurationChanged(token, mConfiguration, INVALID_DISPLAY);
+        client.handleActivityConfigurationChanged(r, mConfiguration, INVALID_DISPLAY);
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
 
@@ -93,7 +95,7 @@
         mConfiguration = in.readTypedObject(Configuration.CREATOR);
     }
 
-    public static final @android.annotation.NonNull Creator<ActivityConfigurationChangeItem> CREATOR =
+    public static final @NonNull Creator<ActivityConfigurationChangeItem> CREATOR =
             new Creator<ActivityConfigurationChangeItem>() {
         public ActivityConfigurationChangeItem createFromParcel(Parcel in) {
             return new ActivityConfigurationChangeItem(in);
diff --git a/core/java/android/app/servertransaction/ActivityLifecycleItem.java b/core/java/android/app/servertransaction/ActivityLifecycleItem.java
index c9193a9..cadb660 100644
--- a/core/java/android/app/servertransaction/ActivityLifecycleItem.java
+++ b/core/java/android/app/servertransaction/ActivityLifecycleItem.java
@@ -25,7 +25,7 @@
  * Request for lifecycle state that an activity should reach.
  * @hide
  */
-public abstract class ActivityLifecycleItem extends ClientTransactionItem {
+public abstract class ActivityLifecycleItem extends ActivityTransactionItem {
 
     @IntDef(prefix = { "UNDEFINED", "PRE_", "ON_" }, value = {
             UNDEFINED,
diff --git a/core/java/android/app/servertransaction/ActivityRelaunchItem.java b/core/java/android/app/servertransaction/ActivityRelaunchItem.java
index 9844de7..87ea3f8 100644
--- a/core/java/android/app/servertransaction/ActivityRelaunchItem.java
+++ b/core/java/android/app/servertransaction/ActivityRelaunchItem.java
@@ -18,7 +18,8 @@
 
 import static android.app.ActivityThread.DEBUG_ORDER;
 
-import android.app.ActivityThread;
+import android.annotation.NonNull;
+import android.app.ActivityThread.ActivityClientRecord;
 import android.app.ClientTransactionHandler;
 import android.app.ResultInfo;
 import android.os.IBinder;
@@ -36,7 +37,7 @@
  * Activity relaunch callback.
  * @hide
  */
-public class ActivityRelaunchItem extends ClientTransactionItem {
+public class ActivityRelaunchItem extends ActivityTransactionItem {
 
     private static final String TAG = "ActivityRelaunchItem";
 
@@ -50,7 +51,7 @@
      * A record that was properly configured for relaunch. Execution will be cancelled if not
      * initialized after {@link #preExecute(ClientTransactionHandler, IBinder)}.
      */
-    private ActivityThread.ActivityClientRecord mActivityClientRecord;
+    private ActivityClientRecord mActivityClientRecord;
 
     @Override
     public void preExecute(ClientTransactionHandler client, IBinder token) {
@@ -59,7 +60,7 @@
     }
 
     @Override
-    public void execute(ClientTransactionHandler client, IBinder token,
+    public void execute(ClientTransactionHandler client, ActivityClientRecord r,
             PendingTransactionActions pendingActions) {
         if (mActivityClientRecord == null) {
             if (DEBUG_ORDER) Slog.d(TAG, "Activity relaunch cancelled");
@@ -73,7 +74,8 @@
     @Override
     public void postExecute(ClientTransactionHandler client, IBinder token,
             PendingTransactionActions pendingActions) {
-        client.reportRelaunch(token, pendingActions);
+        final ActivityClientRecord r = getActivityClientRecord(client, token);
+        client.reportRelaunch(r, pendingActions);
     }
 
     // ObjectPoolItem implementation
@@ -130,16 +132,16 @@
         mPreserveWindow = in.readBoolean();
     }
 
-    public static final @android.annotation.NonNull Creator<ActivityRelaunchItem> CREATOR =
+    public static final @NonNull Creator<ActivityRelaunchItem> CREATOR =
             new Creator<ActivityRelaunchItem>() {
-                public ActivityRelaunchItem createFromParcel(Parcel in) {
-                    return new ActivityRelaunchItem(in);
-                }
+        public ActivityRelaunchItem createFromParcel(Parcel in) {
+            return new ActivityRelaunchItem(in);
+        }
 
-                public ActivityRelaunchItem[] newArray(int size) {
-                    return new ActivityRelaunchItem[size];
-                }
-            };
+        public ActivityRelaunchItem[] newArray(int size) {
+            return new ActivityRelaunchItem[size];
+        }
+    };
 
     @Override
     public boolean equals(Object o) {
diff --git a/core/java/android/app/servertransaction/ActivityResultItem.java b/core/java/android/app/servertransaction/ActivityResultItem.java
index 4e743ca..47096a8 100644
--- a/core/java/android/app/servertransaction/ActivityResultItem.java
+++ b/core/java/android/app/servertransaction/ActivityResultItem.java
@@ -18,10 +18,11 @@
 
 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 
+import android.annotation.NonNull;
+import android.app.ActivityThread.ActivityClientRecord;
 import android.app.ClientTransactionHandler;
 import android.app.ResultInfo;
 import android.compat.annotation.UnsupportedAppUsage;
-import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.Trace;
@@ -33,7 +34,7 @@
  * Activity result delivery callback.
  * @hide
  */
-public class ActivityResultItem extends ClientTransactionItem {
+public class ActivityResultItem extends ActivityTransactionItem {
 
     @UnsupportedAppUsage
     private List<ResultInfo> mResultInfoList;
@@ -45,10 +46,10 @@
     }*/
 
     @Override
-    public void execute(ClientTransactionHandler client, IBinder token,
+    public void execute(ClientTransactionHandler client, ActivityClientRecord r,
             PendingTransactionActions pendingActions) {
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityDeliverResult");
-        client.handleSendResult(token, mResultInfoList, "ACTIVITY_RESULT");
+        client.handleSendResult(r, mResultInfoList, "ACTIVITY_RESULT");
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
 
@@ -88,7 +89,7 @@
         mResultInfoList = in.createTypedArrayList(ResultInfo.CREATOR);
     }
 
-    public static final @android.annotation.NonNull Parcelable.Creator<ActivityResultItem> CREATOR =
+    public static final @NonNull Parcelable.Creator<ActivityResultItem> CREATOR =
             new Parcelable.Creator<ActivityResultItem>() {
         public ActivityResultItem createFromParcel(Parcel in) {
             return new ActivityResultItem(in);
diff --git a/core/java/android/app/servertransaction/ActivityTransactionItem.java b/core/java/android/app/servertransaction/ActivityTransactionItem.java
new file mode 100644
index 0000000..f7d7e9d
--- /dev/null
+++ b/core/java/android/app/servertransaction/ActivityTransactionItem.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+
+import android.annotation.NonNull;
+import android.app.ActivityThread.ActivityClientRecord;
+import android.app.ClientTransactionHandler;
+import android.os.IBinder;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * An activity-targeting callback message to a client that can be scheduled and executed.
+ * It also provides nullity-free version of
+ * {@link #execute(ClientTransactionHandler, IBinder, PendingTransactionActions)} for child class
+ * to inherit.
+ *
+ * @see ClientTransaction
+ * @see ClientTransactionItem
+ * @see com.android.server.wm.ClientLifecycleManager
+ * @hide
+ */
+public abstract class ActivityTransactionItem extends ClientTransactionItem {
+    @Override
+    public final void execute(ClientTransactionHandler client, IBinder token,
+            PendingTransactionActions pendingActions) {
+        final ActivityClientRecord r = getActivityClientRecord(client, token);
+
+        execute(client, r, pendingActions);
+    }
+
+    /**
+     * Like {@link #execute(ClientTransactionHandler, IBinder, PendingTransactionActions)},
+     * but take non-null {@link ActivityClientRecord} as a parameter.
+     */
+    @VisibleForTesting(visibility = PACKAGE)
+    public abstract void execute(@NonNull ClientTransactionHandler client,
+            @NonNull ActivityClientRecord r, PendingTransactionActions pendingActions);
+
+    @NonNull ActivityClientRecord getActivityClientRecord(
+            @NonNull ClientTransactionHandler client, IBinder token) {
+        final ActivityClientRecord r = client.getActivityClient(token);
+        if (r == null) {
+            throw new IllegalArgumentException("Activity client record must not be null to execute "
+                    + "transaction item");
+        }
+        if (client.getActivity(token) == null) {
+            throw new IllegalArgumentException("Activity must not be null to execute "
+                    + "transaction item");
+        }
+        return r;
+    }
+}
diff --git a/core/java/android/app/servertransaction/DestroyActivityItem.java b/core/java/android/app/servertransaction/DestroyActivityItem.java
index 3ee7614..1611369 100644
--- a/core/java/android/app/servertransaction/DestroyActivityItem.java
+++ b/core/java/android/app/servertransaction/DestroyActivityItem.java
@@ -18,6 +18,8 @@
 
 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 
+import android.annotation.NonNull;
+import android.app.ActivityThread.ActivityClientRecord;
 import android.app.ClientTransactionHandler;
 import android.os.IBinder;
 import android.os.Parcel;
@@ -38,10 +40,10 @@
     }
 
     @Override
-    public void execute(ClientTransactionHandler client, IBinder token,
+    public void execute(ClientTransactionHandler client, ActivityClientRecord r,
             PendingTransactionActions pendingActions) {
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityDestroy");
-        client.handleDestroyActivity(token, mFinished, mConfigChanges,
+        client.handleDestroyActivity(r, mFinished, mConfigChanges,
                 false /* getNonConfigInstance */, "DestroyActivityItem");
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
@@ -92,7 +94,7 @@
         mConfigChanges = in.readInt();
     }
 
-    public static final @android.annotation.NonNull Creator<DestroyActivityItem> CREATOR =
+    public static final @NonNull Creator<DestroyActivityItem> CREATOR =
             new Creator<DestroyActivityItem>() {
         public DestroyActivityItem createFromParcel(Parcel in) {
             return new DestroyActivityItem(in);
diff --git a/core/java/android/app/servertransaction/EnterPipRequestedItem.java b/core/java/android/app/servertransaction/EnterPipRequestedItem.java
index b2a1276..b7e81a5 100644
--- a/core/java/android/app/servertransaction/EnterPipRequestedItem.java
+++ b/core/java/android/app/servertransaction/EnterPipRequestedItem.java
@@ -16,20 +16,20 @@
 
 package android.app.servertransaction;
 
+import android.app.ActivityThread.ActivityClientRecord;
 import android.app.ClientTransactionHandler;
-import android.os.IBinder;
 import android.os.Parcel;
 
 /**
  * Request an activity to enter picture-in-picture mode.
  * @hide
  */
-public final class EnterPipRequestedItem extends ClientTransactionItem {
+public final class EnterPipRequestedItem extends ActivityTransactionItem {
 
     @Override
-    public void execute(ClientTransactionHandler client, IBinder token,
+    public void execute(ClientTransactionHandler client, ActivityClientRecord r,
             PendingTransactionActions pendingActions) {
-        client.handlePictureInPictureRequested(token);
+        client.handlePictureInPictureRequested(r);
     }
 
     // ObjectPoolItem implementation
diff --git a/core/java/android/app/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java
index 2e7b626..77457af 100644
--- a/core/java/android/app/servertransaction/LaunchActivityItem.java
+++ b/core/java/android/app/servertransaction/LaunchActivityItem.java
@@ -18,6 +18,7 @@
 
 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 
+import android.annotation.NonNull;
 import android.app.ActivityThread.ActivityClientRecord;
 import android.app.ClientTransactionHandler;
 import android.app.ProfilerInfo;
@@ -163,7 +164,7 @@
                 in.readTypedObject(FixedRotationAdjustments.CREATOR));
     }
 
-    public static final @android.annotation.NonNull Creator<LaunchActivityItem> CREATOR =
+    public static final @NonNull Creator<LaunchActivityItem> CREATOR =
             new Creator<LaunchActivityItem>() {
         public LaunchActivityItem createFromParcel(Parcel in) {
             return new LaunchActivityItem(in);
diff --git a/core/java/android/app/servertransaction/MoveToDisplayItem.java b/core/java/android/app/servertransaction/MoveToDisplayItem.java
index 9a457a3..32de53f 100644
--- a/core/java/android/app/servertransaction/MoveToDisplayItem.java
+++ b/core/java/android/app/servertransaction/MoveToDisplayItem.java
@@ -19,6 +19,7 @@
 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 
 import android.annotation.NonNull;
+import android.app.ActivityThread.ActivityClientRecord;
 import android.app.ClientTransactionHandler;
 import android.content.res.Configuration;
 import android.os.IBinder;
@@ -31,23 +32,24 @@
  * Activity move to a different display message.
  * @hide
  */
-public class MoveToDisplayItem extends ClientTransactionItem {
+public class MoveToDisplayItem extends ActivityTransactionItem {
 
     private int mTargetDisplayId;
     private Configuration mConfiguration;
 
     @Override
     public void preExecute(ClientTransactionHandler client, IBinder token) {
+        final ActivityClientRecord r = getActivityClientRecord(client, token);
         // Notify the client of an upcoming change in the token configuration. This ensures that
         // batches of config change items only process the newest configuration.
-        client.updatePendingActivityConfiguration(token, mConfiguration);
+        client.updatePendingActivityConfiguration(r, mConfiguration);
     }
 
     @Override
-    public void execute(ClientTransactionHandler client, IBinder token,
+    public void execute(ClientTransactionHandler client, ActivityClientRecord r,
             PendingTransactionActions pendingActions) {
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityMovedToDisplay");
-        client.handleActivityConfigurationChanged(token, mConfiguration, mTargetDisplayId);
+        client.handleActivityConfigurationChanged(r, mConfiguration, mTargetDisplayId);
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
 
@@ -96,7 +98,8 @@
         mConfiguration = in.readTypedObject(Configuration.CREATOR);
     }
 
-    public static final @android.annotation.NonNull Creator<MoveToDisplayItem> CREATOR = new Creator<MoveToDisplayItem>() {
+    public static final @NonNull Creator<MoveToDisplayItem> CREATOR =
+            new Creator<MoveToDisplayItem>() {
         public MoveToDisplayItem createFromParcel(Parcel in) {
             return new MoveToDisplayItem(in);
         }
diff --git a/core/java/android/app/servertransaction/NewIntentItem.java b/core/java/android/app/servertransaction/NewIntentItem.java
index 6a4996d..b4e2a7b 100644
--- a/core/java/android/app/servertransaction/NewIntentItem.java
+++ b/core/java/android/app/servertransaction/NewIntentItem.java
@@ -19,9 +19,10 @@
 import static android.app.servertransaction.ActivityLifecycleItem.ON_RESUME;
 import static android.app.servertransaction.ActivityLifecycleItem.UNDEFINED;
 
+import android.annotation.NonNull;
+import android.app.ActivityThread.ActivityClientRecord;
 import android.app.ClientTransactionHandler;
 import android.compat.annotation.UnsupportedAppUsage;
-import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.Trace;
@@ -35,7 +36,7 @@
  * New intent message.
  * @hide
  */
-public class NewIntentItem extends ClientTransactionItem {
+public class NewIntentItem extends ActivityTransactionItem {
 
     @UnsupportedAppUsage
     private List<ReferrerIntent> mIntents;
@@ -47,10 +48,10 @@
     }
 
     @Override
-    public void execute(ClientTransactionHandler client, IBinder token,
+    public void execute(ClientTransactionHandler client, ActivityClientRecord r,
             PendingTransactionActions pendingActions) {
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityNewIntent");
-        client.handleNewIntent(token, mIntents);
+        client.handleNewIntent(r, mIntents);
         Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
     }
 
@@ -94,7 +95,7 @@
         mIntents = in.createTypedArrayList(ReferrerIntent.CREATOR);
     }
 
-    public static final @android.annotation.NonNull Parcelable.Creator<NewIntentItem> CREATOR =
+    public static final @NonNull Parcelable.Creator<NewIntentItem> CREATOR =
             new Parcelable.Creator<NewIntentItem>() {
         public NewIntentItem createFromParcel(Parcel in) {
             return new NewIntentItem(in);
diff --git a/core/java/android/app/servertransaction/PauseActivityItem.java b/core/java/android/app/servertransaction/PauseActivityItem.java
index f65c843..cb154e9 100644
--- a/core/java/android/app/servertransaction/PauseActivityItem.java
+++ b/core/java/android/app/servertransaction/PauseActivityItem.java
@@ -18,8 +18,9 @@
 
 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 
-import android.app.ActivityManager;
+import android.annotation.NonNull;
 import android.app.ActivityTaskManager;
+import android.app.ActivityThread.ActivityClientRecord;
 import android.app.ClientTransactionHandler;
 import android.os.IBinder;
 import android.os.Parcel;
@@ -40,10 +41,10 @@
     private boolean mDontReport;
 
     @Override
-    public void execute(ClientTransactionHandler client, IBinder token,
+    public void execute(ClientTransactionHandler client, ActivityClientRecord r,
             PendingTransactionActions pendingActions) {
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
-        client.handlePauseActivity(token, mFinished, mUserLeaving, mConfigChanges, pendingActions,
+        client.handlePauseActivity(r, mFinished, mUserLeaving, mConfigChanges, pendingActions,
                 "PAUSE_ACTIVITY_ITEM");
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
@@ -130,7 +131,7 @@
         mDontReport = in.readBoolean();
     }
 
-    public static final @android.annotation.NonNull Creator<PauseActivityItem> CREATOR =
+    public static final @NonNull Creator<PauseActivityItem> CREATOR =
             new Creator<PauseActivityItem>() {
         public PauseActivityItem createFromParcel(Parcel in) {
             return new PauseActivityItem(in);
diff --git a/core/java/android/app/servertransaction/ResumeActivityItem.java b/core/java/android/app/servertransaction/ResumeActivityItem.java
index 905076b..d2a156c 100644
--- a/core/java/android/app/servertransaction/ResumeActivityItem.java
+++ b/core/java/android/app/servertransaction/ResumeActivityItem.java
@@ -18,8 +18,10 @@
 
 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 
+import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
+import android.app.ActivityThread.ActivityClientRecord;
 import android.app.ClientTransactionHandler;
 import android.os.IBinder;
 import android.os.Parcel;
@@ -46,10 +48,10 @@
     }
 
     @Override
-    public void execute(ClientTransactionHandler client, IBinder token,
+    public void execute(ClientTransactionHandler client, ActivityClientRecord r,
             PendingTransactionActions pendingActions) {
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityResume");
-        client.handleResumeActivity(token, true /* finalStateRequest */, mIsForward,
+        client.handleResumeActivity(r, true /* finalStateRequest */, mIsForward,
                 "RESUME_ACTIVITY");
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
@@ -128,7 +130,7 @@
         mIsForward = in.readBoolean();
     }
 
-    public static final @android.annotation.NonNull Creator<ResumeActivityItem> CREATOR =
+    public static final @NonNull Creator<ResumeActivityItem> CREATOR =
             new Creator<ResumeActivityItem>() {
         public ResumeActivityItem createFromParcel(Parcel in) {
             return new ResumeActivityItem(in);
diff --git a/core/java/android/app/servertransaction/StartActivityItem.java b/core/java/android/app/servertransaction/StartActivityItem.java
index 4fbe02b..ae0bd24 100644
--- a/core/java/android/app/servertransaction/StartActivityItem.java
+++ b/core/java/android/app/servertransaction/StartActivityItem.java
@@ -18,8 +18,9 @@
 
 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 
+import android.annotation.NonNull;
+import android.app.ActivityThread.ActivityClientRecord;
 import android.app.ClientTransactionHandler;
-import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Trace;
 
@@ -32,10 +33,10 @@
     private static final String TAG = "StartActivityItem";
 
     @Override
-    public void execute(ClientTransactionHandler client, IBinder token,
+    public void execute(ClientTransactionHandler client, ActivityClientRecord r,
             PendingTransactionActions pendingActions) {
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "startActivityItem");
-        client.handleStartActivity(token, pendingActions);
+        client.handleStartActivity(r, pendingActions);
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
 
@@ -79,7 +80,7 @@
         // Empty
     }
 
-    public static final @android.annotation.NonNull Creator<StartActivityItem> CREATOR =
+    public static final @NonNull Creator<StartActivityItem> CREATOR =
             new Creator<StartActivityItem>() {
                 public StartActivityItem createFromParcel(Parcel in) {
                     return new StartActivityItem(in);
diff --git a/core/java/android/app/servertransaction/StopActivityItem.java b/core/java/android/app/servertransaction/StopActivityItem.java
index 8668bd4..7708104 100644
--- a/core/java/android/app/servertransaction/StopActivityItem.java
+++ b/core/java/android/app/servertransaction/StopActivityItem.java
@@ -18,6 +18,8 @@
 
 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 
+import android.annotation.NonNull;
+import android.app.ActivityThread.ActivityClientRecord;
 import android.app.ClientTransactionHandler;
 import android.os.IBinder;
 import android.os.Parcel;
@@ -34,10 +36,10 @@
     private int mConfigChanges;
 
     @Override
-    public void execute(ClientTransactionHandler client, IBinder token,
+    public void execute(ClientTransactionHandler client, ActivityClientRecord r,
             PendingTransactionActions pendingActions) {
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
-        client.handleStopActivity(token, mConfigChanges, pendingActions,
+        client.handleStopActivity(r, mConfigChanges, pendingActions,
                 true /* finalStateRequest */, "STOP_ACTIVITY_ITEM");
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
@@ -93,7 +95,7 @@
         mConfigChanges = in.readInt();
     }
 
-    public static final @android.annotation.NonNull Creator<StopActivityItem> CREATOR =
+    public static final @NonNull Creator<StopActivityItem> CREATOR =
             new Creator<StopActivityItem>() {
         public StopActivityItem createFromParcel(Parcel in) {
             return new StopActivityItem(in);
diff --git a/core/java/android/app/servertransaction/TopResumedActivityChangeItem.java b/core/java/android/app/servertransaction/TopResumedActivityChangeItem.java
index c7e4c36..345c1dd 100644
--- a/core/java/android/app/servertransaction/TopResumedActivityChangeItem.java
+++ b/core/java/android/app/servertransaction/TopResumedActivityChangeItem.java
@@ -17,7 +17,9 @@
 
 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 
+import android.annotation.NonNull;
 import android.app.ActivityTaskManager;
+import android.app.ActivityThread.ActivityClientRecord;
 import android.app.ClientTransactionHandler;
 import android.os.IBinder;
 import android.os.Parcel;
@@ -28,15 +30,15 @@
  * Top resumed activity changed callback.
  * @hide
  */
-public class TopResumedActivityChangeItem extends ClientTransactionItem {
+public class TopResumedActivityChangeItem extends ActivityTransactionItem {
 
     private boolean mOnTop;
 
     @Override
-    public void execute(ClientTransactionHandler client, IBinder token,
+    public void execute(ClientTransactionHandler client, ActivityClientRecord r,
             PendingTransactionActions pendingActions) {
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "topResumedActivityChangeItem");
-        client.handleTopResumedActivityChanged(token, mOnTop, "topResumedActivityChangeItem");
+        client.handleTopResumedActivityChanged(r, mOnTop, "topResumedActivityChangeItem");
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
 
@@ -97,16 +99,16 @@
         mOnTop = in.readBoolean();
     }
 
-    public static final @android.annotation.NonNull Creator<TopResumedActivityChangeItem> CREATOR =
+    public static final @NonNull Creator<TopResumedActivityChangeItem> CREATOR =
             new Creator<TopResumedActivityChangeItem>() {
-                public TopResumedActivityChangeItem createFromParcel(Parcel in) {
-                    return new TopResumedActivityChangeItem(in);
-                }
+        public TopResumedActivityChangeItem createFromParcel(Parcel in) {
+            return new TopResumedActivityChangeItem(in);
+        }
 
-                public TopResumedActivityChangeItem[] newArray(int size) {
-                    return new TopResumedActivityChangeItem[size];
-                }
-            };
+        public TopResumedActivityChangeItem[] newArray(int size) {
+            return new TopResumedActivityChangeItem[size];
+        }
+    };
 
     @Override
     public boolean equals(Object o) {
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index 17fcda5..3dcf2cb 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -218,29 +218,29 @@
                             null /* customIntent */);
                     break;
                 case ON_START:
-                    mTransactionHandler.handleStartActivity(r.token, mPendingActions);
+                    mTransactionHandler.handleStartActivity(r, mPendingActions);
                     break;
                 case ON_RESUME:
-                    mTransactionHandler.handleResumeActivity(r.token, false /* finalStateRequest */,
+                    mTransactionHandler.handleResumeActivity(r, false /* finalStateRequest */,
                             r.isForward, "LIFECYCLER_RESUME_ACTIVITY");
                     break;
                 case ON_PAUSE:
-                    mTransactionHandler.handlePauseActivity(r.token, false /* finished */,
+                    mTransactionHandler.handlePauseActivity(r, false /* finished */,
                             false /* userLeaving */, 0 /* configChanges */, mPendingActions,
                             "LIFECYCLER_PAUSE_ACTIVITY");
                     break;
                 case ON_STOP:
-                    mTransactionHandler.handleStopActivity(r.token, 0 /* configChanges */,
+                    mTransactionHandler.handleStopActivity(r, 0 /* configChanges */,
                             mPendingActions, false /* finalStateRequest */,
                             "LIFECYCLER_STOP_ACTIVITY");
                     break;
                 case ON_DESTROY:
-                    mTransactionHandler.handleDestroyActivity(r.token, false /* finishing */,
+                    mTransactionHandler.handleDestroyActivity(r, false /* finishing */,
                             0 /* configChanges */, false /* getNonConfigInstance */,
                             "performLifecycleSequence. cycling to:" + path.get(size - 1));
                     break;
                 case ON_RESTART:
-                    mTransactionHandler.performRestartActivity(r.token, false /* start */);
+                    mTransactionHandler.performRestartActivity(r, false /* start */);
                     break;
                 default:
                     throw new IllegalArgumentException("Unexpected lifecycle state: " + state);
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/app/timezonedetector/TimeZoneCapabilities.java b/core/java/android/app/timezonedetector/TimeZoneCapabilities.java
index 236b006..cc0af3f 100644
--- a/core/java/android/app/timezonedetector/TimeZoneCapabilities.java
+++ b/core/java/android/app/timezonedetector/TimeZoneCapabilities.java
@@ -91,11 +91,13 @@
 
     private final @UserIdInt int mUserId;
     private final @CapabilityState int mConfigureAutoDetectionEnabled;
+    private final @CapabilityState int mConfigureGeoDetectionEnabled;
     private final @CapabilityState int mSuggestManualTimeZone;
 
     private TimeZoneCapabilities(@NonNull Builder builder) {
         this.mUserId = builder.mUserId;
         this.mConfigureAutoDetectionEnabled = builder.mConfigureAutoDetectionEnabled;
+        this.mConfigureGeoDetectionEnabled = builder.mConfigureGeoDetectionEnabled;
         this.mSuggestManualTimeZone = builder.mSuggestManualTimeZone;
     }
 
@@ -103,6 +105,7 @@
     private static TimeZoneCapabilities createFromParcel(Parcel in) {
         return new TimeZoneCapabilities.Builder(in.readInt())
                 .setConfigureAutoDetectionEnabled(in.readInt())
+                .setConfigureGeoDetectionEnabled(in.readInt())
                 .setSuggestManualTimeZone(in.readInt())
                 .build();
     }
@@ -111,6 +114,7 @@
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeInt(mUserId);
         dest.writeInt(mConfigureAutoDetectionEnabled);
+        dest.writeInt(mConfigureGeoDetectionEnabled);
         dest.writeInt(mSuggestManualTimeZone);
     }
 
@@ -120,8 +124,8 @@
     }
 
     /**
-     * Returns the user's capability state for controlling automatic time zone detection via
-     * {@link TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} and {@link
+     * Returns the user's capability state for controlling whether automatic time zone detection is
+     * enabled via {@link TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} and {@link
      * TimeZoneConfiguration#isAutoDetectionEnabled()}.
      */
     @CapabilityState
@@ -130,6 +134,16 @@
     }
 
     /**
+     * Returns the user's capability state for controlling whether geolocation can be used to detect
+     * time zone via {@link TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} and {@link
+     * TimeZoneConfiguration#isGeoDetectionEnabled()}.
+     */
+    @CapabilityState
+    public int getConfigureGeoDetectionEnabled() {
+        return mConfigureGeoDetectionEnabled;
+    }
+
+    /**
      * Returns the user's capability state for manually setting the time zone on a device via
      * {@link TimeZoneDetector#suggestManualTimeZone(ManualTimeZoneSuggestion)}.
      *
@@ -157,12 +171,16 @@
         TimeZoneCapabilities that = (TimeZoneCapabilities) o;
         return mUserId == that.mUserId
                 && mConfigureAutoDetectionEnabled == that.mConfigureAutoDetectionEnabled
+                && mConfigureGeoDetectionEnabled == that.mConfigureGeoDetectionEnabled
                 && mSuggestManualTimeZone == that.mSuggestManualTimeZone;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mUserId, mConfigureAutoDetectionEnabled, mSuggestManualTimeZone);
+        return Objects.hash(mUserId,
+                mConfigureAutoDetectionEnabled,
+                mConfigureGeoDetectionEnabled,
+                mSuggestManualTimeZone);
     }
 
     @Override
@@ -170,6 +188,7 @@
         return "TimeZoneDetectorCapabilities{"
                 + "mUserId=" + mUserId
                 + ", mConfigureAutomaticDetectionEnabled=" + mConfigureAutoDetectionEnabled
+                + ", mConfigureGeoDetectionEnabled=" + mConfigureGeoDetectionEnabled
                 + ", mSuggestManualTimeZone=" + mSuggestManualTimeZone
                 + '}';
     }
@@ -179,6 +198,7 @@
 
         private final @UserIdInt int mUserId;
         private @CapabilityState int mConfigureAutoDetectionEnabled;
+        private @CapabilityState int mConfigureGeoDetectionEnabled;
         private @CapabilityState int mSuggestManualTimeZone;
 
         /**
@@ -194,6 +214,12 @@
             return this;
         }
 
+        /** Sets the state for the geolocation time zone detection enabled config. */
+        public Builder setConfigureGeoDetectionEnabled(@CapabilityState int value) {
+            this.mConfigureGeoDetectionEnabled = value;
+            return this;
+        }
+
         /** Sets the state for the suggestManualTimeZone action. */
         public Builder setSuggestManualTimeZone(@CapabilityState int value) {
             this.mSuggestManualTimeZone = value;
@@ -204,6 +230,7 @@
         @NonNull
         public TimeZoneCapabilities build() {
             verifyCapabilitySet(mConfigureAutoDetectionEnabled, "configureAutoDetectionEnabled");
+            verifyCapabilitySet(mConfigureGeoDetectionEnabled, "configureGeoDetectionEnabled");
             verifyCapabilitySet(mSuggestManualTimeZone, "suggestManualTimeZone");
             return new TimeZoneCapabilities(this);
         }
diff --git a/core/java/android/app/timezonedetector/TimeZoneConfiguration.java b/core/java/android/app/timezonedetector/TimeZoneConfiguration.java
index 047d349..6f84ee2 100644
--- a/core/java/android/app/timezonedetector/TimeZoneConfiguration.java
+++ b/core/java/android/app/timezonedetector/TimeZoneConfiguration.java
@@ -67,6 +67,10 @@
     @Property
     public static final String PROPERTY_AUTO_DETECTION_ENABLED = "autoDetectionEnabled";
 
+    /** See {@link TimeZoneConfiguration#isGeoDetectionEnabled()} for details. */
+    @Property
+    public static final String PROPERTY_GEO_DETECTION_ENABLED = "geoDetectionEnabled";
+
     private final Bundle mBundle;
 
     private TimeZoneConfiguration(Builder builder) {
@@ -86,7 +90,8 @@
 
     /** Returns {@code true} if all known properties are set. */
     public boolean isComplete() {
-        return hasProperty(PROPERTY_AUTO_DETECTION_ENABLED);
+        return hasProperty(PROPERTY_AUTO_DETECTION_ENABLED)
+                && hasProperty(PROPERTY_GEO_DETECTION_ENABLED);
     }
 
     /** Returns true if the specified property is set. */
@@ -108,6 +113,28 @@
         return mBundle.getBoolean(PROPERTY_AUTO_DETECTION_ENABLED);
     }
 
+    /**
+     * Returns the value of the {@link #PROPERTY_GEO_DETECTION_ENABLED} property. This
+     * controls whether a device can use location to determine time zone. Only used when
+     * {@link #isAutoDetectionEnabled()} is true.
+     *
+     * @throws IllegalStateException if the field has not been set
+     */
+    public boolean isGeoDetectionEnabled() {
+        if (!mBundle.containsKey(PROPERTY_GEO_DETECTION_ENABLED)) {
+            throw new IllegalStateException(PROPERTY_GEO_DETECTION_ENABLED + " is not set");
+        }
+        return mBundle.getBoolean(PROPERTY_GEO_DETECTION_ENABLED);
+    }
+
+    /**
+     * Convenience method to merge this with another. The argument configuration properties have
+     * precedence.
+     */
+    public TimeZoneConfiguration with(TimeZoneConfiguration other) {
+        return new Builder(this).mergeProperties(other).build();
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -174,6 +201,12 @@
             return this;
         }
 
+        /** Sets the desired state of the geolocation time zone detection enabled property. */
+        public Builder setGeoDetectionEnabled(boolean enabled) {
+            this.mBundle.putBoolean(PROPERTY_GEO_DETECTION_ENABLED, enabled);
+            return this;
+        }
+
         /** Returns the {@link TimeZoneConfiguration}. */
         @NonNull
         public TimeZoneConfiguration build() {
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java
index 3522b1b..0e6a063 100644
--- a/core/java/android/app/usage/UsageEvents.java
+++ b/core/java/android/app/usage/UsageEvents.java
@@ -199,7 +199,7 @@
         public static final int NOTIFICATION_INTERRUPTION = 12;
 
         /**
-         * A Slice was pinned by the default launcher or the default assistant.
+         * A Slice was pinned by the default assistant.
          * @hide
          */
         @SystemApi
diff --git a/core/java/android/content/LocusId.java b/core/java/android/content/LocusId.java
index 283cea0..98e71f0 100644
--- a/core/java/android/content/LocusId.java
+++ b/core/java/android/content/LocusId.java
@@ -33,7 +33,7 @@
  * by the Android System to correlate state between different subsystems such as content capture,
  * shortcuts, and notifications.
  *
- * <p>For example, if your app provides an activiy representing a chat between 2 users
+ * <p>For example, if your app provides an activity representing a chat between 2 users
  * (say {@code A} and {@code B}, this chat state could be represented by:
  *
  * <pre><code>
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/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/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 885d137..4f949cf 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;
@@ -150,6 +152,16 @@
                 mHandler.obtainMessage(MSG_CHALLENGE_GENERATED, 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();
+        }
     };
 
     /**
@@ -1071,14 +1083,29 @@
     }
 
     /**
+     * Callback structure provided to {@link #generateChallenge(GenerateChallengeCallback)}.
      * @hide
      */
-    public abstract static class GenerateChallengeCallback {
-        public abstract void onGenerateChallengeResult(long challenge);
+    public interface GenerateChallengeCallback {
+        /**
+         * Invoked when a challenge has been generated.
+         */
+        void onGenerateChallengeResult(long challenge);
+
+        /**
+         * Invoked if the challenge has not been revoked and a subsequent caller/owner invokes
+         * {@link #generateChallenge(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 abstract static class InternalGenerateChallengeCallback
-            extends GenerateChallengeCallback {}
+            implements GenerateChallengeCallback {}
 
     private class OnEnrollCancelListener implements OnCancelListener {
         @Override
@@ -1157,6 +1184,12 @@
                     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);
             }
@@ -1193,6 +1226,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/IFaceServiceReceiver.aidl b/core/java/android/hardware/face/IFaceServiceReceiver.aidl
index 2600b7de..108f4f6 100644
--- a/core/java/android/hardware/face/IFaceServiceReceiver.aidl
+++ b/core/java/android/hardware/face/IFaceServiceReceiver.aidl
@@ -32,4 +32,6 @@
     void onFeatureSet(boolean success, int feature);
     void onFeatureGet(boolean success, int feature, boolean value);
     void onChallengeGenerated(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 e384da7..71598eb 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -377,12 +377,12 @@
     /**
      * @hide
      */
-    public abstract static class GenerateChallengeCallback {
-        public abstract void onChallengeGenerated(long challenge);
+    public interface GenerateChallengeCallback {
+        void onChallengeGenerated(long challenge);
     }
 
     private abstract static class InternalGenerateChallengeCallback
-            extends GenerateChallengeCallback {}
+            implements GenerateChallengeCallback {}
 
     /**
      * Request authentication of a crypto object. This call warms up the fingerprint hardware
@@ -581,37 +581,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_FINGERPRINT)
-    public long generateChallengeBlocking() {
-        final AtomicReference<Long> result = new AtomicReference<>();
-        final CountDownLatch latch = new CountDownLatch(1);
-        final GenerateChallengeCallback callback = new InternalGenerateChallengeCallback() {
-            @Override
-            public void onChallengeGenerated(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
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/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index 0cce192..8f8d451 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -287,8 +287,8 @@
     /**
      * Control network activity of a UID over interfaces with a quota limit.
      */
-    void setUidMeteredNetworkBlacklist(int uid, boolean enable);
-    void setUidMeteredNetworkWhitelist(int uid, boolean enable);
+    void setUidMeteredNetworkDenylist(int uid, boolean enable);
+    void setUidMeteredNetworkAllowlist(int uid, boolean enable);
     boolean setDataSaverModeEnabled(boolean enable);
 
     void setUidCleartextNetworkPolicy(int uid, int policy);
diff --git a/core/java/android/os/Parcelable.java b/core/java/android/os/Parcelable.java
index 3d3759e..f14f66b 100644
--- a/core/java/android/os/Parcelable.java
+++ b/core/java/android/os/Parcelable.java
@@ -161,6 +161,7 @@
      * @return true if this parcelable is stable.
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     default @Stability int getStability() {
         return PARCELABLE_STABILITY_LOCAL;
     }
diff --git a/core/java/android/os/SystemClock.java b/core/java/android/os/SystemClock.java
index fd68c2b..26f3af0 100644
--- a/core/java/android/os/SystemClock.java
+++ b/core/java/android/os/SystemClock.java
@@ -178,6 +178,15 @@
     native public static long uptimeMillis();
 
     /**
+     * Returns nanoseconds since boot, not counting time spent in deep sleep.
+     *
+     * @return nanoseconds of non-sleep uptime since boot.
+     * @hide
+     */
+    @CriticalNative
+    public static native long uptimeNanos();
+
+    /**
      * Return {@link Clock} that starts at system boot, not counting time spent
      * in deep sleep.
      *
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..7ad0e7d 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8974,6 +8974,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
          */
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/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 2ce993d..6ef086b 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -22,8 +22,6 @@
 import static android.graphics.Matrix.MSKEW_Y;
 import static android.graphics.Matrix.MTRANS_X;
 import static android.graphics.Matrix.MTRANS_Y;
-import static android.view.Surface.ROTATION_270;
-import static android.view.Surface.ROTATION_90;
 import static android.view.SurfaceControlProto.HASH_CODE;
 import static android.view.SurfaceControlProto.NAME;
 
@@ -590,6 +588,26 @@
         public boolean containsSecureLayers() {
             return mContainsSecureLayers;
         }
+
+        /**
+         * Copy content of ScreenshotHardwareBuffer into a hardware bitmap and return it.
+         * Note: If you want to modify the Bitmap in software, you will need to copy the Bitmap
+         * into
+         * a software Bitmap using {@link Bitmap#copy(Bitmap.Config, boolean)}
+         *
+         * CAVEAT: This can be extremely slow; avoid use unless absolutely necessary; prefer to
+         * directly
+         * use the {@link HardwareBuffer} directly.
+         *
+         * @return Bitmap generated from the {@link HardwareBuffer}
+         */
+        public Bitmap asBitmap() {
+            if (mHardwareBuffer == null) {
+                Log.w(TAG, "Failed to take screenshot. Null screenshot object");
+                return null;
+            }
+            return Bitmap.wrapHardwareBuffer(mHardwareBuffer, mColorSpace);
+        }
     }
 
     /**
@@ -597,7 +615,7 @@
      * are shared between {@link DisplayCaptureArgs} and {@link LayerCaptureArgs}
      * @hide
      */
-    public abstract static class CaptureArgs {
+    private abstract static class CaptureArgs {
         private final int mPixelFormat;
         private final Rect mSourceCrop = new Rect();
         private final float mFrameScale;
@@ -615,7 +633,7 @@
          *
          * @param <T> A builder that extends {@link Builder}
          */
-        public abstract static class Builder<T extends Builder<T>> {
+        abstract static class Builder<T extends Builder<T>> {
             private int mPixelFormat = PixelFormat.RGBA_8888;
             private final Rect mSourceCrop = new Rect();
             private float mFrameScale = 1;
@@ -675,7 +693,6 @@
         private final int mWidth;
         private final int mHeight;
         private final boolean mUseIdentityTransform;
-        private final int mRotation;
 
         private DisplayCaptureArgs(Builder builder) {
             super(builder);
@@ -683,7 +700,6 @@
             mWidth = builder.mWidth;
             mHeight = builder.mHeight;
             mUseIdentityTransform = builder.mUseIdentityTransform;
-            mRotation = builder.mRotation;
         }
 
         /**
@@ -694,7 +710,6 @@
             private int mWidth;
             private int mHeight;
             private boolean mUseIdentityTransform;
-            private @Surface.Rotation int mRotation = Surface.ROTATION_0;
 
             /**
              * Construct a new {@link LayerCaptureArgs} with the set parameters. The builder
@@ -736,26 +751,16 @@
             }
 
             /**
-             * Replace whatever transformation (rotation, scaling, translation) the surface
-             * layers are currently using with the identity transformation while taking the
-             * screenshot.
+             * Replace the rotation transform of the display with the identity transformation while
+             * taking the screenshot. This ensures the screenshot is taken in the ROTATION_0
+             * orientation. Set this value to false if the screenshot should be taken in the
+             * current screen orientation.
              */
             public Builder setUseIdentityTransform(boolean useIdentityTransform) {
                 mUseIdentityTransform = useIdentityTransform;
                 return this;
             }
 
-            /**
-             * Apply a custom clockwise rotation to the screenshot, i.e.
-             * Surface.ROTATION_0,90,180,270. SurfaceFlinger will always take screenshots in its
-             * native portrait orientation by default, so this is useful for returning screenshots
-             * that are independent of device orientation.
-             */
-            public Builder setRotation(@Surface.Rotation int rotation) {
-                mRotation = rotation;
-                return this;
-            }
-
             @Override
             Builder getThis() {
                 return this;
@@ -2221,130 +2226,16 @@
     }
 
     /**
-     * @see SurfaceControl#screenshot(Rect, int, int, boolean, int)}
+     * Captures all the surfaces in a display and returns a {@link ScreenshotHardwareBuffer} with
+     * the content.
+     *
      * @hide
      */
-    @UnsupportedAppUsage
-    public static Bitmap screenshot(Rect sourceCrop, int width, int height, int rotation) {
-        return screenshot(sourceCrop, width, height, false, rotation);
-    }
-
-    /**
-     * Copy the current screen contents into a hardware bitmap and return it.
-     * Note: If you want to modify the Bitmap in software, you will need to copy the Bitmap into
-     * a software Bitmap using {@link Bitmap#copy(Bitmap.Config, boolean)}
-     *
-     * CAVEAT: Versions of screenshot that return a {@link Bitmap} can be extremely slow; avoid use
-     * unless absolutely necessary; prefer the versions that use a {@link HardwareBuffer} such as
-     * {@link SurfaceControl#screenshotToBuffer(IBinder, Rect, int, int, boolean, int)}.
-     *
-     * @see SurfaceControl#screenshotToBuffer(IBinder, Rect, int, int, boolean, int)}
-     * @hide
-     */
-    @UnsupportedAppUsage
-    public static Bitmap screenshot(Rect sourceCrop, int width, int height,
-            boolean useIdentityTransform, int rotation) {
-        // TODO: should take the display as a parameter
-        final IBinder displayToken = SurfaceControl.getInternalDisplayToken();
-        if (displayToken == null) {
-            Log.w(TAG, "Failed to take screenshot because internal display is disconnected");
-            return null;
-        }
-
-        if (rotation == ROTATION_90 || rotation == ROTATION_270) {
-            rotation = (rotation == ROTATION_90) ? ROTATION_270 : ROTATION_90;
-        }
-
-        SurfaceControl.rotateCropForSF(sourceCrop, rotation);
-        final ScreenshotHardwareBuffer buffer = screenshotToBuffer(displayToken, sourceCrop, width,
-                height, useIdentityTransform, rotation);
-
-        if (buffer == null) {
-            Log.w(TAG, "Failed to take screenshot");
-            return null;
-        }
-        return Bitmap.wrapHardwareBuffer(buffer.getHardwareBuffer(), buffer.getColorSpace());
-    }
-
-    /**
-     * Captures all the surfaces in a display and returns a {@link HardwareBuffer} with the content.
-     *
-     * @param display              The display to take the screenshot of.
-     * @param sourceCrop           The portion of the screen to capture into the Bitmap; caller may
-     *                             pass in 'new Rect()' if no cropping is desired.
-     * @param width                The desired width of the returned bitmap; the raw screen will be
-     *                             scaled down to this size; caller may pass in 0 if no scaling is
-     *                             desired.
-     * @param height               The desired height of the returned bitmap; the raw screen will
-     *                             be scaled down to this size; caller may pass in 0 if no scaling
-     *                             is desired.
-     * @param useIdentityTransform Replace whatever transformation (rotation, scaling, translation)
-     *                             the surface layers are currently using with the identity
-     *                             transformation while taking the screenshot.
-     * @param rotation             Apply a custom clockwise rotation to the screenshot, i.e.
-     *                             Surface.ROTATION_0,90,180,270. SurfaceFlinger will always take
-     *                             screenshots in its native portrait orientation by default, so
-     *                             this is useful for returning screenshots that are independent of
-     *                             device orientation.
-     * @return Returns a HardwareBuffer that contains the captured content.
-     * @hide
-     */
-    public static ScreenshotHardwareBuffer screenshotToBuffer(IBinder display, Rect sourceCrop,
-            int width, int height, boolean useIdentityTransform, int rotation) {
-        if (display == null) {
-            throw new IllegalArgumentException("displayToken must not be null");
-        }
-
-        DisplayCaptureArgs captureArgs = new DisplayCaptureArgs.Builder(display)
-                .setSourceCrop(sourceCrop)
-                .setSize(width, height)
-                .setUseIdentityTransform(useIdentityTransform)
-                .setRotation(rotation)
-                .build();
-
+    public static ScreenshotHardwareBuffer captureDisplay(DisplayCaptureArgs captureArgs) {
         return nativeCaptureDisplay(captureArgs);
     }
 
     /**
-     * Like screenshotToBuffer, but if the caller is AID_SYSTEM, allows
-     * for the capture of secure layers. This is used for the screen rotation
-     * animation where the system server takes screenshots but does
-     * not persist them or allow them to leave the server. However in other
-     * cases in the system server, we mostly want to omit secure layers
-     * like when we take a screenshot on behalf of the assistant.
-     *
-     * @hide
-     */
-    public static ScreenshotHardwareBuffer screenshotToBufferWithSecureLayersUnsafe(IBinder display,
-            Rect sourceCrop, int width, int height, boolean useIdentityTransform,
-            int rotation) {
-        if (display == null) {
-            throw new IllegalArgumentException("displayToken must not be null");
-        }
-
-        DisplayCaptureArgs captureArgs = new DisplayCaptureArgs.Builder(display)
-                .setSourceCrop(sourceCrop)
-                .setSize(width, height)
-                .setUseIdentityTransform(useIdentityTransform)
-                .setRotation(rotation)
-                .setCaptureSecureLayers(true)
-                .build();
-
-        return nativeCaptureDisplay(captureArgs);
-    }
-
-    private static void rotateCropForSF(Rect crop, int rot) {
-        if (rot == Surface.ROTATION_90 || rot == Surface.ROTATION_270) {
-            int tmp = crop.top;
-            crop.top = crop.left;
-            crop.left = tmp;
-            tmp = crop.right;
-            crop.right = crop.bottom;
-            crop.bottom = tmp;
-        }
-    }
-
-    /**
      * Captures a layer and its children and returns a {@link HardwareBuffer} with the content.
      *
      * @param layer            The root layer to capture.
diff --git a/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl b/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl
index e814ec6..eb67191 100644
--- a/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl
+++ b/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl
@@ -29,7 +29,7 @@
 oneway interface IWindowMagnificationConnection {
 
     /**
-     * Enables window magnification on specifed display with specified center and scale.
+     * Enables window magnification on specified display with given center and scale and animation.
      *
      * @param displayId The logical display id.
      * @param scale magnification scale.
@@ -41,7 +41,7 @@
     void enableWindowMagnification(int displayId, float scale, float centerX, float centerY);
 
     /**
-     * Sets the scale of the window magnifier on specifed display.
+     * Sets the scale of the window magnifier on specified display.
      *
      * @param displayId The logical display id.
      * @param scale magnification scale.
@@ -49,14 +49,14 @@
     void setScale(int displayId, float scale);
 
      /**
-     * Disables window magnification on specifed display.
+     * Disables window magnification on specified display with animation.
      *
      * @param displayId The logical display id.
      */
     void disableWindowMagnification(int displayId);
 
     /**
-     * Moves the window magnifier on the specifed display.
+     * Moves the window magnifier on the specified display. It has no effect while animating.
      *
      * @param offsetX the amount in pixels to offset the window magnifier in the X direction, in
      *                current screen pixels.
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/textclassifier/TextClassificationSession.java b/core/java/android/view/textclassifier/TextClassificationSession.java
index fed3dbf..0008658 100644
--- a/core/java/android/view/textclassifier/TextClassificationSession.java
+++ b/core/java/android/view/textclassifier/TextClassificationSession.java
@@ -20,9 +20,11 @@
 import android.annotation.WorkerThread;
 import android.view.textclassifier.SelectionEvent.InvocationMethod;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.Preconditions;
 
 import java.util.Objects;
+import java.util.function.Supplier;
 
 import sun.misc.Cleaner;
 
@@ -40,6 +42,9 @@
     private final TextClassificationContext mClassificationContext;
     private final Cleaner mCleaner;
 
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
     private boolean mDestroyed;
 
     TextClassificationSession(TextClassificationContext context, TextClassifier delegate) {
@@ -54,8 +59,7 @@
 
     @Override
     public TextSelection suggestSelection(TextSelection.Request request) {
-        checkDestroyed();
-        return mDelegate.suggestSelection(request);
+        return checkDestroyedAndRun(() -> mDelegate.suggestSelection(request));
     }
 
     private void initializeRemoteSession() {
@@ -67,77 +71,97 @@
 
     @Override
     public TextClassification classifyText(TextClassification.Request request) {
-        checkDestroyed();
-        return mDelegate.classifyText(request);
+        return checkDestroyedAndRun(() -> mDelegate.classifyText(request));
     }
 
     @Override
     public TextLinks generateLinks(TextLinks.Request request) {
-        checkDestroyed();
-        return mDelegate.generateLinks(request);
+        return checkDestroyedAndRun(() -> mDelegate.generateLinks(request));
     }
 
     @Override
     public ConversationActions suggestConversationActions(ConversationActions.Request request) {
-        checkDestroyed();
-        return mDelegate.suggestConversationActions(request);
+        return checkDestroyedAndRun(() -> mDelegate.suggestConversationActions(request));
     }
 
     @Override
     public TextLanguage detectLanguage(TextLanguage.Request request) {
-        checkDestroyed();
-        return mDelegate.detectLanguage(request);
+        return checkDestroyedAndRun(() -> mDelegate.detectLanguage(request));
     }
 
     @Override
     public int getMaxGenerateLinksTextLength() {
-        checkDestroyed();
-        return mDelegate.getMaxGenerateLinksTextLength();
+        return checkDestroyedAndRun(mDelegate::getMaxGenerateLinksTextLength);
     }
 
     @Override
     public void onSelectionEvent(SelectionEvent event) {
-        try {
-            if (mEventHelper.sanitizeEvent(event)) {
-                mDelegate.onSelectionEvent(event);
+        checkDestroyedAndRun(() -> {
+            try {
+                if (mEventHelper.sanitizeEvent(event)) {
+                    mDelegate.onSelectionEvent(event);
+                }
+            } catch (Exception e) {
+                // Avoid crashing for event reporting.
+                Log.e(LOG_TAG, "Error reporting text classifier selection event", e);
             }
-        } catch (Exception e) {
-            // Avoid crashing for event reporting.
-            Log.e(LOG_TAG, "Error reporting text classifier selection event", e);
-        }
+            return null;
+        });
     }
 
     @Override
     public void onTextClassifierEvent(TextClassifierEvent event) {
-        try {
-            event.mHiddenTempSessionId = mSessionId;
-            mDelegate.onTextClassifierEvent(event);
-        } catch (Exception e) {
-            // Avoid crashing for event reporting.
-            Log.e(LOG_TAG, "Error reporting text classifier event", e);
-        }
+        checkDestroyedAndRun(() -> {
+            try {
+                event.mHiddenTempSessionId = mSessionId;
+                mDelegate.onTextClassifierEvent(event);
+            } catch (Exception e) {
+                // Avoid crashing for event reporting.
+                Log.e(LOG_TAG, "Error reporting text classifier event", e);
+            }
+            return null;
+        });
     }
 
     @Override
     public void destroy() {
-        mCleaner.clean();
-        mDestroyed = true;
+        synchronized (mLock) {
+            if (!mDestroyed) {
+                mCleaner.clean();
+                mDestroyed = true;
+            }
+        }
     }
 
     @Override
     public boolean isDestroyed() {
-        return mDestroyed;
+        synchronized (mLock) {
+            return mDestroyed;
+        }
     }
 
     /**
-     * @throws IllegalStateException if this TextClassification session has been destroyed.
+     * Check whether the TextClassification Session was destroyed before and after the actual API
+     * invocation, and return response if not.
+     *
+     * @param responseSupplier a Supplier that represents a TextClassifier call
+     * @return the response of the TextClassifier call
+     * @throws IllegalStateException if this TextClassification session was destroyed before the
+     *                               call returned
      * @see #isDestroyed()
      * @see #destroy()
      */
-    private void checkDestroyed() {
-        if (mDestroyed) {
-            throw new IllegalStateException("This TextClassification session has been destroyed");
+    private <T> T checkDestroyedAndRun(Supplier<T> responseSupplier) {
+        if (!isDestroyed()) {
+            T response = responseSupplier.get();
+            synchronized (mLock) {
+                if (!mDestroyed) {
+                    return response;
+                }
+            }
         }
+        throw new IllegalStateException(
+                "This TextClassification session has been destroyed");
     }
 
     /**
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/Editor.java b/core/java/android/widget/Editor.java
index 7683067..60f8bb7 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -154,6 +154,10 @@
     // Specifies whether to use the magnifier when pressing the insertion or selection handles.
     private static final boolean FLAG_USE_MAGNIFIER = true;
 
+    // Specifies how far to make the cursor start float when drag the cursor away from the
+    // beginning or end of the line.
+    private static final int CURSOR_START_FLOAT_DISTANCE_PX = 20;
+
     private static final int DELAY_BEFORE_HANDLE_FADES_OUT = 4000;
     private static final int RECENT_CUT_COPY_DURATION_MS = 15 * 1000; // 15 seconds in millis
 
@@ -289,6 +293,9 @@
     private boolean mRenderCursorRegardlessTiming;
     private Blink mBlink;
 
+    // Whether to let magnifier draw cursor on its surface. This is for floating cursor effect.
+    // And it can only be true when |mNewMagnifierEnabled| is true.
+    private boolean mDrawCursorOnMagnifier;
     boolean mCursorVisible = true;
     boolean mSelectAllOnFocus;
     boolean mTextIsSelectable;
@@ -890,7 +897,7 @@
         }
 
         boolean enabled = windowSupportsHandles && mTextView.getLayout() != null;
-        mInsertionControllerEnabled = enabled && isCursorVisible();
+        mInsertionControllerEnabled = enabled && (mDrawCursorOnMagnifier || isCursorVisible());
         mSelectionControllerEnabled = enabled && mTextView.textCanBeSelected();
 
         if (!mInsertionControllerEnabled) {
@@ -5101,26 +5108,38 @@
             final int[] textViewLocationOnScreen = new int[2];
             mTextView.getLocationOnScreen(textViewLocationOnScreen);
             final float touchXInView = event.getRawX() - textViewLocationOnScreen[0];
-            float leftBound = mTextView.getTotalPaddingLeft() - mTextView.getScrollX();
-            float rightBound = mTextView.getTotalPaddingLeft() - mTextView.getScrollX();
-            if (sameLineSelection && ((trigger == MagnifierHandleTrigger.SELECTION_END) ^ rtl)) {
-                leftBound += getHorizontal(mTextView.getLayout(), otherHandleOffset);
+            float leftBound, rightBound;
+            if (mNewMagnifierEnabled) {
+                leftBound = 0;
+                rightBound = mTextView.getWidth();
+                if (touchXInView < leftBound || touchXInView > rightBound) {
+                    // The touch is too far from the current line / selection, so hide the magnifier.
+                    return false;
+                }
             } else {
-                leftBound += mTextView.getLayout().getLineLeft(lineNumber);
-            }
-            if (sameLineSelection && ((trigger == MagnifierHandleTrigger.SELECTION_START) ^ rtl)) {
-                rightBound += getHorizontal(mTextView.getLayout(), otherHandleOffset);
-            } else {
-                rightBound += mTextView.getLayout().getLineRight(lineNumber);
-            }
-            leftBound *= mTextViewScaleX;
-            rightBound *= mTextViewScaleX;
-            final float contentWidth = Math.round(mMagnifierAnimator.mMagnifier.getWidth()
-                    / mMagnifierAnimator.mMagnifier.getZoom());
-            if (touchXInView < leftBound - contentWidth / 2
-                    || touchXInView > rightBound + contentWidth / 2) {
-                // The touch is too far from the current line / selection, so hide the magnifier.
-                return false;
+                leftBound = mTextView.getTotalPaddingLeft() - mTextView.getScrollX();
+                rightBound = mTextView.getTotalPaddingLeft() - mTextView.getScrollX();
+                if (sameLineSelection && ((trigger == MagnifierHandleTrigger.SELECTION_END)
+                        ^ rtl)) {
+                    leftBound += getHorizontal(mTextView.getLayout(), otherHandleOffset);
+                } else {
+                    leftBound += mTextView.getLayout().getLineLeft(lineNumber);
+                }
+                if (sameLineSelection && ((trigger == MagnifierHandleTrigger.SELECTION_START)
+                        ^ rtl)) {
+                    rightBound += getHorizontal(mTextView.getLayout(), otherHandleOffset);
+                } else {
+                    rightBound += mTextView.getLayout().getLineRight(lineNumber);
+                }
+                leftBound *= mTextViewScaleX;
+                rightBound *= mTextViewScaleX;
+                final float contentWidth = Math.round(mMagnifierAnimator.mMagnifier.getWidth()
+                        / mMagnifierAnimator.mMagnifier.getZoom());
+                if (touchXInView < leftBound - contentWidth / 2
+                        || touchXInView > rightBound + contentWidth / 2) {
+                    // The touch is too far from the current line / selection, so hide the magnifier.
+                    return false;
+                }
             }
 
             final float scaledTouchXInView;
@@ -5178,7 +5197,8 @@
             final Rect magnifierRect = new Rect(magnifierTopLeft.x, magnifierTopLeft.y,
                     magnifierTopLeft.x + mMagnifierAnimator.mMagnifier.getWidth(),
                     magnifierTopLeft.y + mMagnifierAnimator.mMagnifier.getHeight());
-            setVisible(!handleOverlapsMagnifier(HandleView.this, magnifierRect));
+            setVisible(!handleOverlapsMagnifier(HandleView.this, magnifierRect)
+                    && !mDrawCursorOnMagnifier);
             final HandleView otherHandle = getOtherSelectionHandle();
             if (otherHandle != null) {
                 otherHandle.setVisible(!handleOverlapsMagnifier(otherHandle, magnifierRect));
@@ -5208,7 +5228,20 @@
                     lineLeft += mTextView.getTotalPaddingLeft() - mTextView.getScrollX();
                     int lineRight = (int) layout.getLineRight(line);
                     lineRight += mTextView.getTotalPaddingLeft() - mTextView.getScrollX();
-                    mMagnifierAnimator.mMagnifier.setSourceHorizontalBounds(lineLeft, lineRight);
+                    mDrawCursorOnMagnifier =
+                            showPosInView.x < lineLeft - CURSOR_START_FLOAT_DISTANCE_PX
+                            || showPosInView.x > lineRight + CURSOR_START_FLOAT_DISTANCE_PX;
+                    mMagnifierAnimator.mMagnifier.setDrawCursor(
+                            mDrawCursorOnMagnifier, mDrawableForCursor);
+                    boolean cursorVisible = mCursorVisible;
+                    // Updates cursor visibility, so that the real cursor and the float cursor on
+                    // magnifier surface won't appear at the same time.
+                    mCursorVisible = !mDrawCursorOnMagnifier;
+                    if (mCursorVisible && !cursorVisible) {
+                        // When the real cursor is a drawable, hiding/showing it would change its
+                        // bounds. So, call updateCursorPosition() to correct its position.
+                        updateCursorPosition();
+                    }
                     final int lineHeight =
                             layout.getLineBottomWithoutSpacing(line) - layout.getLineTop(line);
                     float zoom = mInitialZoom;
@@ -5230,6 +5263,11 @@
             if (mMagnifierAnimator != null) {
                 mMagnifierAnimator.dismiss();
                 mRenderCursorRegardlessTiming = false;
+                mDrawCursorOnMagnifier = false;
+                if (!mCursorVisible) {
+                    mCursorVisible = true;
+                    mTextView.invalidate();
+                }
                 resumeBlink();
                 setVisible(true);
                 final HandleView otherHandle = getOtherSelectionHandle();
diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java
index 89206fda..c72eed4 100644
--- a/core/java/android/widget/Magnifier.java
+++ b/core/java/android/widget/Magnifier.java
@@ -149,9 +149,6 @@
     private int mLeftCutWidth = 0;
     // The width of the cut region on the right edge of the pixel copy source rect.
     private int mRightCutWidth = 0;
-    // The horizontal bounds of the content source in pixels, relative to the view.
-    private int mLeftBound = Integer.MIN_VALUE;
-    private int mRightBound = Integer.MAX_VALUE;
     // The width of the ramp region in pixels on the left & right sides of the fish-eye effect.
     private final int mRamp;
 
@@ -244,18 +241,6 @@
     }
 
     /**
-     * Sets the horizontal bounds of the source when showing the magnifier.
-     * This is used for new style magnifier. e.g. limit the source bounds by the text line bounds.
-     *
-     * @param left the left of the bounds, relative to the view.
-     * @param right the right of the bounds, relative to the view.
-     */
-    void setSourceHorizontalBounds(int left, int right) {
-        mLeftBound = left;
-        mRightBound = right;
-    }
-
-    /**
      * Shows the magnifier on the screen. The method takes the coordinates of the center
      * of the content source going to be magnified and copied to the magnifier. The coordinates
      * are relative to the top left corner of the magnified view. The magnifier will be
@@ -280,6 +265,14 @@
                 sourceCenterY + mDefaultVerticalSourceToMagnifierOffset);
     }
 
+    private Drawable mCursorDrawable;
+    private boolean mDrawCursorEnabled;
+
+    void setDrawCursor(boolean enabled, Drawable cursorDrawable) {
+        mDrawCursorEnabled = enabled;
+        mCursorDrawable = cursorDrawable;
+    }
+
     /**
      * Shows the magnifier on the screen at a position that is independent from its content
      * position. The first two arguments represent the coordinates of the center of the
@@ -309,8 +302,7 @@
             magnifierCenterX = mClampedCenterZoomCoords.x - mViewCoordinatesInSurface[0];
             magnifierCenterY = mClampedCenterZoomCoords.y - mViewCoordinatesInSurface[1];
 
-            // mLeftBound & mRightBound (typically the text line left/right) is for magnified
-            // content. However the PixelCopy requires the pre-magnified bounds.
+            // PixelCopy requires the pre-magnified bounds.
             // The below logic calculates the leftBound & rightBound for the pre-magnified bounds.
             final float rampPre =
                     (mSourceWidth - (mSourceWidth - 2 * mRamp) / mZoom) / 2;
@@ -318,7 +310,7 @@
             // Calculates the pre-zoomed left edge.
             // The leftEdge moves from the left of view towards to sourceCenterX, considering the
             // fisheye-like zooming.
-            final float x0 = sourceCenterX - mSourceWidth / 2;
+            final float x0 = sourceCenterX - mSourceWidth / 2f;
             final float rampX0 = x0 + mRamp;
             float leftEdge = 0;
             if (leftEdge > rampX0) {
@@ -330,12 +322,12 @@
                 // increase per ramp zoom (ramp / rampPre).
                 leftEdge = x0 + rampPre - (rampX0 - leftEdge) * rampPre / mRamp;
             }
-            int leftBound = Math.min(Math.max((int) leftEdge, mLeftBound), mRightBound);
+            int leftBound = Math.min((int) leftEdge, mView.getWidth());
 
             // Calculates the pre-zoomed right edge.
             // The rightEdge moves from the right of view towards to sourceCenterX, considering the
             // fisheye-like zooming.
-            final float x1 = sourceCenterX + mSourceWidth / 2;
+            final float x1 = sourceCenterX + mSourceWidth / 2f;
             final float rampX1 = x1 - mRamp;
             float rightEdge = mView.getWidth();
             if (rightEdge < rampX1) {
@@ -347,7 +339,7 @@
                 // increase per ramp zoom (ramp / rampPre).
                 rightEdge = x1 - rampPre + (rightEdge - rampX1) * rampPre / mRamp;
             }
-            int rightBound = Math.max(leftBound, Math.min((int) rightEdge, mRightBound));
+            int rightBound = Math.max(leftBound, (int) rightEdge);
 
             // Gets the startX for new style, which should be bounded by the horizontal bounds.
             // Also calculates the left/right cut width for pixel copy.
@@ -772,6 +764,23 @@
         }
     }
 
+    private void maybeDrawCursor(Canvas canvas) {
+        if (mDrawCursorEnabled) {
+            if (mCursorDrawable != null) {
+                mCursorDrawable.setBounds(
+                        mSourceWidth / 2, 0,
+                        mSourceWidth / 2 + mCursorDrawable.getIntrinsicWidth(), mSourceHeight);
+                mCursorDrawable.draw(canvas);
+            } else {
+                Paint paint = new Paint();
+                paint.setColor(Color.BLACK);  // The cursor on magnifier is by default in black.
+                canvas.drawRect(
+                        new Rect(mSourceWidth / 2 - 1, 0, mSourceWidth / 2 + 1, mSourceHeight),
+                        paint);
+            }
+        }
+    }
+
     private void performPixelCopy(final int startXInSurface, final int startYInSurface,
             final boolean updateWindowPosition) {
         if (mContentCopySurface.mSurface == null || !mContentCopySurface.mSurface.isValid()) {
@@ -827,8 +836,10 @@
                             final Rect dstRect = new Rect(mLeftCutWidth, 0,
                                     mSourceWidth - mRightCutWidth, bitmap.getHeight());
                             can.drawBitmap(bitmap, null, dstRect, null);
+                            maybeDrawCursor(can);
                             mWindow.updateContent(newBitmap);
                         } else {
+                            maybeDrawCursor(new Canvas(bitmap));
                             mWindow.updateContent(bitmap);
                         }
                     }
diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java
index 201626a..f5bef0b 100644
--- a/core/java/com/android/internal/os/BinderCallsStats.java
+++ b/core/java/com/android/internal/os/BinderCallsStats.java
@@ -74,6 +74,10 @@
 
     // Whether to collect all the data: cpu + exceptions + reply/request sizes.
     private boolean mDetailedTracking = DETAILED_TRACKING_DEFAULT;
+    // If set to true, indicates that all transactions for specific UIDs are being
+    // recorded, ignoring sampling. The UidEntry.recordAllTransactions flag is also set
+    // for the UIDs being tracked.
+    private boolean mRecordingAllTransactionsForUid;
     // Sampling period to control how often to track CPU usage. 1 means all calls, 100 means ~1 out
     // of 100 requests.
     private int mPeriodicSamplingInterval = PERIODIC_SAMPLING_INTERVAL_DEFAULT;
@@ -178,7 +182,8 @@
     @Override
     @Nullable
     public CallSession callStarted(Binder binder, int code, int workSourceUid) {
-        if (mDeviceState == null || mDeviceState.isCharging()) {
+        if (!mRecordingAllTransactionsForUid
+                && (mDeviceState == null || mDeviceState.isCharging())) {
             return null;
         }
 
@@ -190,7 +195,9 @@
         s.exceptionThrown = false;
         s.cpuTimeStarted = -1;
         s.timeStarted = -1;
-        if (shouldRecordDetailedData()) {
+        s.recordedCall = shouldRecordDetailedData();
+
+        if (mRecordingAllTransactionsForUid || s.recordedCall) {
             s.cpuTimeStarted = getThreadTimeMicro();
             s.timeStarted = getElapsedRealtimeMicro();
         }
@@ -218,8 +225,17 @@
 
     private void processCallEnded(CallSession s,
             int parcelRequestSize, int parcelReplySize, int workSourceUid) {
-        // Non-negative time signals we need to record data for this call.
-        final boolean recordCall = s.cpuTimeStarted >= 0;
+        UidEntry uidEntry = null;
+        final boolean recordCall;
+        if (s.recordedCall) {
+            recordCall = true;
+        } else if (mRecordingAllTransactionsForUid) {
+            uidEntry = getUidEntry(workSourceUid);
+            recordCall = uidEntry.recordAllTransactions;
+        } else {
+            recordCall = false;
+        }
+
         final long duration;
         final long latencyDuration;
         if (recordCall) {
@@ -238,14 +254,17 @@
 
         synchronized (mLock) {
             // This was already checked in #callStart but check again while synchronized.
-            if (mDeviceState == null || mDeviceState.isCharging()) {
+            if (!mRecordingAllTransactionsForUid
+                    && (mDeviceState == null || mDeviceState.isCharging())) {
                 return;
             }
 
-            final UidEntry uidEntry = getUidEntry(workSourceUid);
+            if (uidEntry == null) {
+                uidEntry = getUidEntry(workSourceUid);
+            }
+
             uidEntry.callCount++;
             uidEntry.incrementalCallCount++;
-
             if (recordCall) {
                 uidEntry.cpuTimeMicros += duration;
                 uidEntry.recordedCallCount++;
@@ -357,28 +376,67 @@
             for (int entryIdx = 0; entryIdx < uidEntriesSize; entryIdx++) {
                 final UidEntry entry = mUidEntries.valueAt(entryIdx);
                 for (CallStat stat : entry.getCallStatsList()) {
-                    ExportedCallStat exported = new ExportedCallStat();
-                    exported.workSourceUid = entry.workSourceUid;
-                    exported.callingUid = stat.callingUid;
-                    exported.className = stat.binderClass.getName();
-                    exported.binderClass = stat.binderClass;
-                    exported.transactionCode = stat.transactionCode;
-                    exported.screenInteractive = stat.screenInteractive;
-                    exported.cpuTimeMicros = stat.cpuTimeMicros;
-                    exported.maxCpuTimeMicros = stat.maxCpuTimeMicros;
-                    exported.latencyMicros = stat.latencyMicros;
-                    exported.maxLatencyMicros = stat.maxLatencyMicros;
-                    exported.recordedCallCount = stat.recordedCallCount;
-                    exported.callCount = stat.callCount;
-                    exported.maxRequestSizeBytes = stat.maxRequestSizeBytes;
-                    exported.maxReplySizeBytes = stat.maxReplySizeBytes;
-                    exported.exceptionCount = stat.exceptionCount;
-                    resultCallStats.add(exported);
+                    resultCallStats.add(getExportedCallStat(entry.workSourceUid, stat));
                 }
             }
         }
 
         // Resolve codes outside of the lock since it can be slow.
+        resolveBinderMethodNames(resultCallStats);
+
+        // Debug entries added to help validate the data.
+        if (mAddDebugEntries && mBatteryStopwatch != null) {
+            resultCallStats.add(createDebugEntry("start_time_millis", mStartElapsedTime));
+            resultCallStats.add(createDebugEntry("end_time_millis", SystemClock.elapsedRealtime()));
+            resultCallStats.add(
+                    createDebugEntry("battery_time_millis", mBatteryStopwatch.getMillis()));
+            resultCallStats.add(createDebugEntry("sampling_interval", mPeriodicSamplingInterval));
+        }
+
+        return resultCallStats;
+    }
+
+    /**
+     * This method is expensive to call.
+     */
+    public ArrayList<ExportedCallStat> getExportedCallStats(int workSourceUid) {
+        ArrayList<ExportedCallStat> resultCallStats = new ArrayList<>();
+        synchronized (mLock) {
+            final UidEntry entry = getUidEntry(workSourceUid);
+            for (CallStat stat : entry.getCallStatsList()) {
+                resultCallStats.add(getExportedCallStat(workSourceUid, stat));
+            }
+        }
+
+        // Resolve codes outside of the lock since it can be slow.
+        resolveBinderMethodNames(resultCallStats);
+
+        return resultCallStats;
+    }
+
+    private ExportedCallStat getExportedCallStat(int workSourceUid, CallStat stat) {
+        ExportedCallStat exported = new ExportedCallStat();
+        exported.workSourceUid = workSourceUid;
+        exported.callingUid = stat.callingUid;
+        exported.className = stat.binderClass.getName();
+        exported.binderClass = stat.binderClass;
+        exported.transactionCode = stat.transactionCode;
+        exported.screenInteractive = stat.screenInteractive;
+        exported.cpuTimeMicros = stat.cpuTimeMicros;
+        exported.maxCpuTimeMicros = stat.maxCpuTimeMicros;
+        exported.latencyMicros = stat.latencyMicros;
+        exported.maxLatencyMicros = stat.maxLatencyMicros;
+        exported.recordedCallCount = stat.recordedCallCount;
+        exported.callCount = stat.callCount;
+        exported.maxRequestSizeBytes = stat.maxRequestSizeBytes;
+        exported.maxReplySizeBytes = stat.maxReplySizeBytes;
+        exported.exceptionCount = stat.exceptionCount;
+        return exported;
+    }
+
+    private void resolveBinderMethodNames(
+            ArrayList<ExportedCallStat> resultCallStats) {
+        // Resolve codes outside of the lock since it can be slow.
         ExportedCallStat previous = null;
         String previousMethodName = null;
         resultCallStats.sort(BinderCallsStats::compareByBinderClassAndCode);
@@ -398,17 +456,6 @@
             exported.methodName = methodName;
             previous = exported;
         }
-
-        // Debug entries added to help validate the data.
-        if (mAddDebugEntries && mBatteryStopwatch != null) {
-            resultCallStats.add(createDebugEntry("start_time_millis", mStartElapsedTime));
-            resultCallStats.add(createDebugEntry("end_time_millis", SystemClock.elapsedRealtime()));
-            resultCallStats.add(
-                    createDebugEntry("battery_time_millis", mBatteryStopwatch.getMillis()));
-            resultCallStats.add(createDebugEntry("sampling_interval", mPeriodicSamplingInterval));
-        }
-
-        return resultCallStats;
     }
 
     private ExportedCallStat createDebugEntry(String variableName, long value) {
@@ -432,33 +479,24 @@
     }
 
     /** Writes the collected statistics to the supplied {@link PrintWriter}.*/
-    public void dump(PrintWriter pw, AppIdToPackageMap packageMap, boolean verbose) {
+    public void dump(PrintWriter pw, AppIdToPackageMap packageMap, int workSourceUid,
+            boolean verbose) {
         synchronized (mLock) {
-            dumpLocked(pw, packageMap, verbose);
+            dumpLocked(pw, packageMap, workSourceUid, verbose);
         }
     }
 
-    private void dumpLocked(PrintWriter pw, AppIdToPackageMap packageMap, boolean verbose) {
-        long totalCallsCount = 0;
-        long totalRecordedCallsCount = 0;
-        long totalCpuTime = 0;
+    private void dumpLocked(PrintWriter pw, AppIdToPackageMap packageMap, int workSourceUid,
+            boolean verbose) {
+        if (workSourceUid != Process.INVALID_UID) {
+            verbose = true;
+        }
         pw.print("Start time: ");
         pw.println(DateFormat.format("yyyy-MM-dd HH:mm:ss", mStartCurrentTime));
         pw.print("On battery time (ms): ");
         pw.println(mBatteryStopwatch != null ? mBatteryStopwatch.getMillis() : 0);
         pw.println("Sampling interval period: " + mPeriodicSamplingInterval);
-        final List<UidEntry> entries = new ArrayList<>();
 
-        final int uidEntriesSize = mUidEntries.size();
-        for (int i = 0; i < uidEntriesSize; i++) {
-            UidEntry e = mUidEntries.valueAt(i);
-            entries.add(e);
-            totalCpuTime += e.cpuTimeMicros;
-            totalRecordedCallsCount += e.recordedCallCount;
-            totalCallsCount += e.callCount;
-        }
-
-        entries.sort(Comparator.<UidEntry>comparingDouble(value -> value.cpuTimeMicros).reversed());
         final String datasetSizeDesc = verbose ? "" : "(top 90% by cpu time) ";
         final StringBuilder sb = new StringBuilder();
         pw.println("Per-UID raw data " + datasetSizeDesc
@@ -467,10 +505,15 @@
                 + "latency_time_micros, max_latency_time_micros, exception_count, "
                 + "max_request_size_bytes, max_reply_size_bytes, recorded_call_count, "
                 + "call_count):");
-        final List<ExportedCallStat> exportedCallStats = getExportedCallStats();
+        final List<ExportedCallStat> exportedCallStats;
+        if (workSourceUid != Process.INVALID_UID) {
+            exportedCallStats = getExportedCallStats(workSourceUid);
+        } else {
+            exportedCallStats = getExportedCallStats();
+        }
         exportedCallStats.sort(BinderCallsStats::compareByCpuDesc);
         for (ExportedCallStat e : exportedCallStats) {
-            if (e.methodName.startsWith(DEBUG_ENTRY_PREFIX)) {
+            if (e.methodName != null && e.methodName.startsWith(DEBUG_ENTRY_PREFIX)) {
                 // Do not dump debug entries.
                 continue;
             }
@@ -494,6 +537,30 @@
             pw.println(sb);
         }
         pw.println();
+        final List<UidEntry> entries = new ArrayList<>();
+        long totalCallsCount = 0;
+        long totalRecordedCallsCount = 0;
+        long totalCpuTime = 0;
+
+        if (workSourceUid != Process.INVALID_UID) {
+            UidEntry e = getUidEntry(workSourceUid);
+            entries.add(e);
+            totalCpuTime += e.cpuTimeMicros;
+            totalRecordedCallsCount += e.recordedCallCount;
+            totalCallsCount += e.callCount;
+        } else {
+            final int uidEntriesSize = mUidEntries.size();
+            for (int i = 0; i < uidEntriesSize; i++) {
+                UidEntry e = mUidEntries.valueAt(i);
+                entries.add(e);
+                totalCpuTime += e.cpuTimeMicros;
+                totalRecordedCallsCount += e.recordedCallCount;
+                totalCallsCount += e.callCount;
+            }
+            entries.sort(
+                    Comparator.<UidEntry>comparingDouble(value -> value.cpuTimeMicros).reversed());
+        }
+
         pw.println("Per-UID Summary " + datasetSizeDesc
                 + "(cpu_time, % of total cpu_time, recorded_call_count, call_count, package/uid):");
         final List<UidEntry> summaryEntries = verbose ? entries
@@ -505,10 +572,13 @@
                     entry.recordedCallCount, entry.callCount, uidStr));
         }
         pw.println();
-        pw.println(String.format("  Summary: total_cpu_time=%d, "
-                        + "calls_count=%d, avg_call_cpu_time=%.0f",
-                totalCpuTime, totalCallsCount, (double) totalCpuTime / totalRecordedCallsCount));
-        pw.println();
+        if (workSourceUid == Process.INVALID_UID) {
+            pw.println(String.format("  Summary: total_cpu_time=%d, "
+                            + "calls_count=%d, avg_call_cpu_time=%.0f",
+                    totalCpuTime, totalCallsCount,
+                    (double) totalCpuTime / totalRecordedCallsCount));
+            pw.println();
+        }
 
         pw.println("Exceptions thrown (exception_count, class_name):");
         final List<Pair<String, Integer>> exceptionEntries = new ArrayList<>();
@@ -590,6 +660,22 @@
         }
     }
 
+    /**
+     * Marks the specified work source UID for total binder call tracking: detailed information
+     * will be recorded for all calls from this source ID.
+     *
+     * This is expensive and can cause memory pressure, therefore this mode should only be used
+     * for debugging.
+     */
+    public void recordAllCallsForWorkSourceUid(int workSourceUid) {
+        setDetailedTracking(true);
+
+        Slog.i(TAG, "Recording all Binder calls for UID: "  + workSourceUid);
+        UidEntry uidEntry = getUidEntry(workSourceUid);
+        uidEntry.recordAllTransactions = true;
+        mRecordingAllTransactionsForUid = true;
+    }
+
     public void setAddDebugEntries(boolean addDebugEntries) {
         mAddDebugEntries = addDebugEntries;
     }
@@ -637,6 +723,7 @@
             if (mBatteryStopwatch != null) {
                 mBatteryStopwatch.reset();
             }
+            mRecordingAllTransactionsForUid = false;
         }
     }
 
@@ -767,6 +854,8 @@
         public long cpuTimeMicros;
         // Call count that gets reset after delivery to BatteryStats
         public long incrementalCallCount;
+        // Indicates that all transactions for the UID must be tracked
+        public boolean recordAllTransactions;
 
         UidEntry(int uid) {
             this.workSourceUid = uid;
diff --git a/core/java/com/android/internal/os/BinderInternal.java b/core/java/com/android/internal/os/BinderInternal.java
index f14d5f2..2645b8e 100644
--- a/core/java/com/android/internal/os/BinderInternal.java
+++ b/core/java/com/android/internal/os/BinderInternal.java
@@ -85,9 +85,10 @@
         long timeStarted;
         // Should be set to one when an exception is thrown.
         boolean exceptionThrown;
+        // Detailed information should be recorded for this call when it ends.
+        public boolean recordedCall;
     }
 
-
     /**
      * Responsible for resolving a work source.
      */
diff --git a/services/core/java/com/android/server/wm/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java
similarity index 88%
rename from services/core/java/com/android/server/wm/ProtoLogGroup.java
rename to core/java/com/android/internal/protolog/ProtoLogGroup.java
index 51725ce..73d148c 100644
--- a/services/core/java/com/android/server/wm/ProtoLogGroup.java
+++ b/core/java/com/android/internal/protolog/ProtoLogGroup.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,11 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.server.wm;
+package com.android.internal.protolog;
 
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.protolog.common.IProtoLogGroup;
-import com.android.server.protolog.common.ProtoLog;
+import com.android.internal.protolog.common.IProtoLogGroup;
 
 /**
  * Defines logging groups for ProtoLog.
@@ -118,16 +116,6 @@
         this.mLogToLogcat = logToLogcat;
     }
 
-    /**
-     * Test function for automated integration tests. Can be also called manually from adb shell.
-     */
-    @VisibleForTesting
-    public static void testProtoLog() {
-        ProtoLog.e(ProtoLogGroup.TEST_GROUP,
-                "Test completed successfully: %b %d %o %x %e %g %f %% %s.",
-                true, 1, 2, 3, 0.4, 0.5, 0.6, "ok");
-    }
-
     private static class Consts {
         private static final String TAG_WM = "WindowManager";
 
diff --git a/services/core/java/com/android/server/protolog/ProtoLogImpl.java b/core/java/com/android/internal/protolog/ProtoLogImpl.java
similarity index 92%
rename from services/core/java/com/android/server/protolog/ProtoLogImpl.java
rename to core/java/com/android/internal/protolog/ProtoLogImpl.java
index c9d42c8..6874f10 100644
--- a/services/core/java/com/android/server/protolog/ProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/ProtoLogImpl.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,20 +14,20 @@
  * limitations under the License.
  */
 
-package com.android.server.protolog;
+package com.android.internal.protolog;
 
-import static com.android.server.protolog.ProtoLogFileProto.LOG;
-import static com.android.server.protolog.ProtoLogFileProto.MAGIC_NUMBER;
-import static com.android.server.protolog.ProtoLogFileProto.MAGIC_NUMBER_H;
-import static com.android.server.protolog.ProtoLogFileProto.MAGIC_NUMBER_L;
-import static com.android.server.protolog.ProtoLogFileProto.REAL_TIME_TO_ELAPSED_TIME_OFFSET_MILLIS;
-import static com.android.server.protolog.ProtoLogFileProto.VERSION;
-import static com.android.server.protolog.ProtoLogMessage.BOOLEAN_PARAMS;
-import static com.android.server.protolog.ProtoLogMessage.DOUBLE_PARAMS;
-import static com.android.server.protolog.ProtoLogMessage.ELAPSED_REALTIME_NANOS;
-import static com.android.server.protolog.ProtoLogMessage.MESSAGE_HASH;
-import static com.android.server.protolog.ProtoLogMessage.SINT64_PARAMS;
-import static com.android.server.protolog.ProtoLogMessage.STR_PARAMS;
+import static com.android.internal.protolog.ProtoLogFileProto.LOG;
+import static com.android.internal.protolog.ProtoLogFileProto.MAGIC_NUMBER;
+import static com.android.internal.protolog.ProtoLogFileProto.MAGIC_NUMBER_H;
+import static com.android.internal.protolog.ProtoLogFileProto.MAGIC_NUMBER_L;
+import static com.android.internal.protolog.ProtoLogFileProto.REAL_TIME_TO_ELAPSED_TIME_OFFSET_MILLIS;
+import static com.android.internal.protolog.ProtoLogFileProto.VERSION;
+import static com.android.internal.protolog.ProtoLogMessage.BOOLEAN_PARAMS;
+import static com.android.internal.protolog.ProtoLogMessage.DOUBLE_PARAMS;
+import static com.android.internal.protolog.ProtoLogMessage.ELAPSED_REALTIME_NANOS;
+import static com.android.internal.protolog.ProtoLogMessage.MESSAGE_HASH;
+import static com.android.internal.protolog.ProtoLogMessage.SINT64_PARAMS;
+import static com.android.internal.protolog.ProtoLogMessage.STR_PARAMS;
 
 import android.annotation.Nullable;
 import android.os.ShellCommand;
@@ -36,10 +36,9 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.protolog.common.IProtoLogGroup;
-import com.android.server.protolog.common.LogDataType;
+import com.android.internal.protolog.common.IProtoLogGroup;
+import com.android.internal.protolog.common.LogDataType;
 import com.android.internal.util.TraceBuffer;
-import com.android.server.wm.ProtoLogGroup;
 
 import java.io.File;
 import java.io.IOException;
@@ -62,7 +61,7 @@
      * Must be invoked after every action that could change the result of {@link #isEnabled}, eg.
      * starting / stopping proto log, or enabling / disabling log groups.
      */
-    static Runnable sCacheUpdater = () -> { };
+    public static Runnable sCacheUpdater = () -> { };
 
     private static void addLogGroupEnum(IProtoLogGroup[] config) {
         for (IProtoLogGroup group : config) {
@@ -289,9 +288,7 @@
         }
     }
 
-
-    @VisibleForTesting
-    ProtoLogImpl(File file, int bufferCapacity, ProtoLogViewerConfigReader viewerConfig) {
+    public ProtoLogImpl(File file, int bufferCapacity, ProtoLogViewerConfigReader viewerConfig) {
         mLogFile = file;
         mBuffer = new TraceBuffer(bufferCapacity);
         mViewerConfig = viewerConfig;
diff --git a/services/core/java/com/android/server/protolog/ProtoLogViewerConfigReader.java b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
similarity index 87%
rename from services/core/java/com/android/server/protolog/ProtoLogViewerConfigReader.java
rename to core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
index 4944217..e381d30 100644
--- a/services/core/java/com/android/server/protolog/ProtoLogViewerConfigReader.java
+++ b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.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,9 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.protolog;
-
-import static com.android.server.protolog.ProtoLogImpl.logAndPrintln;
+package com.android.internal.protolog;
 
 import org.json.JSONException;
 import org.json.JSONObject;
@@ -80,16 +78,17 @@
                     // Not a messageHash - skip it
                 }
             }
-            logAndPrintln(pw, "Loaded " + mLogMessageMap.size() + " log definitions from "
-                    + viewerConfigFilename);
+            ProtoLogImpl.logAndPrintln(pw, "Loaded " + mLogMessageMap.size()
+                    + " log definitions from " + viewerConfigFilename);
         } catch (FileNotFoundException e) {
-            logAndPrintln(pw, "Unable to load log definitions: File "
+            ProtoLogImpl.logAndPrintln(pw, "Unable to load log definitions: File "
                     + viewerConfigFilename + " not found." + e);
         } catch (IOException e) {
-            logAndPrintln(pw, "Unable to load log definitions: IOException while reading "
+            ProtoLogImpl.logAndPrintln(pw,
+                    "Unable to load log definitions: IOException while reading "
                     + viewerConfigFilename + ". " + e);
         } catch (JSONException e) {
-            logAndPrintln(pw,
+            ProtoLogImpl.logAndPrintln(pw,
                     "Unable to load log definitions: JSON parsing exception while reading "
                             + viewerConfigFilename + ". " + e);
         }
diff --git a/services/core/java/com/android/server/protolog/common/BitmaskConversionException.java b/core/java/com/android/internal/protolog/common/BitmaskConversionException.java
similarity index 88%
rename from services/core/java/com/android/server/protolog/common/BitmaskConversionException.java
rename to core/java/com/android/internal/protolog/common/BitmaskConversionException.java
index 7bb27b2..68b9d69 100644
--- a/services/core/java/com/android/server/protolog/common/BitmaskConversionException.java
+++ b/core/java/com/android/internal/protolog/common/BitmaskConversionException.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.server.protolog.common;
+package com.android.internal.protolog.common;
 
 /**
  * Error while converting a bitmask representing a list of LogDataTypes.
diff --git a/services/core/java/com/android/server/protolog/common/IProtoLogGroup.java b/core/java/com/android/internal/protolog/common/IProtoLogGroup.java
similarity index 93%
rename from services/core/java/com/android/server/protolog/common/IProtoLogGroup.java
rename to core/java/com/android/internal/protolog/common/IProtoLogGroup.java
index 2c65341..e3db468 100644
--- a/services/core/java/com/android/server/protolog/common/IProtoLogGroup.java
+++ b/core/java/com/android/internal/protolog/common/IProtoLogGroup.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.server.protolog.common;
+package com.android.internal.protolog.common;
 
 /**
  * Defines a log group configuration object for ProtoLog. Should be implemented as en enum.
diff --git a/services/core/java/com/android/server/protolog/common/InvalidFormatStringException.java b/core/java/com/android/internal/protolog/common/InvalidFormatStringException.java
similarity index 89%
rename from services/core/java/com/android/server/protolog/common/InvalidFormatStringException.java
rename to core/java/com/android/internal/protolog/common/InvalidFormatStringException.java
index 947bf98..97d3dfb 100644
--- a/services/core/java/com/android/server/protolog/common/InvalidFormatStringException.java
+++ b/core/java/com/android/internal/protolog/common/InvalidFormatStringException.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.server.protolog.common;
+package com.android.internal.protolog.common;
 
 /**
  * Unsupported/invalid message format string error.
diff --git a/services/core/java/com/android/server/protolog/common/LogDataType.java b/core/java/com/android/internal/protolog/common/LogDataType.java
similarity index 96%
rename from services/core/java/com/android/server/protolog/common/LogDataType.java
rename to core/java/com/android/internal/protolog/common/LogDataType.java
index e73b41a..651932a 100644
--- a/services/core/java/com/android/server/protolog/common/LogDataType.java
+++ b/core/java/com/android/internal/protolog/common/LogDataType.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.server.protolog.common;
+package com.android.internal.protolog.common;
 
 import java.util.ArrayList;
 import java.util.List;
diff --git a/services/core/java/com/android/server/protolog/common/ProtoLog.java b/core/java/com/android/internal/protolog/common/ProtoLog.java
similarity index 97%
rename from services/core/java/com/android/server/protolog/common/ProtoLog.java
rename to core/java/com/android/internal/protolog/common/ProtoLog.java
index b631bcb..ab58d35 100644
--- a/services/core/java/com/android/server/protolog/common/ProtoLog.java
+++ b/core/java/com/android/internal/protolog/common/ProtoLog.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.server.protolog.common;
+package com.android.internal.protolog.common;
 
 /**
  * ProtoLog API - exposes static logging methods. Usage of this API is similar
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/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java
index 9bf0513..a23fc4b 100644
--- a/core/java/com/android/internal/util/ScreenshotHelper.java
+++ b/core/java/com/android/internal/util/ScreenshotHelper.java
@@ -291,7 +291,7 @@
             };
 
             Message msg = Message.obtain(null, screenshotType, screenshotRequest);
-            final ServiceConnection myConn = mScreenshotConnection;
+
             Handler h = new Handler(handler.getLooper()) {
                 @Override
                 public void handleMessage(Message msg) {
@@ -304,8 +304,8 @@
                             break;
                         case SCREENSHOT_MSG_PROCESS_COMPLETE:
                             synchronized (mScreenshotLock) {
-                                if (myConn != null && mScreenshotConnection == myConn) {
-                                    mContext.unbindService(myConn);
+                                if (mScreenshotConnection != null) {
+                                    mContext.unbindService(mScreenshotConnection);
                                     mScreenshotConnection = null;
                                     mScreenshotService = null;
                                 }
@@ -368,6 +368,7 @@
                 }
             } else {
                 Messenger messenger = new Messenger(mScreenshotService);
+
                 try {
                     messenger.send(msg);
                 } catch (RemoteException e) {
diff --git a/core/java/com/android/internal/util/StatLogger.java b/core/java/com/android/internal/util/StatLogger.java
index 29568d5..2d65090 100644
--- a/core/java/com/android/internal/util/StatLogger.java
+++ b/core/java/com/android/internal/util/StatLogger.java
@@ -84,7 +84,7 @@
      * give it back to the {@link #logDurationStat(int, long)}} after the event.
      */
     public long getTime() {
-        return SystemClock.elapsedRealtimeNanos() / 1000;
+        return SystemClock.uptimeNanos() / 1000;
     }
 
     /**
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/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index e35fda1..d5d635d 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -47,8 +47,9 @@
     void resetKeyStore(int userId);
     VerifyCredentialResponse checkCredential(in LockscreenCredential credential, int userId,
             in ICheckCredentialProgressCallback progressCallback);
-    VerifyCredentialResponse verifyCredential(in LockscreenCredential credential, long challenge, int userId);
-    VerifyCredentialResponse verifyTiedProfileChallenge(in LockscreenCredential credential, long challenge, int userId);
+    VerifyCredentialResponse verifyCredential(in LockscreenCredential credential, int userId, int flags);
+    VerifyCredentialResponse verifyTiedProfileChallenge(in LockscreenCredential credential, int userId, int flags);
+    VerifyCredentialResponse verifyGatekeeperPassword(in byte[] gatekeeperPassword, long challenge, int userId);
     boolean checkVoldPassword(int userId);
     int getCredentialType(int userId);
     byte[] getHashFactor(in LockscreenCredential currentCredential, int userId);
diff --git a/core/java/com/android/internal/widget/LockPatternChecker.java b/core/java/com/android/internal/widget/LockPatternChecker.java
index 85a45fd..5adbc58 100644
--- a/core/java/com/android/internal/widget/LockPatternChecker.java
+++ b/core/java/com/android/internal/widget/LockPatternChecker.java
@@ -1,5 +1,6 @@
 package com.android.internal.widget;
 
+import android.annotation.NonNull;
 import android.os.AsyncTask;
 
 import com.android.internal.widget.LockPatternUtils.RequestThrottledException;
@@ -41,11 +42,11 @@
         /**
          * Invoked when a security verification is finished.
          *
-         * @param attestation The attestation that the challenge was verified, or null.
+         * @param response The response, optionally containing Gatekeeper HAT or Gatekeeper Password
          * @param throttleTimeoutMs The amount of time in ms to wait before reattempting
-         * the call. Only non-0 if attestation is null.
+         * the call. Only non-0 if the response is {@link VerifyCredentialResponse#RESPONSE_RETRY}.
          */
-        void onVerified(byte[] attestation, int throttleTimeoutMs);
+        void onVerified(@NonNull VerifyCredentialResponse response, int throttleTimeoutMs);
     }
 
     /**
@@ -53,33 +54,27 @@
      *
      * @param utils The LockPatternUtils instance to use.
      * @param credential The credential to check.
-     * @param challenge The challenge to verify against the credential.
      * @param userId The user to check against the credential.
+     * @param flags See {@link LockPatternUtils.VerifyFlag}
      * @param callback The callback to be invoked with the verification result.
      */
     public static AsyncTask<?, ?, ?> verifyCredential(final LockPatternUtils utils,
             final LockscreenCredential credential,
-            final long challenge,
             final int userId,
+            final @LockPatternUtils.VerifyFlag int flags,
             final OnVerifyCallback callback) {
         // Create a copy of the credential since checking credential is asynchrounous.
         final LockscreenCredential credentialCopy = credential.duplicate();
-        AsyncTask<Void, Void, byte[]> task = new AsyncTask<Void, Void, byte[]>() {
-            private int mThrottleTimeout;
-
+        AsyncTask<Void, Void, VerifyCredentialResponse> task =
+                new AsyncTask<Void, Void, VerifyCredentialResponse>() {
             @Override
-            protected byte[] doInBackground(Void... args) {
-                try {
-                    return utils.verifyCredential(credentialCopy, challenge, userId);
-                } catch (RequestThrottledException ex) {
-                    mThrottleTimeout = ex.getTimeoutMs();
-                    return null;
-                }
+            protected VerifyCredentialResponse doInBackground(Void... args) {
+                return utils.verifyCredential(credentialCopy, userId, flags);
             }
 
             @Override
-            protected void onPostExecute(byte[] result) {
-                callback.onVerified(result, mThrottleTimeout);
+            protected void onPostExecute(@NonNull VerifyCredentialResponse result) {
+                callback.onVerified(result, result.getTimeout());
                 credentialCopy.zeroize();
             }
 
@@ -141,33 +136,27 @@
      *
      * @param utils The LockPatternUtils instance to use.
      * @param credential The credential to check.
-     * @param challenge The challenge to verify against the credential.
      * @param userId The user to check against the credential.
+     * @param flags See {@link LockPatternUtils.VerifyFlag}
      * @param callback The callback to be invoked with the verification result.
      */
     public static AsyncTask<?, ?, ?> verifyTiedProfileChallenge(final LockPatternUtils utils,
             final LockscreenCredential credential,
-            final long challenge,
             final int userId,
+            final @LockPatternUtils.VerifyFlag int flags,
             final OnVerifyCallback callback) {
-        // Create a copy of the credential since checking credential is asynchrounous.
+        // Create a copy of the credential since checking credential is asynchronous.
         final LockscreenCredential credentialCopy = credential.duplicate();
-        AsyncTask<Void, Void, byte[]> task = new AsyncTask<Void, Void, byte[]>() {
-            private int mThrottleTimeout;
-
+        AsyncTask<Void, Void, VerifyCredentialResponse> task =
+                new AsyncTask<Void, Void, VerifyCredentialResponse>() {
             @Override
-            protected byte[] doInBackground(Void... args) {
-                try {
-                    return utils.verifyTiedProfileChallenge(credentialCopy, challenge, userId);
-                } catch (RequestThrottledException ex) {
-                    mThrottleTimeout = ex.getTimeoutMs();
-                    return null;
-                }
+            protected VerifyCredentialResponse doInBackground(Void... args) {
+                return utils.verifyTiedProfileChallenge(credentialCopy, userId, flags);
             }
 
             @Override
-            protected void onPostExecute(byte[] result) {
-                callback.onVerified(result, mThrottleTimeout);
+            protected void onPostExecute(@NonNull VerifyCredentialResponse response) {
+                callback.onVerified(response, response.getTimeout());
                 credentialCopy.zeroize();
             }
 
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 93690cd..f7370d6 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -130,6 +130,18 @@
     public @interface CredentialType {}
 
     /**
+     * Flag provided to {@link #verifyCredential(LockscreenCredential, long, int, int)} . If set,
+     * the method will return the Gatekeeper Password in the {@link VerifyCredentialResponse}.
+     */
+    public static final int VERIFY_FLAG_RETURN_GK_PW = 1 << 0;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, value = {
+            VERIFY_FLAG_RETURN_GK_PW
+    })
+    public @interface VerifyFlag {}
+
+    /**
      * Special user id for triggering the FRP verification flow.
      */
     public static final int USER_FRP = UserHandle.USER_NULL + 1;
@@ -374,29 +386,46 @@
      * If credential matches, return an opaque attestation that the challenge was verified.
      *
      * @param credential The credential to check.
-     * @param challenge The challenge to verify against the credential
      * @param userId The user whose credential is being verified
-     * @return the attestation that the challenge was verified, or null
-     * @throws RequestThrottledException if credential verification is being throttled due to
-     *         to many incorrect attempts.
+     * @param flags See {@link VerifyFlag}
      * @throws IllegalStateException if called on the main thread.
      */
-    public byte[] verifyCredential(@NonNull LockscreenCredential credential, long challenge,
-            int userId) throws RequestThrottledException {
+    @NonNull
+    public VerifyCredentialResponse verifyCredential(@NonNull LockscreenCredential credential,
+            int userId, @VerifyFlag int flags) {
         throwIfCalledOnMainThread();
         try {
-            VerifyCredentialResponse response = getLockSettings().verifyCredential(
-                    credential, challenge, userId);
-            if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
-                return response.getPayload();
-            } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) {
-                throw new RequestThrottledException(response.getTimeout());
+            final VerifyCredentialResponse response = getLockSettings().verifyCredential(
+                    credential, userId, flags);
+            if (response == null) {
+                return VerifyCredentialResponse.ERROR;
             } else {
-                return null;
+                return response;
             }
         } catch (RemoteException re) {
             Log.e(TAG, "failed to verify credential", re);
-            return null;
+            return VerifyCredentialResponse.ERROR;
+        }
+    }
+
+    /**
+     * With the Gatekeeper Password returned via {@link #verifyCredential(LockscreenCredential,
+     * int, int)}, request Gatekeeper to create a HardwareAuthToken wrapping the given
+     * challenge.
+     */
+    @NonNull
+    public VerifyCredentialResponse verifyGatekeeperPassword(@NonNull byte[] gatekeeperPassword,
+            long challenge, int userId) {
+        try {
+            final VerifyCredentialResponse response = getLockSettings().verifyGatekeeperPassword(
+                    gatekeeperPassword, challenge, userId);
+            if (response == null) {
+                return VerifyCredentialResponse.ERROR;
+            }
+            return response;
+        } catch (RemoteException e) {
+            Log.e(TAG, "failed to verify gatekeeper password", e);
+            return VerifyCredentialResponse.ERROR;
         }
     }
 
@@ -418,8 +447,9 @@
         try {
             VerifyCredentialResponse response = getLockSettings().checkCredential(
                     credential, userId, wrapCallback(progressCallback));
-
-            if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
+            if (response == null) {
+                return false;
+            } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
                 return true;
             } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) {
                 throw new RequestThrottledException(response.getTimeout());
@@ -439,30 +469,26 @@
      * verified.
      *
      * @param credential The parent user's credential to check.
-     * @param challenge The challenge to verify against the credential
      * @return the attestation that the challenge was verified, or null
      * @param userId The managed profile user id
-     * @throws RequestThrottledException if credential verification is being throttled due to
-     *         to many incorrect attempts.
+     * @param flags See {@link VerifyFlag}
      * @throws IllegalStateException if called on the main thread.
      */
-    public byte[] verifyTiedProfileChallenge(@NonNull LockscreenCredential credential,
-            long challenge, int userId) throws RequestThrottledException {
+    @NonNull
+    public VerifyCredentialResponse verifyTiedProfileChallenge(
+            @NonNull LockscreenCredential credential, int userId, @VerifyFlag int flags) {
         throwIfCalledOnMainThread();
         try {
-            VerifyCredentialResponse response =
-                    getLockSettings().verifyTiedProfileChallenge(credential, challenge, userId);
-
-            if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
-                return response.getPayload();
-            } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) {
-                throw new RequestThrottledException(response.getTimeout());
+            final VerifyCredentialResponse response = getLockSettings()
+                    .verifyTiedProfileChallenge(credential, userId, flags);
+            if (response == null) {
+                return VerifyCredentialResponse.ERROR;
             } else {
-                return null;
+                return response;
             }
         } catch (RemoteException re) {
             Log.e(TAG, "failed to verify tied profile credential", re);
-            return null;
+            return VerifyCredentialResponse.ERROR;
         }
     }
 
diff --git a/core/java/com/android/internal/widget/LockscreenCredential.java b/core/java/com/android/internal/widget/LockscreenCredential.java
index 55f30fb..a488449 100644
--- a/core/java/com/android/internal/widget/LockscreenCredential.java
+++ b/core/java/com/android/internal/widget/LockscreenCredential.java
@@ -48,7 +48,7 @@
  *     // Process the credential in some way
  * }
  * </pre>
- * With this construct, we can garantee that there will be no copies of the password left in
+ * With this construct, we can guarantee that there will be no copies of the password left in
  * memory when the credential goes out of scope. This should help mitigate certain class of
  * attacks where the attcker gains read-only access to full device memory (cold boot attack,
  * unsecured software/hardware memory dumping interfaces such as JTAG).
diff --git a/core/java/com/android/internal/widget/VerifyCredentialResponse.java b/core/java/com/android/internal/widget/VerifyCredentialResponse.java
index 7d1c706..e09eb42 100644
--- a/core/java/com/android/internal/widget/VerifyCredentialResponse.java
+++ b/core/java/com/android/internal/widget/VerifyCredentialResponse.java
@@ -16,11 +16,16 @@
 
 package com.android.internal.widget;
 
+import android.annotation.IntDef;
+import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.service.gatekeeper.GateKeeperResponse;
 import android.util.Slog;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Response object for a ILockSettings credential verification request.
  * @hide
@@ -30,78 +35,114 @@
     public static final int RESPONSE_ERROR = -1;
     public static final int RESPONSE_OK = 0;
     public static final int RESPONSE_RETRY = 1;
+    @IntDef({RESPONSE_ERROR,
+            RESPONSE_OK,
+            RESPONSE_RETRY})
+    @Retention(RetentionPolicy.SOURCE)
+    @interface ResponseCode {}
 
-    public static final VerifyCredentialResponse OK = new VerifyCredentialResponse();
-    public static final VerifyCredentialResponse ERROR
-            = new VerifyCredentialResponse(RESPONSE_ERROR, 0, null);
+    public static final VerifyCredentialResponse OK = new VerifyCredentialResponse.Builder()
+            .build();
+    public static final VerifyCredentialResponse ERROR = fromError();
     private static final String TAG = "VerifyCredentialResponse";
 
-    private int mResponseCode;
-    private byte[] mPayload;
-    private int mTimeout;
+    private final @ResponseCode int mResponseCode;
+    private final int mTimeout;
+    @Nullable private final byte[] mGatekeeperHAT;
+    @Nullable private final byte[] mGatekeeperPw;
 
     public static final Parcelable.Creator<VerifyCredentialResponse> CREATOR
             = new Parcelable.Creator<VerifyCredentialResponse>() {
         @Override
         public VerifyCredentialResponse createFromParcel(Parcel source) {
-            int responseCode = source.readInt();
-            VerifyCredentialResponse response = new VerifyCredentialResponse(responseCode, 0, null);
-            if (responseCode == RESPONSE_RETRY) {
-                response.setTimeout(source.readInt());
-            } else if (responseCode == RESPONSE_OK) {
-                int size = source.readInt();
-                if (size > 0) {
-                    byte[] payload = new byte[size];
-                    source.readByteArray(payload);
-                    response.setPayload(payload);
-                }
-            }
-            return response;
+            final @ResponseCode int responseCode = source.readInt();
+            final int timeout = source.readInt();
+            final byte[] gatekeeperHAT = source.createByteArray();
+            final byte[] gatekeeperPassword = source.createByteArray();
+
+            return new VerifyCredentialResponse(responseCode, timeout, gatekeeperHAT,
+                    gatekeeperPassword);
         }
 
         @Override
         public VerifyCredentialResponse[] newArray(int size) {
             return new VerifyCredentialResponse[size];
         }
-
     };
 
-    public VerifyCredentialResponse() {
-        mResponseCode = RESPONSE_OK;
-        mPayload = null;
+    public static class Builder {
+        @Nullable private byte[] mGatekeeperHAT;
+        @Nullable private byte[] mGatekeeperPassword;
+
+        /**
+         * @param gatekeeperHAT Gatekeeper HardwareAuthToken, minted upon successful authentication.
+         */
+        public Builder setGatekeeperHAT(byte[] gatekeeperHAT) {
+            mGatekeeperHAT = gatekeeperHAT;
+            return this;
+        }
+
+        public Builder setGatekeeperPassword(byte[] gatekeeperPassword) {
+            mGatekeeperPassword = gatekeeperPassword;
+            return this;
+        }
+
+        /**
+         * Builds a VerifyCredentialResponse with {@link #RESPONSE_OK} and any other parameters
+         * that were preveiously set.
+         * @return
+         */
+        public VerifyCredentialResponse build() {
+            return new VerifyCredentialResponse(RESPONSE_OK,
+                    0 /* timeout */,
+                    mGatekeeperHAT,
+                    mGatekeeperPassword);
+        }
     }
 
-
-    public VerifyCredentialResponse(byte[] payload) {
-        mPayload = payload;
-        mResponseCode = RESPONSE_OK;
+    /**
+     * Since timeouts are always an error, provide a way to create the VerifyCredentialResponse
+     * object directly. None of the other fields (Gatekeeper HAT, Gatekeeper Password, etc)
+     * are valid in this case. Similarly, the response code will always be
+     * {@link #RESPONSE_RETRY}.
+     */
+    public static VerifyCredentialResponse fromTimeout(int timeout) {
+        return new VerifyCredentialResponse(RESPONSE_RETRY,
+                timeout,
+                null /* gatekeeperHAT */,
+                null /* gatekeeperPassword */);
     }
 
-    public VerifyCredentialResponse(int timeout) {
-        mTimeout = timeout;
-        mResponseCode = RESPONSE_RETRY;
-        mPayload = null;
+    /**
+     * Since error (incorrect password) should never result in any of the other fields from
+     * being populated, provide a default method to return a VerifyCredentialResponse.
+     */
+    public static VerifyCredentialResponse fromError() {
+        return new VerifyCredentialResponse(RESPONSE_ERROR,
+                0 /* timeout */,
+                null /* gatekeeperHAT */,
+                null /* gatekeeperPassword */);
     }
 
-    private VerifyCredentialResponse(int responseCode, int timeout, byte[] payload) {
+    private VerifyCredentialResponse(@ResponseCode int responseCode, int timeout,
+            @Nullable byte[] gatekeeperHAT, @Nullable byte[] gatekeeperPassword) {
         mResponseCode = responseCode;
         mTimeout = timeout;
-        mPayload = payload;
+        mGatekeeperHAT = gatekeeperHAT;
+        mGatekeeperPw = gatekeeperPassword;
+    }
+
+    public VerifyCredentialResponse stripPayload() {
+        return new VerifyCredentialResponse(mResponseCode, mTimeout,
+                null /* gatekeeperHAT */, null /* gatekeeperPassword */);
     }
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(mResponseCode);
-        if (mResponseCode == RESPONSE_RETRY) {
-            dest.writeInt(mTimeout);
-        } else if (mResponseCode == RESPONSE_OK) {
-            if (mPayload != null) {
-                dest.writeInt(mPayload.length);
-                dest.writeByteArray(mPayload);
-            } else {
-                dest.writeInt(0);
-            }
-        }
+        dest.writeInt(mTimeout);
+        dest.writeByteArray(mGatekeeperHAT);
+        dest.writeByteArray(mGatekeeperPw);
     }
 
     @Override
@@ -109,48 +150,51 @@
         return 0;
     }
 
-    public byte[] getPayload() {
-        return mPayload;
+    @Nullable
+    public byte[] getGatekeeperHAT() {
+        return mGatekeeperHAT;
+    }
+
+    @Nullable
+    public byte[] getGatekeeperPw() {
+        return mGatekeeperPw;
     }
 
     public int getTimeout() {
         return mTimeout;
     }
 
-    public int getResponseCode() {
+    public @ResponseCode int getResponseCode() {
         return mResponseCode;
     }
 
-    private void setTimeout(int timeout) {
-        mTimeout = timeout;
+    public boolean isMatched() {
+        return mResponseCode == RESPONSE_OK;
     }
 
-    private void setPayload(byte[] payload) {
-        mPayload = payload;
-    }
-
-    public VerifyCredentialResponse stripPayload() {
-        return new VerifyCredentialResponse(mResponseCode, mTimeout, new byte[0]);
+    @Override
+    public String toString() {
+        return "Response: " + mResponseCode
+                + ", GK HAT: " + (mGatekeeperHAT != null)
+                + ", GK PW: " + (mGatekeeperPw != null);
     }
 
     public static VerifyCredentialResponse fromGateKeeperResponse(
             GateKeeperResponse gateKeeperResponse) {
-        VerifyCredentialResponse response;
         int responseCode = gateKeeperResponse.getResponseCode();
         if (responseCode == GateKeeperResponse.RESPONSE_RETRY) {
-            response = new VerifyCredentialResponse(gateKeeperResponse.getTimeout());
+            return fromTimeout(gateKeeperResponse.getTimeout());
         } else if (responseCode == GateKeeperResponse.RESPONSE_OK) {
             byte[] token = gateKeeperResponse.getPayload();
             if (token == null) {
                 // something's wrong if there's no payload with a challenge
                 Slog.e(TAG, "verifyChallenge response had no associated payload");
-                response = VerifyCredentialResponse.ERROR;
+                return fromError();
             } else {
-                response = new VerifyCredentialResponse(token);
+                return new VerifyCredentialResponse.Builder().setGatekeeperHAT(token).build();
             }
         } else {
-            response = VerifyCredentialResponse.ERROR;
+            return fromError();
         }
-        return response;
     }
 }
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/android_os_SystemClock.cpp b/core/jni/android_os_SystemClock.cpp
index b1f6000..2fba445 100644
--- a/core/jni/android_os_SystemClock.cpp
+++ b/core/jni/android_os_SystemClock.cpp
@@ -37,10 +37,12 @@
 static_assert(std::is_same<int64_t, jlong>::value, "jlong isn't an int64_t");
 static_assert(std::is_same<decltype(uptimeMillis()), int64_t>::value,
         "uptimeMillis signature change, expected int64_t return value");
+static_assert(std::is_same<decltype(uptimeNanos()), int64_t>::value,
+        "uptimeNanos signature change, expected int64_t return value");
 static_assert(std::is_same<decltype(elapsedRealtime()), int64_t>::value,
-        "uptimeMillis signature change, expected int64_t return value");
+        "elapsedRealtime signature change, expected int64_t return value");
 static_assert(std::is_same<decltype(elapsedRealtimeNano()), int64_t>::value,
-        "uptimeMillis signature change, expected int64_t return value");
+        "elapsedRealtimeNano signature change, expected int64_t return value");
 
 /*
  * native public static long currentThreadTimeMillis();
@@ -76,6 +78,7 @@
     // All of these are @CriticalNative, so we can defer directly to SystemClock.h for
     // some of these
     { "uptimeMillis", "()J", (void*) uptimeMillis },
+    { "uptimeNanos", "()J", (void*) uptimeNanos },
     { "elapsedRealtime", "()J", (void*) elapsedRealtime },
     { "elapsedRealtimeNanos", "()J", (void*) elapsedRealtimeNano },
 
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index d6a773f..85b4fe1 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -116,7 +116,6 @@
     jfieldID width;
     jfieldID height;
     jfieldID useIdentityTransform;
-    jfieldID rotation;
 } gDisplayCaptureArgsClassInfo;
 
 static struct {
@@ -325,8 +324,6 @@
     captureArgs.useIdentityTransform =
             env->GetBooleanField(displayCaptureArgsObject,
                                  gDisplayCaptureArgsClassInfo.useIdentityTransform);
-    captureArgs.rotation = ui::toRotation(
-            env->GetIntField(displayCaptureArgsObject, gDisplayCaptureArgsClassInfo.rotation));
     return captureArgs;
 }
 
@@ -1849,8 +1846,6 @@
             GetFieldIDOrDie(env, displayCaptureArgsClazz, "mHeight", "I");
     gDisplayCaptureArgsClassInfo.useIdentityTransform =
             GetFieldIDOrDie(env, displayCaptureArgsClazz, "mUseIdentityTransform", "Z");
-    gDisplayCaptureArgsClassInfo.rotation =
-            GetFieldIDOrDie(env, displayCaptureArgsClazz, "mRotation", "I");
 
     jclass layerCaptureArgsClazz =
             FindClassOrDie(env, "android/view/SurfaceControl$LayerCaptureArgs");
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index 1014fbb..0d65bd1 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;
 }
 
 /**
diff --git a/core/proto/android/app/tvsettings_enums.proto b/core/proto/android/app/tvsettings_enums.proto
index 31c5dd6..4a3c594 100644
--- a/core/proto/android/app/tvsettings_enums.proto
+++ b/core/proto/android/app/tvsettings_enums.proto
@@ -168,7 +168,11 @@
     // Google Assistant > Personal results (toggle)
     ACCOUNT_SLICE_REG_ACCOUNT_ASSISTANT_PERSONAL_RESULTS = 0x12134000;
 
-    // Reserving [0x12140000, 0x12190000] for possible future settings
+    // TvSettings > Account & Sign In (Slice) > [A regular account] >
+    // Apps only mode (toggle)
+    ACCOUNT_SLICE_REG_ACCOUNT_APPS_ONLY_MODE = 0x12140000;
+
+    // Reserving [0x12150000, 0x12190000] for possible future settings
 
     // TvSettings > Account & Sign In (Slice) > [A regular account] > Remove
     ACCOUNT_SLICE_REG_ACCOUNT_REMOVE = 0x121A0000;
diff --git a/core/proto/android/server/protolog.proto b/core/proto/android/internal/protolog.proto
similarity index 98%
rename from core/proto/android/server/protolog.proto
rename to core/proto/android/internal/protolog.proto
index 34dc55b..fee7a87 100644
--- a/core/proto/android/server/protolog.proto
+++ b/core/proto/android/internal/protolog.proto
@@ -16,7 +16,7 @@
 
 syntax = "proto2";
 
-package com.android.server.protolog;
+package com.android.internal.protolog;
 
 option java_multiple_files = true;
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index fe290f3..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" />
@@ -1342,7 +1343,7 @@
         android:priority="800" />
 
     <!-- Allows an application to access data from sensors that the user uses to
-         measure what is happening inside his/her body, such as heart rate.
+         measure what is happening inside their body, such as heart rate.
          <p>Protection level: dangerous -->
     <permission android:name="android.permission.BODY_SENSORS"
         android:permissionGroup="android.permission-group.UNDEFINED"
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-ca/strings.xml b/core/res/res/values-ca/strings.xml
index 2e4169e..4f55bf5 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -470,10 +470,10 @@
     <string name="permdesc_wakeLock" product="tablet" msgid="2441742939101526277">"Permet que l\'aplicació impedeixi que la tauleta entri en repòs."</string>
     <string name="permdesc_wakeLock" product="tv" msgid="2329298966735118796">"Permet que l\'aplicació impedeixi que el dispositiu Android TV entri en repòs."</string>
     <string name="permdesc_wakeLock" product="default" msgid="3689523792074007163">"Permet que l\'aplicació impedeixi que el telèfon entri en repòs."</string>
-    <string name="permlab_transmitIr" msgid="8077196086358004010">"transmissió d\'infraroigs"</string>
-    <string name="permdesc_transmitIr" product="tablet" msgid="5884738958581810253">"Permet que l\'aplicació utilitzi el transmissor d\'infraroigs de la tauleta."</string>
-    <string name="permdesc_transmitIr" product="tv" msgid="3278506969529173281">"Permet que l\'aplicació faci servir el transmissor d\'infraroigs del dispositiu Android TV."</string>
-    <string name="permdesc_transmitIr" product="default" msgid="8484193849295581808">"Permet que l\'aplicació utilitzi el transmissor d\'infraroigs del telèfon."</string>
+    <string name="permlab_transmitIr" msgid="8077196086358004010">"transmissió d\'infrarojos"</string>
+    <string name="permdesc_transmitIr" product="tablet" msgid="5884738958581810253">"Permet que l\'aplicació utilitzi el transmissor d\'infrarojos de la tauleta."</string>
+    <string name="permdesc_transmitIr" product="tv" msgid="3278506969529173281">"Permet que l\'aplicació faci servir el transmissor d\'infrarojos del dispositiu Android TV."</string>
+    <string name="permdesc_transmitIr" product="default" msgid="8484193849295581808">"Permet que l\'aplicació utilitzi el transmissor d\'infrarojos del telèfon."</string>
     <string name="permlab_setWallpaper" msgid="6959514622698794511">"establir fons de pantalla"</string>
     <string name="permdesc_setWallpaper" msgid="2973996714129021397">"Permet que l\'aplicació estableixi el fons de pantalla del sistema."</string>
     <string name="permlab_setWallpaperHints" msgid="1153485176642032714">"ajustament de la mida del fons de pantalla"</string>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index fd7b1b2..bb79c21 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -1633,7 +1633,7 @@
     <string name="accessibility_shortcut_menu_item_status_off" msgid="5531598275559472393">"निष्क्रिय"</string>
     <string name="accessibility_enable_service_title" msgid="3931558336268541484">"<xliff:g id="SERVICE">%1$s</xliff:g> लाई तपाईंको यन्त्र पूर्ण रूपमा नियन्त्रण गर्न दिने हो?"</string>
     <string name="accessibility_enable_service_encryption_warning" msgid="8603532708618236909">"तपाईंले <xliff:g id="SERVICE">%1$s</xliff:g> सक्रिय गर्नुभयो भने तपाईंको यन्त्रले डेटा इन्क्रिप्ट गर्ने सुविधाको स्तरोन्नति गर्न तपाईंको स्क्रिन लक सुविधाको प्रयोग गर्ने छैन।"</string>
-    <string name="accessibility_service_warning_description" msgid="291674995220940133">"तपाईंलाई पहुँच राख्न आवश्यक पर्ने कुरामा सहयोग गर्ने अनुप्रयोगहरूमाथि पूर्ण नियन्त्रण गर्नु उपयुक्त हुन्छ तर अधिकांश अनुप्रयोगहरूका हकमा यस्तो नियन्त्रण उपयुक्त हुँदैन।"</string>
+    <string name="accessibility_service_warning_description" msgid="291674995220940133">"तपाईंलाई पहुँच राख्न आवश्यक पर्ने कुरामा सहयोग गर्ने एपमाथि पूर्ण नियन्त्रण गर्नु उपयुक्त हुन्छ तर अधिकांश अनुप्रयोगहरूका हकमा यस्तो नियन्त्रण उपयुक्त हुँदैन।"</string>
     <string name="accessibility_service_screen_control_title" msgid="190017412626919776">"स्क्रिन हेर्नुहोस् र नियन्त्रण गर्नुहोस्"</string>
     <string name="accessibility_service_screen_control_description" msgid="6946315917771791525">"यसले स्क्रिनमा देखिने सबै सामग्री पढ्न सक्छ र अन्य एपहरूमा उक्त सामग्री देखाउन सक्छ।"</string>
     <string name="accessibility_service_action_perform_title" msgid="779670378951658160">"कारबाहीहरू हेर्नुहोस् र तिनमा कार्य गर्नुहोस्"</string>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 8913e8b..f99be88 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1823,6 +1823,8 @@
     <string name="config_defaultCallScreening" translatable="false"></string>
     <!-- The name of the package that will hold the system gallery role. -->
     <string name="config_systemGallery" translatable="false">com.android.gallery3d</string>
+    <!-- The name of the package that will hold the system cluster service role. -->
+    <string name="config_systemAutomotiveCluster" translatable="false"></string>
 
     <!-- The name of the package that will be allowed to change its components' label/icon. -->
     <string name="config_overrideComponentUiPackage" translatable="false"></string>
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/public.xml b/core/res/res/values/public.xml
index 303fde6..1e2d554 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3069,7 +3069,8 @@
   </public-group>
 
   <public-group type="string" first-id="0x01040028">
-    <!-- string definitions go here -->
+    <!-- @hide @SystemApi @TestApi -->
+    <public name="config_systemAutomotiveCluster" />
   </public-group>
 
   <public-group type="id" first-id="0x01020055">
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/android/app/WindowContextTest.java b/core/tests/coretests/src/android/app/WindowContextTest.java
index 630e16a..0f9bc4b 100644
--- a/core/tests/coretests/src/android/app/WindowContextTest.java
+++ b/core/tests/coretests/src/android/app/WindowContextTest.java
@@ -30,7 +30,6 @@
 import android.view.IWindowManager;
 import android.view.WindowManagerGlobal;
 
-import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
@@ -47,7 +46,6 @@
  * <p>This test class is a part of Window Manager Service tests and specified in
  * {@link com.android.server.wm.test.filters.FrameworksTestsFilter}.
  */
-@FlakyTest(bugId = 150812449, detail = "Remove after confirmed it's stable.")
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 @Presubmit
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index 000e870..896a534 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -25,14 +25,14 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
-import static org.testng.Assert.assertFalse;
 
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.ActivityThread;
+import android.app.ActivityThread.ActivityClientRecord;
 import android.app.IApplicationThread;
 import android.app.PictureInPictureParams;
 import android.app.ResourcesManager;
@@ -114,11 +114,12 @@
         final ActivityThread activityThread = activity.getActivityThread();
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             activityThread.executeTransaction(newResumeTransaction(activity));
-            assertNull(activityThread.performResumeActivity(activity.getActivityToken(),
-                    true /* finalStateRequest */, "test"));
+            final ActivityClientRecord r = getActivityClientRecord(activity);
+            assertFalse(activityThread.performResumeActivity(r, true /* finalStateRequest */,
+                    "test"));
 
-            assertNull(activityThread.performResumeActivity(activity.getActivityToken(),
-                    false /* finalStateRequest */, "test"));
+            assertFalse(activityThread.performResumeActivity(r, false /* finalStateRequest */,
+                    "test"));
         });
     }
 
@@ -244,20 +245,18 @@
             newerConfig.orientation = orientation == ORIENTATION_LANDSCAPE
                     ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
             newerConfig.seq = seq + 2;
-            activityThread.updatePendingActivityConfiguration(activity.getActivityToken(),
-                    newerConfig);
+            final ActivityClientRecord r = getActivityClientRecord(activity);
+            activityThread.updatePendingActivityConfiguration(r, newerConfig);
 
             final Configuration olderConfig = new Configuration();
             olderConfig.orientation = orientation;
             olderConfig.seq = seq + 1;
 
-            activityThread.handleActivityConfigurationChanged(activity.getActivityToken(),
-                    olderConfig, INVALID_DISPLAY);
+            activityThread.handleActivityConfigurationChanged(r, olderConfig, INVALID_DISPLAY);
             assertEquals(numOfConfig, activity.mNumOfConfigChanges);
             assertEquals(olderConfig.orientation, activity.mConfig.orientation);
 
-            activityThread.handleActivityConfigurationChanged(activity.getActivityToken(),
-                    newerConfig, INVALID_DISPLAY);
+            activityThread.handleActivityConfigurationChanged(r, newerConfig, INVALID_DISPLAY);
             assertEquals(numOfConfig + 1, activity.mNumOfConfigChanges);
             assertEquals(newerConfig.orientation, activity.mConfig.orientation);
         });
@@ -274,7 +273,7 @@
             config.seq = BASE_SEQ;
             config.orientation = ORIENTATION_PORTRAIT;
 
-            activityThread.handleActivityConfigurationChanged(activity.getActivityToken(),
+            activityThread.handleActivityConfigurationChanged(getActivityClientRecord(activity),
                     config, INVALID_DISPLAY);
         });
 
@@ -335,8 +334,8 @@
             config.seq = BASE_SEQ;
             config.orientation = ORIENTATION_PORTRAIT;
 
-            activityThread.handleActivityConfigurationChanged(activity.getActivityToken(),
-                    config, INVALID_DISPLAY);
+            final ActivityClientRecord r = getActivityClientRecord(activity);
+            activityThread.handleActivityConfigurationChanged(r, config, INVALID_DISPLAY);
         });
 
         final int numOfConfig = activity.mNumOfConfigChanges;
@@ -513,9 +512,10 @@
         startIntent.putExtra(TestActivity.PIP_REQUESTED_OVERRIDE_ENTER, true);
         final TestActivity activity = mActivityTestRule.launchActivity(startIntent);
         final ActivityThread activityThread = activity.getActivityThread();
+        final ActivityClientRecord r = getActivityClientRecord(activity);
 
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-            activityThread.handlePictureInPictureRequested(activity.getActivityToken());
+            activityThread.handlePictureInPictureRequested(r);
         });
 
         assertTrue(activity.pipRequested());
@@ -528,9 +528,10 @@
         startIntent.putExtra(TestActivity.PIP_REQUESTED_OVERRIDE_SKIP, true);
         final TestActivity activity = mActivityTestRule.launchActivity(startIntent);
         final ActivityThread activityThread = activity.getActivityThread();
+        final ActivityClientRecord r = getActivityClientRecord(activity);
 
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-            activityThread.handlePictureInPictureRequested(activity.getActivityToken());
+            activityThread.handlePictureInPictureRequested(r);
         });
 
         assertTrue(activity.pipRequested());
@@ -541,9 +542,10 @@
     public void testHandlePictureInPictureRequested_notOverridden() {
         final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
         final ActivityThread activityThread = activity.getActivityThread();
+        final ActivityClientRecord r = getActivityClientRecord(activity);
 
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-            activityThread.handlePictureInPictureRequested(activity.getActivityToken());
+            activityThread.handlePictureInPictureRequested(r);
         });
 
         assertTrue(activity.pipRequested());
@@ -552,8 +554,9 @@
     }
 
     /**
-     * Calls {@link ActivityThread#handleActivityConfigurationChanged(IBinder, Configuration, int)}
-     * to try to push activity configuration to the activity for the given sequence number.
+     * Calls {@link ActivityThread#handleActivityConfigurationChanged(ActivityClientRecord,
+     * Configuration, int)} to try to push activity configuration to the activity for the given
+     * sequence number.
      * <p>
      * It uses orientation to push the configuration and it tries a different orientation if the
      * first attempt doesn't make through, to rule out the possibility that the previous
@@ -566,13 +569,13 @@
      */
     private int applyConfigurationChange(TestActivity activity, int seq) {
         final ActivityThread activityThread = activity.getActivityThread();
+        final ActivityClientRecord r = getActivityClientRecord(activity);
 
         final int numOfConfig = activity.mNumOfConfigChanges;
         Configuration config = new Configuration();
         config.orientation = ORIENTATION_PORTRAIT;
         config.seq = seq;
-        activityThread.handleActivityConfigurationChanged(activity.getActivityToken(), config,
-                INVALID_DISPLAY);
+        activityThread.handleActivityConfigurationChanged(r, config, INVALID_DISPLAY);
 
         if (activity.mNumOfConfigChanges > numOfConfig) {
             return config.seq;
@@ -581,12 +584,17 @@
         config = new Configuration();
         config.orientation = ORIENTATION_LANDSCAPE;
         config.seq = seq + 1;
-        activityThread.handleActivityConfigurationChanged(activity.getActivityToken(), config,
-                INVALID_DISPLAY);
+        activityThread.handleActivityConfigurationChanged(r, config, INVALID_DISPLAY);
 
         return config.seq;
     }
 
+    private static ActivityClientRecord getActivityClientRecord(Activity activity) {
+        final ActivityThread thread = activity.getActivityThread();
+        final IBinder token = activity.getActivityToken();
+        return thread.getActivityClient(token);
+    }
+
     private static ClientTransaction newRelaunchResumeTransaction(Activity activity) {
         final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain(null,
                 null, 0, new MergedConfiguration(), false /* preserveWindow */);
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
index 3c32c71..6a0105c 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
@@ -32,11 +32,12 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.Activity;
 import android.app.ActivityThread.ActivityClientRecord;
 import android.app.ClientTransactionHandler;
 import android.app.servertransaction.ActivityLifecycleItem.LifecycleState;
@@ -225,6 +226,7 @@
         when(callback2.getPostExecutionState()).thenReturn(UNDEFINED);
         ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class);
         IBinder token = mock(IBinder.class);
+        when(mTransactionHandler.getActivity(token)).thenReturn(mock(Activity.class));
 
         ClientTransaction transaction = ClientTransaction.obtain(null /* client */,
                 token /* activityToken */);
@@ -236,9 +238,9 @@
         mExecutor.execute(transaction);
 
         InOrder inOrder = inOrder(mTransactionHandler, callback1, callback2, stateRequest);
-        inOrder.verify(callback1, times(1)).execute(eq(mTransactionHandler), eq(token), any());
-        inOrder.verify(callback2, times(1)).execute(eq(mTransactionHandler), eq(token), any());
-        inOrder.verify(stateRequest, times(1)).execute(eq(mTransactionHandler), eq(token), any());
+        inOrder.verify(callback1).execute(eq(mTransactionHandler), eq(token), any());
+        inOrder.verify(callback2).execute(eq(mTransactionHandler), eq(token), any());
+        inOrder.verify(stateRequest).execute(eq(mTransactionHandler), eq(mClientRecord), any());
     }
 
     @Test
@@ -273,7 +275,7 @@
 
         // The launch transaction should not be executed because its token is in the
         // to-be-destroyed container.
-        verify(launchItem, times(0)).execute(any(), any(), any());
+        verify(launchItem, never()).execute(any(), any(), any());
 
         // After the destroy transaction has been executed, the token should be removed.
         mExecutor.execute(destroyTransaction);
@@ -282,6 +284,8 @@
 
     @Test
     public void testActivityResultRequiredStateResolution() {
+        when(mTransactionHandler.getActivity(any())).thenReturn(mock(Activity.class));
+
         PostExecItem postExecItem = new PostExecItem(ON_RESUME);
 
         IBinder token = mock(IBinder.class);
@@ -292,12 +296,12 @@
         // Verify resolution that should get to onPause
         mClientRecord.setState(ON_RESUME);
         mExecutor.executeCallbacks(transaction);
-        verify(mExecutor, times(1)).cycleToPath(eq(mClientRecord), eq(ON_PAUSE), eq(transaction));
+        verify(mExecutor).cycleToPath(eq(mClientRecord), eq(ON_PAUSE), eq(transaction));
 
         // Verify resolution that should get to onStart
         mClientRecord.setState(ON_STOP);
         mExecutor.executeCallbacks(transaction);
-        verify(mExecutor, times(1)).cycleToPath(eq(mClientRecord), eq(ON_START), eq(transaction));
+        verify(mExecutor).cycleToPath(eq(mClientRecord), eq(ON_START), eq(transaction));
     }
 
     @Test
@@ -433,6 +437,38 @@
                 mExecutorHelper.getClosestPreExecutionState(mClientRecord, ON_RESUME));
     }
 
+    @Test(expected = IllegalArgumentException.class)
+    public void testActivityItemNullRecordThrowsException() {
+        final ActivityTransactionItem activityItem = mock(ActivityTransactionItem.class);
+        when(activityItem.getPostExecutionState()).thenReturn(UNDEFINED);
+        final IBinder token = mock(IBinder.class);
+        final ClientTransaction transaction = ClientTransaction.obtain(null /* client */,
+                token /* activityToken */);
+        transaction.addCallback(activityItem);
+        when(mTransactionHandler.getActivityClient(token)).thenReturn(null);
+
+        mExecutor.executeCallbacks(transaction);
+    }
+
+    @Test
+    public void testActivityItemExecute() {
+        final IBinder token = mock(IBinder.class);
+        final ClientTransaction transaction = ClientTransaction.obtain(null /* client */,
+                token /* activityToken */);
+        final ActivityTransactionItem activityItem = mock(ActivityTransactionItem.class);
+        when(activityItem.getPostExecutionState()).thenReturn(UNDEFINED);
+        transaction.addCallback(activityItem);
+        final ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class);
+        transaction.setLifecycleStateRequest(stateRequest);
+        when(mTransactionHandler.getActivity(token)).thenReturn(mock(Activity.class));
+
+        mExecutor.execute(transaction);
+
+        final InOrder inOrder = inOrder(activityItem, stateRequest);
+        inOrder.verify(activityItem).execute(eq(mTransactionHandler), eq(mClientRecord), any());
+        inOrder.verify(stateRequest).execute(eq(mTransactionHandler), eq(mClientRecord), any());
+    }
+
     private static int[] shuffledArray(int[] inputArray) {
         final List<Integer> list = Arrays.stream(inputArray).boxed().collect(Collectors.toList());
         Collections.shuffle(list);
@@ -489,13 +525,13 @@
 
         public static final Parcelable.Creator<StubItem> CREATOR =
                 new Parcelable.Creator<StubItem>() {
-                    public StubItem createFromParcel(Parcel in) {
-                        return new StubItem(in);
-                    }
+            public StubItem createFromParcel(Parcel in) {
+                return new StubItem(in);
+            }
 
-                    public StubItem[] newArray(int size) {
-                        return new StubItem[size];
-                    }
-                };
+            public StubItem[] newArray(int size) {
+                return new StubItem[size];
+            }
+        };
     }
 }
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index e0d702e..2bf9848 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -24,6 +24,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import android.app.ContentProviderHolder;
 import android.app.IApplicationThread;
 import android.app.IInstrumentationWatcher;
 import android.app.IUiAutomationConnection;
@@ -665,5 +666,10 @@
         public void performDirectAction(IBinder activityToken, String actionId, Bundle arguments,
                 RemoteCallback cancellationCallback, RemoteCallback resultCallback) {
         }
+
+        @Override
+        public void notifyContentProviderPublishStatus(ContentProviderHolder holder, String auth,
+                int userId, boolean published) {
+        }
     }
 }
diff --git a/core/tests/coretests/src/android/app/timezonedetector/TimeZoneCapabilitiesTest.java b/core/tests/coretests/src/android/app/timezonedetector/TimeZoneCapabilitiesTest.java
index 8a36f1d..72391f4 100644
--- a/core/tests/coretests/src/android/app/timezonedetector/TimeZoneCapabilitiesTest.java
+++ b/core/tests/coretests/src/android/app/timezonedetector/TimeZoneCapabilitiesTest.java
@@ -33,9 +33,11 @@
     public void testEquals() {
         TimeZoneCapabilities.Builder builder1 = new TimeZoneCapabilities.Builder(ARBITRARY_USER_ID)
                 .setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
+                .setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
                 .setSuggestManualTimeZone(CAPABILITY_POSSESSED);
         TimeZoneCapabilities.Builder builder2 = new TimeZoneCapabilities.Builder(ARBITRARY_USER_ID)
                 .setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
+                .setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
                 .setSuggestManualTimeZone(CAPABILITY_POSSESSED);
         {
             TimeZoneCapabilities one = builder1.build();
@@ -57,6 +59,20 @@
             assertEquals(one, two);
         }
 
+        builder2.setConfigureGeoDetectionEnabled(CAPABILITY_NOT_ALLOWED);
+        {
+            TimeZoneCapabilities one = builder1.build();
+            TimeZoneCapabilities two = builder2.build();
+            assertNotEquals(one, two);
+        }
+
+        builder1.setConfigureGeoDetectionEnabled(CAPABILITY_NOT_ALLOWED);
+        {
+            TimeZoneCapabilities one = builder1.build();
+            TimeZoneCapabilities two = builder2.build();
+            assertEquals(one, two);
+        }
+
         builder2.setSuggestManualTimeZone(CAPABILITY_NOT_ALLOWED);
         {
             TimeZoneCapabilities one = builder1.build();
@@ -76,12 +92,16 @@
     public void testParcelable() {
         TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder(ARBITRARY_USER_ID)
                 .setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
+                .setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
                 .setSuggestManualTimeZone(CAPABILITY_POSSESSED);
         assertRoundTripParcelable(builder.build());
 
         builder.setConfigureAutoDetectionEnabled(CAPABILITY_NOT_ALLOWED);
         assertRoundTripParcelable(builder.build());
 
+        builder.setConfigureGeoDetectionEnabled(CAPABILITY_NOT_ALLOWED);
+        assertRoundTripParcelable(builder.build());
+
         builder.setSuggestManualTimeZone(CAPABILITY_NOT_ALLOWED);
         assertRoundTripParcelable(builder.build());
     }
diff --git a/core/tests/coretests/src/android/app/timezonedetector/TimeZoneConfigurationTest.java b/core/tests/coretests/src/android/app/timezonedetector/TimeZoneConfigurationTest.java
index ac7e9c4..00dc73e 100644
--- a/core/tests/coretests/src/android/app/timezonedetector/TimeZoneConfigurationTest.java
+++ b/core/tests/coretests/src/android/app/timezonedetector/TimeZoneConfigurationTest.java
@@ -29,8 +29,9 @@
 
     @Test
     public void testBuilder_copyConstructor() {
-        TimeZoneConfiguration.Builder builder1 =
-                new TimeZoneConfiguration.Builder().setAutoDetectionEnabled(true);
+        TimeZoneConfiguration.Builder builder1 = new TimeZoneConfiguration.Builder()
+                .setAutoDetectionEnabled(true)
+                .setGeoDetectionEnabled(true);
         TimeZoneConfiguration configuration1 = builder1.build();
 
         TimeZoneConfiguration configuration2 =
@@ -41,16 +42,15 @@
 
     @Test
     public void testIsComplete() {
-        TimeZoneConfiguration incompleteConfiguration =
-                new TimeZoneConfiguration.Builder()
-                        .build();
-        assertFalse(incompleteConfiguration.isComplete());
+        TimeZoneConfiguration.Builder builder =
+                new TimeZoneConfiguration.Builder();
+        assertFalse(builder.build().isComplete());
 
-        TimeZoneConfiguration completeConfiguration =
-                new TimeZoneConfiguration.Builder()
-                        .setAutoDetectionEnabled(true)
-                        .build();
-        assertTrue(completeConfiguration.isComplete());
+        builder.setAutoDetectionEnabled(true);
+        assertFalse(builder.build().isComplete());
+
+        builder.setGeoDetectionEnabled(true);
+        assertTrue(builder.build().isComplete());
     }
 
     @Test
@@ -122,6 +122,27 @@
             TimeZoneConfiguration two = builder2.build();
             assertEquals(one, two);
         }
+
+        builder1.setGeoDetectionEnabled(true);
+        {
+            TimeZoneConfiguration one = builder1.build();
+            TimeZoneConfiguration two = builder2.build();
+            assertNotEquals(one, two);
+        }
+
+        builder2.setGeoDetectionEnabled(false);
+        {
+            TimeZoneConfiguration one = builder1.build();
+            TimeZoneConfiguration two = builder2.build();
+            assertNotEquals(one, two);
+        }
+
+        builder1.setGeoDetectionEnabled(false);
+        {
+            TimeZoneConfiguration one = builder1.build();
+            TimeZoneConfiguration two = builder2.build();
+            assertEquals(one, two);
+        }
     }
 
     @Test
@@ -135,5 +156,11 @@
 
         builder.setAutoDetectionEnabled(false);
         assertRoundTripParcelable(builder.build());
+
+        builder.setGeoDetectionEnabled(false);
+        assertRoundTripParcelable(builder.build());
+
+        builder.setGeoDetectionEnabled(true);
+        assertRoundTripParcelable(builder.build());
     }
 }
diff --git a/core/tests/coretests/src/android/view/WindowMetricsTest.java b/core/tests/coretests/src/android/view/WindowMetricsTest.java
index ddc977d..96df9dd 100644
--- a/core/tests/coretests/src/android/view/WindowMetricsTest.java
+++ b/core/tests/coretests/src/android/view/WindowMetricsTest.java
@@ -27,7 +27,6 @@
 import android.os.Handler;
 import android.platform.test.annotations.Presubmit;
 
-import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
@@ -46,7 +45,6 @@
  * <p>This test class is a part of Window Manager Service tests and specified in
  * {@link com.android.server.wm.test.filters.FrameworksTestsFilter}.
  */
-@FlakyTest(bugId = 148789183, detail = "Remove after confirmed it's stable.")
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 @Presubmit
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
index 188ba9e..96250db 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
@@ -20,6 +20,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import android.os.Binder;
 import android.os.Handler;
@@ -44,6 +45,7 @@
 import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Random;
@@ -493,7 +495,9 @@
         bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
 
         PrintWriter pw = new PrintWriter(new StringWriter());
-        bcs.dump(pw, new AppIdToPackageMap(new HashMap<>()), true);
+        bcs.dump(pw, new AppIdToPackageMap(new HashMap<>()), Process.INVALID_UID, true);
+
+        bcs.dump(pw, new AppIdToPackageMap(new HashMap<>()), WORKSOURCE_UID, true);
     }
 
     @Test
@@ -606,8 +610,8 @@
         assertEquals("-1", callStats.methodName);
         assertEquals("com.android.internal.os.BinderCallsStats$OverflowBinder",
                 callStats.className);
-        assertEquals(false , callStats.screenInteractive);
-        assertEquals(-1 , callStats.callingUid);
+        assertEquals(false, callStats.screenInteractive);
+        assertEquals(-1, callStats.callingUid);
     }
 
     @Test
@@ -785,7 +789,7 @@
         bcs.time += 30;
         bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
 
-        for (Runnable runnable: mHandler.mRunnables) {
+        for (Runnable runnable : mHandler.mRunnables) {
             // Execute all pending runnables. Ignore the delay.
             runnable.run();
         }
@@ -839,6 +843,53 @@
         assertEquals(3, tids[2]);
     }
 
+    @Test
+    public void testTrackingSpecificWorksourceUid() {
+        mDeviceState.setCharging(true);
+
+        Binder binder = new Binder();
+
+        TestBinderCallsStats bcs = new TestBinderCallsStats();
+        bcs.recordAllCallsForWorkSourceUid(WORKSOURCE_UID);
+
+        int[] transactions = {41, 42, 43, 42, 43, 43};
+        int[] durationsMs = {100, 200, 300, 400, 500, 600};
+
+        for (int i = 0; i < transactions.length; i++) {
+            CallSession callSession = bcs.callStarted(binder, transactions[i], WORKSOURCE_UID);
+            bcs.time += durationsMs[i];
+            bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
+        }
+
+        BinderCallsStats.UidEntry uidEntry = bcs.getUidEntries().get(WORKSOURCE_UID);
+        Assert.assertNotNull(uidEntry);
+        assertEquals(6, uidEntry.callCount);
+
+        Collection<BinderCallsStats.CallStat> callStatsList = uidEntry.getCallStatsList();
+        assertEquals(3, callStatsList.size());
+        for (BinderCallsStats.CallStat callStat : callStatsList) {
+            switch (callStat.transactionCode) {
+                case 41:
+                    assertEquals(1, callStat.callCount);
+                    assertEquals(1, callStat.incrementalCallCount);
+                    assertEquals(100, callStat.cpuTimeMicros);
+                    break;
+                case 42:
+                    assertEquals(2, callStat.callCount);
+                    assertEquals(2, callStat.incrementalCallCount);
+                    assertEquals(200 + 400, callStat.cpuTimeMicros);
+                    break;
+                case 43:
+                    assertEquals(3, callStat.callCount);
+                    assertEquals(3, callStat.incrementalCallCount);
+                    assertEquals(300 + 500 + 600, callStat.cpuTimeMicros);
+                    break;
+                default:
+                    fail("Unexpected transaction code: " + callStat.transactionCode);
+            }
+        }
+    }
+
     private static class TestHandler extends Handler {
         ArrayList<Runnable> mRunnables = new ArrayList<>();
 
diff --git a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
index 1cdc75a..3f2d0f1 100644
--- a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
+++ b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
@@ -176,27 +176,27 @@
         }
 
         private void startActivity(ActivityClientRecord r) {
-            mThread.handleStartActivity(r.token, null /* pendingActions */);
+            mThread.handleStartActivity(r, null /* pendingActions */);
         }
 
         private void resumeActivity(ActivityClientRecord r) {
-            mThread.handleResumeActivity(r.token, true /* finalStateRequest */,
+            mThread.handleResumeActivity(r, true /* finalStateRequest */,
                     true /* isForward */, "test");
         }
 
         private void pauseActivity(ActivityClientRecord r) {
-            mThread.handlePauseActivity(r.token, false /* finished */,
+            mThread.handlePauseActivity(r, false /* finished */,
                     false /* userLeaving */, 0 /* configChanges */, null /* pendingActions */,
                     "test");
         }
 
         private void stopActivity(ActivityClientRecord r) {
-            mThread.handleStopActivity(r.token, 0 /* configChanges */,
+            mThread.handleStopActivity(r, 0 /* configChanges */,
                     new PendingTransactionActions(), false /* finalStateRequest */, "test");
         }
 
         private void destroyActivity(ActivityClientRecord r) {
-            mThread.handleDestroyActivity(r.token, true /* finishing */, 0 /* configChanges */,
+            mThread.handleDestroyActivity(r, true /* finishing */, 0 /* configChanges */,
                     false /* getNonConfigInstance */, "test");
         }
 
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/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/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/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java
index 6e3fb19..d4fb1be 100644
--- a/media/java/android/media/AudioDeviceInfo.java
+++ b/media/java/android/media/AudioDeviceInfo.java
@@ -147,6 +147,19 @@
     // {@link android.media.audiopolicy.AudioMix#ROUTE_FLAG_LOOP_BACK} flag.
     public static final int TYPE_REMOTE_SUBMIX = 25;
 
+    /**
+     * A device type describing a Bluetooth Low Energy (BLE) audio headset or headphones.
+     * Headphones are grouped with headsets when the device is a sink:
+     * the features of headsets and headphones with regard to playback are the same.
+     */
+    public static final int TYPE_BLE_HEADSET   = 26;
+
+    /**
+     * A device type describing a Bluetooth Low Energy (BLE) audio speaker.
+     */
+    public static final int TYPE_BLE_SPEAKER   = 27;
+
+
     /** @hide */
     @IntDef(flag = false, prefix = "TYPE", value = {
             TYPE_BUILTIN_EARPIECE,
@@ -171,7 +184,9 @@
             TYPE_HEARING_AID,
             TYPE_BUILTIN_MIC,
             TYPE_FM_TUNER,
-            TYPE_TV_TUNER }
+            TYPE_TV_TUNER,
+            TYPE_BLE_HEADSET,
+            TYPE_BLE_SPEAKER}
     )
     @Retention(RetentionPolicy.SOURCE)
     public @interface AudioDeviceType {}
@@ -193,7 +208,8 @@
             TYPE_LINE_ANALOG,
             TYPE_LINE_DIGITAL,
             TYPE_IP,
-            TYPE_BUS }
+            TYPE_BUS,
+            TYPE_BLE_HEADSET}
     )
     @Retention(RetentionPolicy.SOURCE)
     public @interface AudioDeviceTypeIn {}
@@ -219,7 +235,9 @@
             TYPE_AUX_LINE,
             TYPE_IP,
             TYPE_BUS,
-            TYPE_HEARING_AID }
+            TYPE_HEARING_AID,
+            TYPE_BLE_HEADSET,
+            TYPE_BLE_SPEAKER}
     )
     @Retention(RetentionPolicy.SOURCE)
     public @interface AudioDeviceTypeOut {}
@@ -248,7 +266,8 @@
             case TYPE_BUS:
             case TYPE_HEARING_AID:
             case TYPE_BUILTIN_SPEAKER_SAFE:
-            case TYPE_REMOTE_SUBMIX:
+            case TYPE_BLE_HEADSET:
+            case TYPE_BLE_SPEAKER:
                 return true;
             default:
                 return false;
@@ -275,6 +294,7 @@
             case TYPE_IP:
             case TYPE_BUS:
             case TYPE_REMOTE_SUBMIX:
+            case TYPE_BLE_HEADSET:
                 return true;
             default:
                 return false;
@@ -527,6 +547,8 @@
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_SPEAKER_SAFE,
                 TYPE_BUILTIN_SPEAKER_SAFE);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_REMOTE_SUBMIX, TYPE_REMOTE_SUBMIX);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_BLE_HEADSET, TYPE_BLE_HEADSET);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_BLE_SPEAKER, TYPE_BLE_SPEAKER);
 
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BUILTIN_MIC, TYPE_BUILTIN_MIC);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET, TYPE_BLUETOOTH_SCO);
@@ -547,6 +569,7 @@
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_IP, TYPE_IP);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BUS, TYPE_BUS);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_REMOTE_SUBMIX, TYPE_REMOTE_SUBMIX);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BLE_HEADSET, TYPE_BLE_HEADSET);
 
         // privileges mapping to output device
         EXT_TO_INT_DEVICE_MAPPING = new SparseIntArray();
@@ -576,6 +599,8 @@
         EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BUILTIN_SPEAKER_SAFE,
                 AudioSystem.DEVICE_OUT_SPEAKER_SAFE);
         EXT_TO_INT_DEVICE_MAPPING.put(TYPE_REMOTE_SUBMIX, AudioSystem.DEVICE_OUT_REMOTE_SUBMIX);
+        EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BLE_HEADSET, AudioSystem.DEVICE_OUT_BLE_HEADSET);
+        EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BLE_SPEAKER, AudioSystem.DEVICE_OUT_BLE_SPEAKER);
     }
 }
 
diff --git a/media/java/android/media/AudioDevicePort.java b/media/java/android/media/AudioDevicePort.java
index 42d0f0c..f6b0454 100644
--- a/media/java/android/media/AudioDevicePort.java
+++ b/media/java/android/media/AudioDevicePort.java
@@ -70,7 +70,9 @@
      * {@link AudioManager#DEVICE_IN_USB_DEVICE}) use an address composed of the ALSA card number
      * and device number: "card=2;device=1"
      * - Bluetooth devices ({@link AudioManager#DEVICE_OUT_BLUETOOTH_SCO},
-     * {@link AudioManager#DEVICE_OUT_BLUETOOTH_SCO}, {@link AudioManager#DEVICE_OUT_BLUETOOTH_A2DP})
+     * {@link AudioManager#DEVICE_OUT_BLUETOOTH_SCO},
+     * {@link AudioManager#DEVICE_OUT_BLUETOOTH_A2DP}),
+     * {@link AudioManager#DEVICE_OUT_BLE_HEADSET}, {@link AudioManager#DEVICE_OUT_BLE_SPEAKER})
      * use the MAC address of the bluetooth device in the form "00:11:22:AA:BB:CC" as reported by
      * {@link BluetoothDevice#getAddress()}.
      * - Deivces that do not have an address will indicate an empty string "".
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 408f34b..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);
@@ -4413,6 +4530,18 @@
      */
     public static final int DEVICE_OUT_FM = AudioSystem.DEVICE_OUT_FM;
     /** @hide
+     * The audio output device code for echo reference injection point.
+     */
+    public static final int DEVICE_OUT_ECHO_CANCELLER = AudioSystem.DEVICE_OUT_ECHO_CANCELLER;
+    /** @hide
+     * The audio output device code for a BLE audio headset.
+     */
+    public static final int DEVICE_OUT_BLE_HEADSET = AudioSystem.DEVICE_OUT_BLE_HEADSET;
+    /** @hide
+     * The audio output device code for a BLE audio speaker.
+     */
+    public static final int DEVICE_OUT_BLE_SPEAKER = AudioSystem.DEVICE_OUT_BLE_SPEAKER;
+    /** @hide
      * This is not used as a returned value from {@link #getDevicesForStream}, but could be
      *  used in the future in a set method to select whatever default device is chosen by the
      *  platform-specific implementation.
@@ -4496,6 +4625,14 @@
      * The audio input device code for audio loopback
      */
     public static final int DEVICE_IN_LOOPBACK = AudioSystem.DEVICE_IN_LOOPBACK;
+    /** @hide
+     * The audio input device code for an echo reference capture point.
+     */
+    public static final int DEVICE_IN_ECHO_REFERENCE = AudioSystem.DEVICE_IN_ECHO_REFERENCE;
+    /** @hide
+     * The audio input device code for a BLE audio headset.
+     */
+    public static final int DEVICE_IN_BLE_HEADSET = AudioSystem.DEVICE_IN_BLE_HEADSET;
 
     /**
      * Return true if the device code corresponds to an output device.
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index da52cfe..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;
@@ -866,6 +867,12 @@
     public static final int DEVICE_OUT_USB_HEADSET = 0x4000000;
     /** @hide */
     public static final int DEVICE_OUT_HEARING_AID = 0x8000000;
+    /** @hide */
+    public static final int DEVICE_OUT_ECHO_CANCELLER = 0x10000000;
+    /** @hide */
+    public static final int DEVICE_OUT_BLE_HEADSET = 0x20000000;
+    /** @hide */
+    public static final int DEVICE_OUT_BLE_SPEAKER = 0x20000001;
 
     /** @hide */
     public static final int DEVICE_OUT_DEFAULT = DEVICE_BIT_DEFAULT;
@@ -890,6 +897,8 @@
     public static final Set<Integer> DEVICE_OUT_ALL_HDMI_SYSTEM_AUDIO_SET;
     /** @hide */
     public static final Set<Integer> DEVICE_ALL_HDMI_SYSTEM_AUDIO_AND_SPEAKER_SET;
+    /** @hide */
+    public static final Set<Integer> DEVICE_OUT_ALL_BLE_SET;
     static {
         DEVICE_OUT_ALL_SET = new HashSet<>();
         DEVICE_OUT_ALL_SET.add(DEVICE_OUT_EARPIECE);
@@ -920,6 +929,9 @@
         DEVICE_OUT_ALL_SET.add(DEVICE_OUT_PROXY);
         DEVICE_OUT_ALL_SET.add(DEVICE_OUT_USB_HEADSET);
         DEVICE_OUT_ALL_SET.add(DEVICE_OUT_HEARING_AID);
+        DEVICE_OUT_ALL_SET.add(DEVICE_OUT_ECHO_CANCELLER);
+        DEVICE_OUT_ALL_SET.add(DEVICE_OUT_BLE_HEADSET);
+        DEVICE_OUT_ALL_SET.add(DEVICE_OUT_BLE_SPEAKER);
         DEVICE_OUT_ALL_SET.add(DEVICE_OUT_DEFAULT);
 
         DEVICE_OUT_ALL_A2DP_SET = new HashSet<>();
@@ -945,6 +957,10 @@
         DEVICE_ALL_HDMI_SYSTEM_AUDIO_AND_SPEAKER_SET = new HashSet<>();
         DEVICE_ALL_HDMI_SYSTEM_AUDIO_AND_SPEAKER_SET.addAll(DEVICE_OUT_ALL_HDMI_SYSTEM_AUDIO_SET);
         DEVICE_ALL_HDMI_SYSTEM_AUDIO_AND_SPEAKER_SET.add(DEVICE_OUT_SPEAKER);
+
+        DEVICE_OUT_ALL_BLE_SET = new HashSet<>();
+        DEVICE_OUT_ALL_BLE_SET.add(DEVICE_OUT_BLE_HEADSET);
+        DEVICE_OUT_ALL_BLE_SET.add(DEVICE_OUT_BLE_SPEAKER);
     }
 
     // input devices
@@ -1019,6 +1035,8 @@
     /** @hide */
     public static final int DEVICE_IN_ECHO_REFERENCE = DEVICE_BIT_IN | 0x10000000;
     /** @hide */
+    public static final int DEVICE_IN_BLE_HEADSET = DEVICE_BIT_IN | 0x20000000;
+    /** @hide */
     @UnsupportedAppUsage
     public static final int DEVICE_IN_DEFAULT = DEVICE_BIT_IN | DEVICE_BIT_DEFAULT;
 
@@ -1056,6 +1074,7 @@
         DEVICE_IN_ALL_SET.add(DEVICE_IN_BLUETOOTH_BLE);
         DEVICE_IN_ALL_SET.add(DEVICE_IN_HDMI_ARC);
         DEVICE_IN_ALL_SET.add(DEVICE_IN_ECHO_REFERENCE);
+        DEVICE_IN_ALL_SET.add(DEVICE_IN_BLE_HEADSET);
         DEVICE_IN_ALL_SET.add(DEVICE_IN_DEFAULT);
 
         DEVICE_IN_ALL_SCO_SET = new HashSet<>();
@@ -1118,6 +1137,9 @@
     /** @hide */ public static final String DEVICE_OUT_PROXY_NAME = "proxy";
     /** @hide */ public static final String DEVICE_OUT_USB_HEADSET_NAME = "usb_headset";
     /** @hide */ public static final String DEVICE_OUT_HEARING_AID_NAME = "hearing_aid_out";
+    /** @hide */ public static final String DEVICE_OUT_ECHO_CANCELLER_NAME = "echo_canceller";
+    /** @hide */ public static final String DEVICE_OUT_BLE_HEADSET_NAME = "ble_headset";
+    /** @hide */ public static final String DEVICE_OUT_BLE_SPEAKER_NAME = "ble_speaker";
 
     /** @hide */ public static final String DEVICE_IN_COMMUNICATION_NAME = "communication";
     /** @hide */ public static final String DEVICE_IN_AMBIENT_NAME = "ambient";
@@ -1145,6 +1167,7 @@
     /** @hide */ public static final String DEVICE_IN_BLUETOOTH_BLE_NAME = "bt_ble";
     /** @hide */ public static final String DEVICE_IN_ECHO_REFERENCE_NAME = "echo_reference";
     /** @hide */ public static final String DEVICE_IN_HDMI_ARC_NAME = "hdmi_arc";
+    /** @hide */ public static final String DEVICE_IN_BLE_HEADSET_NAME = "ble_headset";
 
     /** @hide */
     @UnsupportedAppUsage
@@ -1207,6 +1230,12 @@
             return DEVICE_OUT_USB_HEADSET_NAME;
         case DEVICE_OUT_HEARING_AID:
             return DEVICE_OUT_HEARING_AID_NAME;
+        case DEVICE_OUT_ECHO_CANCELLER:
+            return DEVICE_OUT_ECHO_CANCELLER_NAME;
+        case DEVICE_OUT_BLE_HEADSET:
+            return DEVICE_OUT_BLE_HEADSET_NAME;
+        case DEVICE_OUT_BLE_SPEAKER:
+            return DEVICE_OUT_BLE_SPEAKER_NAME;
         case DEVICE_OUT_DEFAULT:
         default:
             return Integer.toString(device);
@@ -1269,6 +1298,8 @@
             return DEVICE_IN_ECHO_REFERENCE_NAME;
         case DEVICE_IN_HDMI_ARC:
             return DEVICE_IN_HDMI_ARC_NAME;
+        case DEVICE_IN_BLE_HEADSET:
+            return DEVICE_IN_BLE_HEADSET_NAME;
         case DEVICE_IN_DEFAULT:
         default:
             return Integer.toString(device);
@@ -1347,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) {
@@ -1667,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/MediaTranscodeManager.java b/media/java/android/media/MediaTranscodeManager.java
index e959e8e..e1bff03 100644
--- a/media/java/android/media/MediaTranscodeManager.java
+++ b/media/java/android/media/MediaTranscodeManager.java
@@ -225,13 +225,50 @@
             job.updateStatusAndResult(TranscodingJob.STATUS_FINISHED,
                     TranscodingJob.RESULT_ERROR);
 
-            // Notifies client the job is done.
+            // Notifies client the job failed.
             if (job.mListener != null && job.mListenerExecutor != null) {
                 job.mListenerExecutor.execute(() -> job.mListener.onTranscodingFinished(job));
             }
         }
     }
 
+    private void handleTranscodingProgressUpdate(int jobId, int newProgress) {
+        synchronized (mPendingTranscodingJobs) {
+            // Gets the job associated with the jobId.
+            final TranscodingJob job = mPendingTranscodingJobs.get(jobId);
+
+            if (job == null) {
+                // This should not happen in reality.
+                Log.e(TAG, "Job " + jobId + " is not in PendingJobs");
+                return;
+            }
+
+            // Updates the job progress.
+            job.updateProgress(newProgress);
+
+            // Notifies client the progress update.
+            if (job.mProgressUpdateExecutor != null && job.mProgressUpdateListener != null) {
+                job.mProgressUpdateExecutor.execute(
+                        () -> job.mProgressUpdateListener.onProgressUpdate(newProgress));
+            }
+        }
+    }
+
+    private void updateStatus(int jobId, int status) {
+        synchronized (mPendingTranscodingJobs) {
+            final TranscodingJob job = mPendingTranscodingJobs.get(jobId);
+
+            if (job == null) {
+                // This should not happen in reality.
+                Log.e(TAG, "Job " + jobId + " is not in PendingJobs");
+                return;
+            }
+
+            // Updates the job status.
+            job.updateStatus(status);
+        }
+    }
+
     // Just forwards all the events to the event handler.
     private ITranscodingClientCallback mTranscodingClientCallback =
             new ITranscodingClientCallback.Stub() {
@@ -263,17 +300,17 @@
 
                 @Override
                 public void onTranscodingStarted(int jobId) throws RemoteException {
-
+                    updateStatus(jobId, TranscodingJob.STATUS_RUNNING);
                 }
 
                 @Override
                 public void onTranscodingPaused(int jobId) throws RemoteException {
-
+                    updateStatus(jobId, TranscodingJob.STATUS_PAUSED);
                 }
 
                 @Override
                 public void onTranscodingResumed(int jobId) throws RemoteException {
-
+                    updateStatus(jobId, TranscodingJob.STATUS_RUNNING);
                 }
 
                 @Override
@@ -294,8 +331,8 @@
                 }
 
                 @Override
-                public void onProgressUpdate(int jobId, int progress) throws RemoteException {
-                    //TODO(hkuang): Implement this.
+                public void onProgressUpdate(int jobId, int newProgress) throws RemoteException {
+                    handleTranscodingProgressUpdate(jobId, newProgress);
                 }
             };
 
@@ -661,11 +698,14 @@
         public static final int STATUS_RUNNING = 2;
         /** The job is finished. */
         public static final int STATUS_FINISHED = 3;
+        /** The job is paused. */
+        public static final int STATUS_PAUSED = 4;
 
         @IntDef(prefix = { "STATUS_" }, value = {
                 STATUS_PENDING,
                 STATUS_RUNNING,
                 STATUS_FINISHED,
+                STATUS_PAUSED,
         })
         @Retention(RetentionPolicy.SOURCE)
         public @interface Status {}
@@ -821,17 +861,12 @@
             return mResult;
         }
 
-        private void setJobProgress(int newProgress) {
-            synchronized (this) {
-                mProgress = newProgress;
-            }
+        private synchronized void updateProgress(int newProgress) {
+            mProgress = newProgress;
+        }
 
-            // Notify listener.
-            OnProgressUpdateListener onProgressUpdateListener = mProgressUpdateListener;
-            if (mProgressUpdateListener != null) {
-                mProgressUpdateExecutor.execute(
-                        () -> onProgressUpdateListener.onProgressUpdate(mProgress));
-            }
+        private synchronized void updateStatus(int newStatus) {
+            mStatus = newStatus;
         }
     }
 
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/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodeManagerTest.java b/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodeManagerTest.java
index 3a5e692..8fe1088 100644
--- a/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodeManagerTest.java
+++ b/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodeManagerTest.java
@@ -33,10 +33,12 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /*
  * Functional tests for MediaTranscodeManager in the media framework.
@@ -230,5 +232,73 @@
                 30, TimeUnit.MILLISECONDS);
         assertTrue("Fails to cancel transcoding", finishedOnTime);
     }
+
+
+    @Test
+    public void testTranscodingProgressUpdate() throws Exception {
+        Log.d(TAG, "Starting: testMediaTranscodeManager");
+
+        Semaphore transcodeCompleteSemaphore = new Semaphore(0);
+        final CountDownLatch statusLatch = new CountDownLatch(1);
+
+        // Create a file Uri: file:///data/user/0/com.android.mediatranscodingtest/cache/temp.mp4
+        // The full path of this file is:
+        // /data/user/0/com.android.mediatranscodingtest/cache/temp.mp4
+        Uri destinationUri = Uri.parse(ContentResolver.SCHEME_FILE + "://"
+                + mContext.getCacheDir().getAbsolutePath() + "/HevcTranscode.mp4");
+
+        TranscodingRequest request =
+                new TranscodingRequest.Builder()
+                        .setSourceUri(mSourceHEVCVideoUri)
+                        .setDestinationUri(destinationUri)
+                        .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
+                        .setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
+                        .setVideoTrackFormat(createMediaFormat())
+                        .build();
+        Executor listenerExecutor = Executors.newSingleThreadExecutor();
+
+        Log.i(TAG, "transcoding to " + createMediaFormat());
+
+        TranscodingJob job = mMediaTranscodeManager.enqueueRequest(request, listenerExecutor,
+                transcodingJob -> {
+                    Log.d(TAG, "Transcoding completed with result: " + transcodingJob.getResult());
+                    assertEquals(transcodingJob.getResult(), TranscodingJob.RESULT_SUCCESS);
+                    transcodeCompleteSemaphore.release();
+                });
+        assertNotNull(job);
+
+        AtomicInteger progressUpdateCount = new AtomicInteger(0);
+
+        // Set progress update executor and use the same executor as result listener.
+        job.setOnProgressUpdateListener(listenerExecutor,
+                new TranscodingJob.OnProgressUpdateListener() {
+                    int mPreviousProgress = 0;
+
+                    @Override
+                    public void onProgressUpdate(int newProgress) {
+                        assertTrue("Invalid proress update", newProgress > mPreviousProgress);
+                        assertTrue("Invalid proress update", newProgress <= 100);
+                        if (newProgress > 0) {
+                            statusLatch.countDown();
+                        }
+                        mPreviousProgress = newProgress;
+                        progressUpdateCount.getAndIncrement();
+                        Log.i(TAG, "Get progress update " + newProgress);
+                    }
+                });
+
+        try {
+            statusLatch.await();
+            // The transcoding should not be finished yet as the clip is long.
+            assertTrue("Invalid status", job.getStatus() == TranscodingJob.STATUS_RUNNING);
+        } catch (InterruptedException e) { }
+
+        Log.d(TAG, "testMediaTranscodeManager - Waiting for transcode to cancel.");
+        boolean finishedOnTime = transcodeCompleteSemaphore.tryAcquire(
+                TRANSCODE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+        assertTrue("Transcode failed to complete in time.", finishedOnTime);
+        assertTrue("Failed to receive at least 10 progress updates",
+                progressUpdateCount.get() > 10);
+    }
 }
 
diff --git a/non-updatable-api/current.txt b/non-updatable-api/current.txt
index 70ef69d..f9d0d14 100644
--- a/non-updatable-api/current.txt
+++ b/non-updatable-api/current.txt
@@ -5596,6 +5596,7 @@
     method public android.graphics.drawable.Icon getIcon();
     method public android.app.RemoteInput[] getRemoteInputs();
     method public int getSemanticAction();
+    method public boolean isAuthenticationRequired();
     method public boolean isContextual();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.Notification.Action> CREATOR;
@@ -5625,6 +5626,7 @@
     method @NonNull public android.app.Notification.Action.Builder extend(android.app.Notification.Action.Extender);
     method @NonNull public android.os.Bundle getExtras();
     method @NonNull public android.app.Notification.Action.Builder setAllowGeneratedReplies(boolean);
+    method @NonNull public android.app.Notification.Action.Builder setAuthenticationRequired(boolean);
     method @NonNull public android.app.Notification.Action.Builder setContextual(boolean);
     method @NonNull public android.app.Notification.Action.Builder setSemanticAction(int);
   }
@@ -24047,6 +24049,8 @@
     method public boolean isSink();
     method public boolean isSource();
     field public static final int TYPE_AUX_LINE = 19; // 0x13
+    field public static final int TYPE_BLE_HEADSET = 26; // 0x1a
+    field public static final int TYPE_BLE_SPEAKER = 27; // 0x1b
     field public static final int TYPE_BLUETOOTH_A2DP = 8; // 0x8
     field public static final int TYPE_BLUETOOTH_SCO = 7; // 0x7
     field public static final int TYPE_BUILTIN_EARPIECE = 1; // 0x1
@@ -44122,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();
@@ -44353,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();
@@ -63863,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[]);
@@ -63950,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);
@@ -63973,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);
@@ -63991,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 86ac3e4..617a83f0 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";
   }
 
 }
@@ -54,6 +55,7 @@
   }
 
   public interface Parcelable {
+    method public default int getStability();
     field public static final int PARCELABLE_STABILITY_LOCAL = 0; // 0x0
     field public static final int PARCELABLE_STABILITY_VINTF = 1; // 0x1
   }
diff --git a/non-updatable-api/system-current.txt b/non-updatable-api/system-current.txt
index 0906a06..dbf9e9f 100644
--- a/non-updatable-api/system-current.txt
+++ b/non-updatable-api/system-current.txt
@@ -301,6 +301,7 @@
     field public static final int config_helpIntentNameKey = 17039390; // 0x104001e
     field public static final int config_helpPackageNameKey = 17039387; // 0x104001b
     field public static final int config_helpPackageNameValue = 17039388; // 0x104001c
+    field public static final int config_systemAutomotiveCluster = 17039400; // 0x1040028
     field public static final int config_systemGallery = 17039399; // 0x1040027
   }
 
@@ -4135,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);
@@ -4147,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;
@@ -4163,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);
@@ -4186,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 {
diff --git a/packages/CarSystemUI/proguard.flags b/packages/CarSystemUI/proguard.flags
index 66cbf26..516e70f 100644
--- a/packages/CarSystemUI/proguard.flags
+++ b/packages/CarSystemUI/proguard.flags
@@ -1,4 +1,6 @@
 -keep class com.android.systemui.CarSystemUIFactory
 -keep class com.android.car.notification.headsup.animationhelper.**
 
+-keep class com.android.systemui.CarGlobalRootComponent { *; }
+
 -include ../SystemUI/proguard.flags
diff --git a/packages/CarSystemUI/samples/sample1/rro/Android.bp b/packages/CarSystemUI/samples/sample1/rro/Android.bp
new file mode 100644
index 0000000..5b0347f
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample1/rro/Android.bp
@@ -0,0 +1,27 @@
+//
+// 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_app {
+    name: "CarSystemUISampleOneRRO",
+    resource_dirs: ["res"],
+    certificate: "platform",
+    platform_apis: true,
+    manifest: "AndroidManifest.xml",
+    aaptflags: [
+        "--no-resource-deduping",
+        "--no-resource-removal",
+     ]
+}
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/tv_circle_white_translucent.xml b/packages/CarSystemUI/samples/sample1/rro/AndroidManifest.xml
similarity index 62%
copy from packages/SystemUI/res/drawable/tv_circle_white_translucent.xml
copy to packages/CarSystemUI/samples/sample1/rro/AndroidManifest.xml
index 55d21de..5c25056 100644
--- a/packages/SystemUI/res/drawable/tv_circle_white_translucent.xml
+++ b/packages/CarSystemUI/samples/sample1/rro/AndroidManifest.xml
@@ -1,6 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ 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.
@@ -15,10 +14,11 @@
   ~ limitations under the License.
   -->
 
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="oval">
-
-  <solid
-      android:color="@color/tv_audio_recording_indicator_pulse" />
-
-</shape>
\ No newline at end of file
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.systemui.rro">
+    <overlay
+        android:targetPackage="com.android.systemui"
+        android:isStatic="false"
+        android:resourcesMap="@xml/car_sysui_overlays"
+    />
+</manifest>
\ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample1/rro/res/values/config.xml b/packages/CarSystemUI/samples/sample1/rro/res/values/config.xml
new file mode 100644
index 0000000..854ab7d
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample1/rro/res/values/config.xml
@@ -0,0 +1,47 @@
+<?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>
+    <!-- Configure which system bars should be displayed. -->
+    <bool name="config_enableTopNavigationBar">true</bool>
+    <bool name="config_enableLeftNavigationBar">true</bool>
+    <bool name="config_enableRightNavigationBar">true</bool>
+    <bool name="config_enableBottomNavigationBar">true</bool>
+
+    <!-- Configure the type of each system bar. Each system bar must have a unique type. -->
+    <!--    STATUS_BAR = 0-->
+    <!--    NAVIGATION_BAR = 1-->
+    <!--    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_bottomSystemBarType">1</integer>
+
+    <!-- Configure the relative z-order among the system bars. When two system bars overlap (e.g.
+         if both top bar and left bar are enabled, it creates an overlapping space in the upper left
+         corner), the system bar with the higher z-order takes the overlapping space and padding is
+         applied to the other bar.-->
+    <!-- NOTE: If two overlapping system bars have the same z-order, SystemBarConfigs will throw a
+         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_leftSystemBarZOrder">0</integer>
+    <integer name="config_rightSystemBarZOrder">0</integer>
+    <integer name="config_bottomSystemBarZOrder">10</integer>
+</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
new file mode 100644
index 0000000..7bcb8e1
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample1/rro/res/xml/car_sysui_overlays.xml
@@ -0,0 +1,33 @@
+
+<!--
+  ~ 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.
+  -->
+
+<overlay>
+    <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="integer/config_topSystemBarType" value="@integer/config_topSystemBarType"/>
+    <item target="integer/config_leftSystemBarType" value="@integer/config_leftSystemBarType"/>
+    <item target="integer/config_rightSystemBarType" value="@integer/config_rightSystemBarType"/>
+    <item target="integer/config_bottomSystemBarType" value="@integer/config_bottomSystemBarType"/>
+
+    <item target="integer/config_topSystemBarZOrder" value="@integer/config_topSystemBarZOrder"/>
+    <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"/>
+</overlay>
\ No newline at end of file
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIRootComponent.java b/packages/CarSystemUI/src/com/android/systemui/CarGlobalRootComponent.java
similarity index 79%
rename from packages/CarSystemUI/src/com/android/systemui/CarSystemUIRootComponent.java
rename to packages/CarSystemUI/src/com/android/systemui/CarGlobalRootComponent.java
index ece3bee..fde0b1a 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIRootComponent.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarGlobalRootComponent.java
@@ -18,9 +18,9 @@
 
 import com.android.systemui.dagger.DependencyBinder;
 import com.android.systemui.dagger.DependencyProvider;
+import com.android.systemui.dagger.GlobalRootComponent;
 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;
 
@@ -28,6 +28,7 @@
 
 import dagger.Component;
 
+/** Car subclass for GlobalRootComponent. */
 @Singleton
 @Component(
         modules = {
@@ -41,12 +42,15 @@
                 CarSystemUIModule.class,
                 CarSystemUIBinder.class
         })
-public interface CarSystemUIRootComponent extends SystemUIRootComponent {
+public interface CarGlobalRootComponent extends GlobalRootComponent {
     /**
-     * Builder for a CarSystemUIRootComponent.
+     * Builder for a CarGlobalRootComponent.
      */
     @Component.Builder
-    interface Builder extends SystemUIRootComponent.Builder {
-        CarSystemUIRootComponent build();
+    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..d19f368
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSysUIComponent.java
@@ -0,0 +1,38 @@
+/*
+ * 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.SysUIComponent;
+import com.android.systemui.dagger.SysUISingleton;
+
+import dagger.Subcomponent;
+
+/**
+ * Dagger Subcomponent for Core SysUI.
+ */
+@SysUISingleton
+@Subcomponent(modules = {})
+public interface CarSysUIComponent extends SysUIComponent {
+
+    /**
+     * Builder for a CarSysUIComponent.
+     */
+    @Subcomponent.Builder
+    interface Builder extends SysUIComponent.Builder {
+        CarSysUIComponent build();
+    }
+}
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..51ad286 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
@@ -29,12 +29,13 @@
 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.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;
@@ -52,12 +53,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;
@@ -79,8 +80,15 @@
 import dagger.Module;
 import dagger.Provides;
 
-@Module(includes = {DividerModule.class, QSModule.class})
-public abstract class CarSystemUIModule {
+@Module(
+        includes = {
+                DividerModule.class,
+                QSModule.class
+        },
+        subcomponents = {
+                CarSysUIComponent.class
+        })
+abstract class CarSystemUIModule {
 
     @Singleton
     @Provides
@@ -165,10 +173,11 @@
     @Singleton
     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;
     }
@@ -188,8 +197,8 @@
     abstract ShadeController provideShadeController(ShadeControllerImpl shadeController);
 
     @Binds
-    abstract SystemUIRootComponent bindSystemUIRootComponent(
-            CarSystemUIRootComponent systemUIRootComponent);
+    abstract GlobalRootComponent bindGlobalRootComponent(
+            CarGlobalRootComponent globalRootComponent);
 
     @Binds
     abstract VolumeDialogComponent bindVolumeDialogComponent(
@@ -200,6 +209,10 @@
             CarKeyguardViewController carKeyguardViewController);
 
     @Binds
+    abstract NotificationShadeWindowController bindNotificationShadeController(
+            NotificationShadeWindowControllerImpl notificationPanelViewController);
+
+    @Binds
     abstract DeviceProvisionedController bindDeviceProvisionedController(
             CarDeviceProvisionedControllerImpl deviceProvisionedController);
 
@@ -208,9 +221,5 @@
             CarDeviceProvisionedControllerImpl deviceProvisionedController);
 
     @Binds
-    abstract NotificationShadeWindowController bindNotificationShadeWindowController(
-            DummyNotificationShadeWindowController notificationShadeWindowController);
-
-    @Binds
     abstract DozeHost bindDozeHost(DozeServiceHost dozeServiceHost);
 }
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..0c064bd
--- /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.statusbar.NotificationShadeWindowController;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/** The automotive version of the notification shade window controller. */
+@Singleton
+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/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/volume/CarVolumeDialogComponent.java b/packages/CarSystemUI/src/com/android/systemui/car/volume/CarVolumeDialogComponent.java
index 98d24b1..d453957 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/volume/CarVolumeDialogComponent.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/volume/CarVolumeDialogComponent.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 
 import com.android.systemui.car.CarServiceProvider;
+import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.plugins.VolumeDialog;
 import com.android.systemui.volume.VolumeDialogComponent;
@@ -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/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java b/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java
index 8efbad6..2c4545e 100644
--- a/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java
+++ b/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java
@@ -155,41 +155,6 @@
         assertThat(mManager.getNextLocation(TIMEOUT_MS)).isEqualTo(location);
     }
 
-    @Test
-    public void testBypassRequest() throws Exception {
-        LocationRequest request = LocationRequest.createFromDeprecatedProvider(FUSED_PROVIDER, 1000,
-                0, false).setQuality(LocationRequest.POWER_HIGH).setLocationSettingsIgnored(true);
-
-        mProvider.setRequest(
-                new ProviderRequest.Builder()
-                        .setInterval(1000)
-                        .setLocationSettingsIgnored(true)
-                        .setLocationRequests(Collections.singletonList(request))
-                        .build(),
-                new WorkSource());
-
-        boolean containsNetworkBypass = false;
-        for (LocationRequest iRequest : mLocationManager.getTestProviderCurrentRequests(
-                NETWORK_PROVIDER)) {
-            if (iRequest.isLocationSettingsIgnored()) {
-                containsNetworkBypass = true;
-                break;
-            }
-        }
-
-        boolean containsGpsBypass = false;
-        for (LocationRequest iRequest : mLocationManager.getTestProviderCurrentRequests(
-                GPS_PROVIDER)) {
-            if (iRequest.isLocationSettingsIgnored()) {
-                containsGpsBypass = true;
-                break;
-            }
-        }
-
-        assertThat(containsNetworkBypass).isTrue();
-        assertThat(containsGpsBypass).isTrue();
-    }
-
     private static class LocationProviderManagerCapture extends ILocationProviderManager.Stub {
 
         private final LinkedBlockingQueue<Location> mLocations;
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index ddf2bc0..5f5be97 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -165,7 +165,7 @@
     <string name="tts_lang_not_selected" msgid="7927823081096056147">"Langue non sélectionnée"</string>
     <string name="tts_default_lang_summary" msgid="9042620014800063470">"Définir la langue utilisée par la synthèse vocale"</string>
     <string name="tts_play_example_title" msgid="1599468547216481684">"Écouter un échantillon"</string>
-    <string name="tts_play_example_summary" msgid="634044730710636383">"Lire une courte démonstration de la synthèse vocale"</string>
+    <string name="tts_play_example_summary" msgid="634044730710636383">"Écoutez une courte démonstration de la synthèse vocale"</string>
     <string name="tts_install_data_title" msgid="1829942496472751703">"Installer les données vocales"</string>
     <string name="tts_install_data_summary" msgid="3608874324992243851">"Installer les données nécessaires à la synthèse vocale"</string>
     <string name="tts_engine_security_warning" msgid="3372432853837988146">"Ce moteur de synthèse vocale est susceptible de collecter tout ce qui sera lu, y compris les données personnelles comme les mots de passe et les numéros de carte de paiement. Il provient du moteur <xliff:g id="TTS_PLUGIN_ENGINE_NAME">%s</xliff:g>. Voulez-vous activer son utilisation ?"</string>
@@ -189,8 +189,8 @@
     <item msgid="1158955023692670059">"Rapide"</item>
     <item msgid="5664310435707146591">"Plus rapide"</item>
     <item msgid="5491266922147715962">"Très rapide"</item>
-    <item msgid="7659240015901486196">"Rapide"</item>
-    <item msgid="7147051179282410945">"Très rapide"</item>
+    <item msgid="7659240015901486196">"Beaucoup plus rapide"</item>
+    <item msgid="7147051179282410945">"Extrêmement rapide"</item>
     <item msgid="581904787661470707">"La plus rapide"</item>
   </string-array>
     <string name="choose_profile" msgid="343803890897657450">"Sélectionner un profil"</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/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 4eea8ad..f1a3fee 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,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index c68ddbd..04916e2 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,
diff --git a/packages/Shell/src/com/android/shell/Screenshooter.java b/packages/Shell/src/com/android/shell/Screenshooter.java
index 8e01619..85f2552 100644
--- a/packages/Shell/src/com/android/shell/Screenshooter.java
+++ b/packages/Shell/src/com/android/shell/Screenshooter.java
@@ -17,11 +17,8 @@
 package com.android.shell;
 
 import android.graphics.Bitmap;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.hardware.display.DisplayManagerGlobal;
+import android.os.IBinder;
 import android.util.Log;
-import android.view.Display;
 import android.view.SurfaceControl;
 
 /**
@@ -40,22 +37,17 @@
      * @return The screenshot bitmap on success, null otherwise.
      */
     static Bitmap takeScreenshot() {
-        Display display = DisplayManagerGlobal.getInstance()
-                .getRealDisplay(Display.DEFAULT_DISPLAY);
-        Point displaySize = new Point();
-        display.getRealSize(displaySize);
-        final int displayWidth = displaySize.x;
-        final int displayHeight = displaySize.y;
-
-        int rotation = display.getRotation();
-        Rect crop = new Rect(0, 0, displayWidth, displayHeight);
-        Log.d(TAG, "Taking screenshot of dimensions " + displayWidth + " x " + displayHeight);
+        Log.d(TAG, "Taking fullscreen screenshot");
         // Take the screenshot
-        Bitmap screenShot =
-                SurfaceControl.screenshot(crop, displayWidth, displayHeight, rotation);
+        final IBinder displayToken = SurfaceControl.getInternalDisplayToken();
+        final SurfaceControl.DisplayCaptureArgs captureArgs =
+                new SurfaceControl.DisplayCaptureArgs.Builder(displayToken)
+                        .build();
+        final SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =
+                SurfaceControl.captureDisplay(captureArgs);
+        final Bitmap screenShot = screenshotBuffer == null ? null : screenshotBuffer.asBitmap();
         if (screenShot == null) {
-            Log.e(TAG, "Failed to take screenshot of dimensions " + displayWidth + " x "
-                    + displayHeight);
+            Log.e(TAG, "Failed to take fullscreen screenshot");
             return null;
         }
 
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/proguard.flags b/packages/SystemUI/proguard.flags
index 14097b1..92e7d88 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,6 @@
     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 { *; }
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index 53eb234..401f3e3 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -27,6 +27,7 @@
         <item name="android:textColor">?attr/wallpaperTextColorSecondary</item>
         <item name="android:textSize">14dp</item>
         <item name="android:background">@drawable/kg_emergency_button_background</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:paddingLeft">12dp</item>
         <item name="android:paddingRight">12dp</item>
     </style>
diff --git a/packages/SystemUI/res/drawable-television/ic_volume_media.xml b/packages/SystemUI/res/drawable-television/ic_volume_media.xml
new file mode 100644
index 0000000..e43c4b4
--- /dev/null
+++ b/packages/SystemUI/res/drawable-television/ic_volume_media.xml
@@ -0,0 +1,26 @@
+<?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
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0">
+    <path
+        android:fillColor="@color/tv_volume_dialog_accent"
+        android:pathData="M3,9v6h4l5,5L12,4L7,9L3,9zM16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v8.05c1.48,-0.73 2.5,-2.25 2.5,-4.02zM14,3.23v2.06c2.89,0.86 5,3.54 5,6.71s-2.11,5.85 -5,6.71v2.06c4.01,-0.91 7,-4.49 7,-8.77s-2.99,-7.86 -7,-8.77z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable-television/ic_volume_media_low.xml b/packages/SystemUI/res/drawable-television/ic_volume_media_low.xml
new file mode 100644
index 0000000..0f6dc95
--- /dev/null
+++ b/packages/SystemUI/res/drawable-television/ic_volume_media_low.xml
@@ -0,0 +1,26 @@
+<?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
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0">
+    <path
+        android:fillColor="@color/tv_volume_dialog_accent"
+        android:pathData="M3,15V9H7L12,4V20L7,15H3ZM14,7.97C15.48,8.71 16.5,10.23 16.5,12C16.5,13.77 15.48,15.29 14,16.02V7.97Z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable-television/ic_volume_media_mute.xml b/packages/SystemUI/res/drawable-television/ic_volume_media_mute.xml
new file mode 100644
index 0000000..4b59e13
--- /dev/null
+++ b/packages/SystemUI/res/drawable-television/ic_volume_media_mute.xml
@@ -0,0 +1,27 @@
+<?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
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0">
+    <path
+        android:fillColor="@color/tv_volume_dialog_accent"
+        android:pathData="M16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v2.21l2.45,2.45c0.03,-0.2 0.05,-0.41 0.05,-0.63zM19,12c0,0.94 -0.2,1.82 -0.54,2.64l1.51,1.51C20.63,14.91 21,13.5 21,12c0,-4.28 -2.99,-7.86 -7,-8.77v2.06c2.89,0.86 5,3.54 5,6.71zM4.27,3L3,4.27 7.73,9L3,9v6h4l5,5v-6.73l4.25,4.25c-0.67,0.52 -1.42,0.93 -2.25,1.18v2.06c1.38,-0.31 2.63,-0.95 3.69,-1.81L19.73,21 21,19.73l-9,-9L4.27,3zM12,4L9.91,6.09 12,8.18L12,4z"/>
+</vector>
+
diff --git a/packages/SystemUI/res/drawable/ic_volume_media_low.xml b/packages/SystemUI/res/drawable/ic_volume_media_low.xml
new file mode 100644
index 0000000..87591de
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_volume_media_low.xml
@@ -0,0 +1,18 @@
+<!--
+     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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@drawable/ic_volume_media" />
+</selector>
diff --git a/packages/SystemUI/res/drawable/tv_circle_dark.xml b/packages/SystemUI/res/drawable/tv_circle_dark.xml
deleted file mode 100644
index d1ba8e7..0000000
--- a/packages/SystemUI/res/drawable/tv_circle_dark.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?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.
-  -->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="oval">
-
-  <solid
-      android:color="@color/tv_audio_recording_indicator_background" />
-
-</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/tv_circle_white_translucent.xml b/packages/SystemUI/res/drawable/tv_rect_shadow_rounded.xml
similarity index 75%
copy from packages/SystemUI/res/drawable/tv_circle_white_translucent.xml
copy to packages/SystemUI/res/drawable/tv_rect_shadow_rounded.xml
index 55d21de..93f8724 100644
--- a/packages/SystemUI/res/drawable/tv_circle_white_translucent.xml
+++ b/packages/SystemUI/res/drawable/tv_rect_shadow_rounded.xml
@@ -16,9 +16,10 @@
   -->
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="oval">
+       android:shape="rectangle">
 
-  <solid
-      android:color="@color/tv_audio_recording_indicator_pulse" />
+    <corners android:radius="20dp"/>
+    <solid android:color="@color/tv_audio_recording_indicator_icon_background"/>
+    <stroke android:width="1dp" android:color="@color/tv_audio_recording_indicator_stroke"/>
 
 </shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/tv_ring_white.xml b/packages/SystemUI/res/drawable/tv_ring_white.xml
deleted file mode 100644
index 0f7cc10..0000000
--- a/packages/SystemUI/res/drawable/tv_ring_white.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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.
-  -->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="oval">
-
-  <stroke
-      android:width="1dp"
-      android:color="@android:color/white" />
-
-</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/tv_circle_white_translucent.xml b/packages/SystemUI/res/drawable/tv_volume_dialog_background.xml
similarity index 75%
copy from packages/SystemUI/res/drawable/tv_circle_white_translucent.xml
copy to packages/SystemUI/res/drawable/tv_volume_dialog_background.xml
index 55d21de..fee6e57 100644
--- a/packages/SystemUI/res/drawable/tv_circle_white_translucent.xml
+++ b/packages/SystemUI/res/drawable/tv_volume_dialog_background.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ 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.
@@ -16,9 +16,9 @@
   -->
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="oval">
+    android:shape="rectangle">
 
-  <solid
-      android:color="@color/tv_audio_recording_indicator_pulse" />
+  <solid android:color="@color/tv_volume_dialog_background" />
+  <corners android:radius="@dimen/tv_volume_dialog_corner_radius"/>
 
-</shape>
\ No newline at end of file
+</shape>
diff --git a/packages/SystemUI/res/drawable/tv_circle_white_translucent.xml b/packages/SystemUI/res/drawable/tv_volume_dialog_circle.xml
similarity index 84%
rename from packages/SystemUI/res/drawable/tv_circle_white_translucent.xml
rename to packages/SystemUI/res/drawable/tv_volume_dialog_circle.xml
index 55d21de..3c4fc05 100644
--- a/packages/SystemUI/res/drawable/tv_circle_white_translucent.xml
+++ b/packages/SystemUI/res/drawable/tv_volume_dialog_circle.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ 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.
@@ -17,8 +17,6 @@
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
     android:shape="oval">
+  <solid android:color="@color/tv_volume_dialog_circle" />
 
-  <solid
-      android:color="@color/tv_audio_recording_indicator_pulse" />
-
-</shape>
\ No newline at end of file
+</shape>
diff --git a/packages/SystemUI/res/layout-land-television/volume_dialog.xml b/packages/SystemUI/res/layout-land-television/volume_dialog.xml
index e0d158d..56d847c 100644
--- a/packages/SystemUI/res/layout-land-television/volume_dialog.xml
+++ b/packages/SystemUI/res/layout-land-television/volume_dialog.xml
@@ -20,7 +20,7 @@
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:background="@android:color/transparent"
-    android:theme="@style/qs_theme">
+    android:theme="@style/volume_dialog_theme">
 
     <FrameLayout
         android:id="@+id/volume_dialog"
@@ -46,7 +46,7 @@
             android:translationZ="@dimen/volume_dialog_elevation"
             android:clipChildren="false"
             android:clipToPadding="false"
-            android:background="@drawable/rounded_bg_full">
+            android:background="@drawable/tv_volume_dialog_background">
 
             <LinearLayout
                 android:id="@+id/volume_dialog_rows"
@@ -54,9 +54,7 @@
                 android:layout_height="wrap_content"
                 android:minWidth="@dimen/volume_dialog_panel_width"
                 android:gravity="center"
-                android:orientation="horizontal"
-                android:paddingRight="@dimen/volume_dialog_stream_padding"
-                android:paddingLeft="@dimen/volume_dialog_stream_padding">
+                android:orientation="horizontal">
                 <!-- volume rows added and removed here! :-) -->
             </LinearLayout>
 
@@ -98,4 +96,4 @@
 
     </FrameLayout>
 
-</FrameLayout>
\ No newline at end of file
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml b/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml
index 08209ab..c0f0aa8 100644
--- a/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml
+++ b/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml
@@ -21,11 +21,12 @@
     android:background="@android:color/transparent"
     android:clipChildren="false"
     android:clipToPadding="false"
-    android:theme="@style/qs_theme">
+    android:theme="@style/volume_dialog_theme">
 
     <LinearLayout
         android:layout_width="wrap_content"
         android:layout_height="match_parent"
+        android:padding="@dimen/tv_volume_dialog_row_padding"
         android:background="@android:color/transparent"
         android:gravity="center"
         android:layout_gravity="center"
@@ -33,9 +34,9 @@
         <com.android.keyguard.AlphaOptimizedImageButton
             android:id="@+id/volume_row_icon"
             style="@style/VolumeButtons"
-            android:layout_width="@dimen/volume_dialog_tap_target_size"
-            android:layout_height="@dimen/volume_dialog_tap_target_size"
-            android:background="@drawable/ripple_drawable_20dp"
+            android:layout_width="@dimen/tv_volume_dialog_bubble_size"
+            android:layout_height="@dimen/tv_volume_dialog_bubble_size"
+            android:background="@drawable/tv_volume_dialog_circle"
             android:tint="@color/accent_tint_color_selector"
             android:soundEffectsEnabled="false" />
         <TextView
@@ -62,6 +63,15 @@
                 android:layout_gravity="center"
                 android:rotation="0" />
         </FrameLayout>
+        <TextView
+            android:id="@+id/volume_number"
+            android:layout_width="@dimen/tv_volume_dialog_bubble_size"
+            android:layout_height="@dimen/tv_volume_dialog_bubble_size"
+            android:maxLength="2"
+            android:gravity="center"
+            android:background="@drawable/tv_volume_dialog_circle"
+            android:textSize="@dimen/tv_volume_number_text_size"
+            android:textColor="@color/accent_tint_color_selector"/>
     </LinearLayout>
 
     <include layout="@layout/volume_dnd_icon"/>
diff --git a/packages/SystemUI/res/layout-land/volume_dialog.xml b/packages/SystemUI/res/layout-land/volume_dialog.xml
index 5da7819..c420117 100644
--- a/packages/SystemUI/res/layout-land/volume_dialog.xml
+++ b/packages/SystemUI/res/layout-land/volume_dialog.xml
@@ -22,7 +22,7 @@
     android:gravity="right"
     android:layout_gravity="right"
     android:background="@android:color/transparent"
-    android:theme="@style/qs_theme">
+    android:theme="@style/volume_dialog_theme">
 
     <FrameLayout
         android:id="@+id/volume_dialog"
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/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/tv_audio_recording_indicator.xml b/packages/SystemUI/res/layout/tv_audio_recording_indicator.xml
index 1d2340d..f9336a54 100644
--- a/packages/SystemUI/res/layout/tv_audio_recording_indicator.xml
+++ b/packages/SystemUI/res/layout/tv_audio_recording_indicator.xml
@@ -19,7 +19,7 @@
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:orientation="horizontal"
-              android:padding="32dp">
+              android:padding="12dp">
 
     <FrameLayout
         android:layout_width="wrap_content"
@@ -32,45 +32,25 @@
             android:orientation="horizontal">
 
             <FrameLayout
-                android:layout_width="45dp"
-                android:layout_height="47dp">
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent">
 
                 <View
                     android:id="@+id/icon_container_bg"
-                    android:layout_width="match_parent"
+                    android:layout_width="50dp"
                     android:layout_height="match_parent"
                     android:background="@drawable/tv_rect_dark_left_rounded"/>
 
                 <FrameLayout
                     android:id="@+id/icon_mic"
-                    android:layout_width="35dp"
-                    android:layout_height="35dp"
-                    android:layout_marginStart="6dp"
-                    android:layout_marginTop="6dp"
-                    android:layout_marginBottom="6dp">
-
-                    <View
-                        android:layout_width="27dp"
-                        android:layout_height="27dp"
-                        android:layout_gravity="center"
-                        android:background="@drawable/tv_circle_dark"/>
+                    android:layout_width="34dp"
+                    android:layout_height="24dp"
+                    android:layout_gravity="center"
+                    android:background="@drawable/tv_rect_shadow_rounded">
 
                     <ImageView
-                        android:id="@+id/pulsating_circle"
-                        android:layout_width="27dp"
-                        android:layout_height="27dp"
-                        android:layout_gravity="center"
-                        android:background="@drawable/tv_circle_white_translucent"/>
-
-                    <ImageView
-                        android:layout_width="27dp"
-                        android:layout_height="27dp"
-                        android:layout_gravity="center"
-                        android:src="@drawable/tv_ring_white"/>
-
-                    <ImageView
-                        android:layout_width="16dp"
-                        android:layout_height="16dp"
+                        android:layout_width="13dp"
+                        android:layout_height="13dp"
                         android:layout_gravity="center"
                         android:background="@drawable/tv_ic_mic_white"/>
                 </FrameLayout>
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index 7d6547b..a90b1eb 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -22,7 +22,7 @@
     android:gravity="right"
     android:layout_gravity="right"
     android:background="@android:color/transparent"
-    android:theme="@style/qs_theme">
+    android:theme="@style/volume_dialog_theme">
 
     <!-- right-aligned to be physically near volume button -->
     <LinearLayout
diff --git a/packages/SystemUI/res/layout/volume_dialog_row.xml b/packages/SystemUI/res/layout/volume_dialog_row.xml
index 6128da8..b9efc5b 100644
--- a/packages/SystemUI/res/layout/volume_dialog_row.xml
+++ b/packages/SystemUI/res/layout/volume_dialog_row.xml
@@ -20,7 +20,7 @@
     android:layout_width="@dimen/volume_dialog_panel_width"
     android:clipChildren="false"
     android:clipToPadding="false"
-    android:theme="@style/qs_theme">
+    android:theme="@style/volume_dialog_theme">
 
     <LinearLayout
         android:layout_height="wrap_content"
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index ee87002..bac915d 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Laai tans aanbevelings"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Media"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Versteek die huidige sessie."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Versteek"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Maak toe"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Hervat"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Instellings"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Onaktief, gaan program na"</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index 03eb396..aedbded 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"ደብቅ"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 95d54a2..fee86ca 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -1093,7 +1093,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"إخفاء"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 3023a27..3778df8 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"লুকুৱাওক"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 3c8e04b..874bf74 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Gizlədin"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 4b400eb..2a120c5 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -1075,7 +1075,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Sakrij"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 f1064fb..e4bb67a 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -1081,7 +1081,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Схаваць"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 8ba2a1e..c51e58c 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Скриване"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 5861c44..3d00ca8 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"লুকান"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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>
@@ -1084,8 +1085,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"নতুন কন্ট্রোল দেখতে পাওয়ার বোতাম টিপে ধরে থাকুন"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"কন্ট্রোল যোগ করুন"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"কন্ট্রোল এডিট করুন"</string>
-    <!-- no translation found for one_handed_tutorial_title (6312198435090726656) -->
-    <skip />
-    <!-- no translation found for one_handed_tutorial_description (7674850272340517174) -->
-    <skip />
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"\'এক হাতে ব্যবহার করার মোড\'-এর ব্যবহার"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"বেরিয়ে আসার জন্য, স্ক্রিনের নিচ থেকে উপরের দিকে সোয়াইপ করুন অথবা অ্যাপের আইকনের উপরে যেকোনও জায়গায় ট্যাপ করুন"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index 984cbeb..1425984 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -1075,7 +1075,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Sakrij"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 b8c41ef..b150155 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Amaga"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 f946cc4..ae6cc13 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -1081,7 +1081,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Skrýt"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 a6de8a6..09f7f1a 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Skjul"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 372cc11..2410e47 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Ausblenden"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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>
@@ -1084,8 +1085,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Zum Anzeigen der Karten für neue Geräte Ein-/Aus-Taste gedrückt halten"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Steuerelemente hinzufügen"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Steuerelemente bearbeiten"</string>
-    <!-- no translation found for one_handed_tutorial_title (6312198435090726656) -->
-    <skip />
-    <!-- no translation found for one_handed_tutorial_description (7674850272340517174) -->
-    <skip />
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Einhandmodus verwenden"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Wenn du die App schließen möchtest, wische vom unteren Rand des Displays nach oben oder tippe auf eine beliebige Stelle oberhalb der App"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index b467347..f66c5a8 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Απόκρυψη"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 7fa7f83..0e22b58 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Loading recommendations"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Media"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Hide the current session."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Hide"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Dismiss"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Resume"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Settings"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inactive, check app"</string>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index f73dad3..acf087d 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Loading recommendations"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Media"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Hide the current session."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Hide"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Dismiss"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Resume"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Settings"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inactive, check app"</string>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 7fa7f83..0e22b58 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Loading recommendations"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Media"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Hide the current session."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Hide"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Dismiss"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Resume"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Settings"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inactive, check app"</string>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 7fa7f83..0e22b58 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Loading recommendations"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Media"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Hide the current session."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Hide"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Dismiss"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Resume"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Settings"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inactive, check app"</string>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index 6140bf1..4f4238a 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‎‎‎‎‏‏‎‏‎‎‏‏‎‏‎‏‏‎‏‎‏‏‎‏‏‏‏‏‎‎‏‎‎‏‎‏‎‏‏‏‎‎‎‏‎‎‏‎‎‎‏‎‏‎‏‎‎‎Loading recommendations‎‏‎‎‏‎"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‏‏‏‎‏‎‎‏‏‎‎‏‏‎‎‎‎‏‏‏‎‎‏‎‏‏‎‏‎‎‎‎‏‎‏‎‎‎‎‎‏‎‏‎Media‎‏‎‎‏‎"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‎‏‏‏‎‏‎‏‎‎‏‏‎‏‎‏‏‏‎‎‎‎‎‎‏‎‏‎‏‏‎‏‎‎‎‏‎‎‎‎‏‎‎‎‏‏‏‎‏‏‏‏‏‏‎‎‏‎Hide the current session.‎‏‎‎‏‎"</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‎‎‏‎‎‎‎‎‎‎‏‎‏‎‎‏‎‎‏‎‎‎‏‏‎‏‏‎‏‎‎‏‏‎‏‎‏‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‏‎‎Hide‎‏‎‎‏‎"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‎‏‏‏‏‎‎‎‎‏‏‎‏‏‏‏‎‎‎‎‏‏‎‎‎‏‎‎‏‎‏‎‎‎‏‎‎‎‏‏‎‏‎‎‏‎‏‏‎‏‎‏‎Dismiss‎‏‎‎‏‎"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‎‏‏‎‏‎‏‎‏‎‎‏‏‏‏‏‎‏‏‎‏‎‎‎‏‎‏‏‏‏‏‎‎‎‏‏‎‎‏‎‏‎‎‏‎‎‎‏‏‏‎‎‎‎‏‎‏‎Resume‎‏‎‎‏‎"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‎‏‏‎‏‎‏‏‏‎‏‎‏‏‎‏‎‎‎‎‎‎‏‎‏‏‎‏‎‎‎‏‏‎‏‏‎‏‏‎‎‎‎‎‎‏‏‎‎‎‏‏‎‎‎‎Settings‎‏‎‎‏‎"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‏‎‎‎‎‎‏‎‏‏‎‎‎‏‏‏‎‎‎‏‏‏‎‏‎‎‎‏‎‏‎‏‎‎‏‏‏‏‎‏‏‎‏‎‎‎‎‏‎‎‏‏‏‏‏‏‎‎Inactive, check app‎‏‎‎‏‎"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 6818116..45e0724 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Ocultar"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 f09a0d2..f7ebbd0 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Ocultar"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 6236f25..1e5e49e 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Peida"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 167a09c..38b2103d 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Gomendioak kargatzen"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Multimedia-edukia"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Ezkutatu uneko saioa."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Ezkutatu"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Baztertu"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Berrekin"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Ezarpenak"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inaktibo; egiaztatu aplikazioa"</string>
@@ -1085,5 +1085,5 @@
     <string name="controls_menu_add" msgid="4447246119229920050">"Gehitu aukerak"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Editatu aukerak"</string>
     <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Esku bakarreko modua erabiltzea"</string>
-    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Ateratzeko, pasatu hatza pantailaren behealdetik gora edo sakatu aplikazioaren gainean, edonon"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Ateratzeko, pasatu hatza pantailaren behealdetik gora edo sakatu aplikazioaren gainaldea"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index 0555052..d39d0c3 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -1069,7 +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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"پنهان کردن"</string>
+    <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-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index ec9bbc4..fa076c4 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Piilota"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 6c5387f..9c418f7 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Masquer"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 3a2ae07..3e2f5fd 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Masquer"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 25c7b4b..9ecd75e 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Ocultar"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 17cc5a4..6e05a54 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"છુપાવો"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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>
@@ -1084,8 +1085,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"નવા નિયંત્રણ જોવા માટે પાવર બટનને દબાવી રાખો"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"નિયંત્રણો ઉમેરો"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"નિયંત્રણોમાં ફેરફાર કરો"</string>
-    <!-- no translation found for one_handed_tutorial_title (6312198435090726656) -->
-    <skip />
-    <!-- no translation found for one_handed_tutorial_description (7674850272340517174) -->
-    <skip />
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"એક-હાથે વાપરો મોડનો ઉપયોગ કરી રહ્યાં છીએ"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"બહાર નીકળવા માટે, સ્ક્રીનની નીચેના ભાગથી ઉપરની તરફ સ્વાઇપ કરો અથવા ઍપના આઇકન પર ગમે ત્યાં ટૅપ કરો"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index 97ae7b6..de4fc48 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -1071,7 +1071,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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"छिपाएं"</string>
+    <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>
@@ -1086,6 +1086,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"नए कंट्रोल देखने के लिए पावर बटन दबाकर रखें"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"कंट्राेल जोड़ें"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"कंट्रोल मेन्यू में बदलाव करें"</string>
-    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"वन-हैंडेड मोड का इस्तेमाल करें"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"वन-हैंडेड मोड का इस्तेमाल करना"</string>
     <string name="one_handed_tutorial_description" msgid="7674850272340517174">"इसे बंद करने के लिए, स्क्रीन के सबसे निचले हिस्से से ऊपर की ओर स्वाइप करें या ऐप्लिकेशन के आइकॉन के ऊपर कहीं भी टैप करें"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index 9e17735..4bebe79 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -1075,7 +1075,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Sakrij"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 804587f..42e251f 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Elrejtés"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 08ef489..e6b7139 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Թաքցնել"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 ba9eca3..fec4205 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Sembunyikan"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 6295813..3a9e63b 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Fela"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 ef862cb..3eca501 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Nascondi"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 358946d..e88c951 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -1081,7 +1081,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"הסתרה"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 9cb872bf..130f682 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"非表示"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 ef6ff128..d050875 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"დამალვა"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 08b1f62..36c726e 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Жасыру"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 6d80ee4..9d87c58 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"លាក់"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 db20864..901f024 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"ಮರೆಮಾಡಿ"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 b43eb41..8a64155 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"숨기기"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 02e468a..3cf4126 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Жашыруу"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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-land-television/dimens.xml b/packages/SystemUI/res/values-land-television/dimens.xml
index 499341c..90fc652 100644
--- a/packages/SystemUI/res/values-land-television/dimens.xml
+++ b/packages/SystemUI/res/values-land-television/dimens.xml
@@ -17,5 +17,8 @@
 <resources>
   <!-- Width of volume bar -->
   <dimen name="volume_dialog_row_width">252dp</dimen>
-  <dimen name="volume_dialog_tap_target_size">36dp</dimen>
+  <dimen name="tv_volume_dialog_bubble_size">36dp</dimen>
+  <dimen name="tv_volume_dialog_corner_radius">40dp</dimen>
+  <dimen name="tv_volume_dialog_row_padding">5dp</dimen>
+  <dimen name="tv_volume_number_text_size">16dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index 030bdc1..b1c5a5c 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"ເຊື່ອງ"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 6f48741..4e9b802 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -1081,7 +1081,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Slėpti"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 bdea26a..a4c4666 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -1075,7 +1075,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Paslēpt"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 c9f3777..7e623ad 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Сокриј"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 c968c28..35e6427 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"മറയ്‌ക്കുക"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 9ce6779..b4690d4 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Нуух"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 d712679..4ea965a 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"लपवा"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 01e918a..38ee25c 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Sembunyikan"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index 23b0a06..150ed94e 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -1069,7 +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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"ဖျောက်ထားမည်"</string>
+    <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-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index b0a593f..d872a89 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Skjul"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 09462cf..1974e80 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -731,11 +731,11 @@
     <string name="see_more_title" msgid="7409317011708185729">"थप हेर्नुहोस्"</string>
     <string name="appops_camera" msgid="5215967620896725715">"यो अनुप्रयोगले क्यामेराको प्रयोग गर्दै छ।"</string>
     <string name="appops_microphone" msgid="8805468338613070149">"यो अनुप्रयोगले माइक्रोफोनको प्रयोग गर्दै छ।"</string>
-    <string name="appops_overlay" msgid="4822261562576558490">"यो अनुप्रयोगले तपाईंको स्क्रिनका अन्य अनुप्रयोगहरूमाथि प्रदर्शन गर्दै छ।"</string>
+    <string name="appops_overlay" msgid="4822261562576558490">"यो अनुप्रयोगले तपाईंको स्क्रिनका अन्य एपमाथि प्रदर्शन गर्दै छ।"</string>
     <string name="appops_camera_mic" msgid="7032239823944420431">"यो अनुप्रयोगले माइक्रोफोन र क्यामेराको प्रयोग गर्दै छ।"</string>
-    <string name="appops_camera_overlay" msgid="6466845606058816484">"यो अनुप्रयोगले तपाईंको स्क्रिनका अन्य अनुप्रयोगहरूमाथि प्रदर्शन गर्नुका साथै क्यामेराको प्रयोग गर्दै छ।"</string>
-    <string name="appops_mic_overlay" msgid="4609326508944233061">"यो अनुप्रयोगले तपाईंको स्क्रिनका अन्य अनुप्रयोगहरूमाथि प्रदर्शन गर्नुका साथै माइक्रोफोनको प्रयोग गर्दै छ।"</string>
-    <string name="appops_camera_mic_overlay" msgid="5584311236445644095">"यो अनुप्रयोगले तपाईंको स्क्रिनका अन्य अनुप्रयोगहरूमाथि प्रदर्शन गर्नुका साथै माइक्रोफोन र क्यामेराको प्रयोग गर्दै छ।"</string>
+    <string name="appops_camera_overlay" msgid="6466845606058816484">"यो अनुप्रयोगले तपाईंको स्क्रिनका अन्य एपमाथि प्रदर्शन गर्नुका साथै क्यामेराको प्रयोग गर्दै छ।"</string>
+    <string name="appops_mic_overlay" msgid="4609326508944233061">"यो अनुप्रयोगले तपाईंको स्क्रिनका अन्य एपमाथि प्रदर्शन गर्नुका साथै माइक्रोफोनको प्रयोग गर्दै छ।"</string>
+    <string name="appops_camera_mic_overlay" msgid="5584311236445644095">"यो अनुप्रयोगले तपाईंको स्क्रिनका अन्य एपमाथि प्रदर्शन गर्नुका साथै माइक्रोफोन र क्यामेराको प्रयोग गर्दै छ।"</string>
     <string name="notification_appops_settings" msgid="5208974858340445174">"सेटिङहरू"</string>
     <string name="notification_appops_ok" msgid="2177609375872784124">"ठिक छ"</string>
     <string name="feedback_silenced" msgid="5382212321253328247">"सिस्टमले यो सूचना आउँदा बज्ने ध्वनि बन्द गरेको छ।"</string>
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"लुकाउनुहोस्"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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>
@@ -1084,8 +1085,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"नयाँ नियन्त्रण सुविधाहरू हेर्न पावर बटन थिचिराख्नुहोस्"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"नियन्त्रण सुविधाहरू थप्नुहोस्"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"नियन्त्रण सुविधाहरू सम्पादन गर्नु…"</string>
-    <!-- no translation found for one_handed_tutorial_title (6312198435090726656) -->
-    <skip />
-    <!-- no translation found for one_handed_tutorial_description (7674850272340517174) -->
-    <skip />
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"एक हाते मोड प्रयोग गरिँदै छ"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"बाहिर निस्कन, स्क्रिनको पुछारबाट माथितिर स्वाइप गर्नुहोस् वा एपभन्दा माथि जुनसुकै ठाउँमा ट्याप गर्नुहोस्"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 2385017..9da8afa 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Verbergen"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 d5c077b..5b5cbc6 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"ଲୁଚାନ୍ତୁ"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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>
@@ -1084,8 +1085,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"ନୂଆ ନିୟନ୍ତ୍ରଣଗୁଡ଼ିକୁ ଦେଖିବା ପାଇଁ ପାୱାର ବଟନକୁ ଧରି ରଖନ୍ତୁ"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"ନିୟନ୍ତ୍ରଣଗୁଡ଼ିକ ଯୋଗ କରନ୍ତୁ"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"ନିୟନ୍ତ୍ରଣଗୁଡ଼ିକ ସମ୍ପାଦନ କରନ୍ତୁ"</string>
-    <!-- no translation found for one_handed_tutorial_title (6312198435090726656) -->
-    <skip />
-    <!-- no translation found for one_handed_tutorial_description (7674850272340517174) -->
-    <skip />
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"ଏକ-ହାତ ମୋଡ୍ ବ୍ୟବହାର କରି"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"ବାହାରି ଯିବା ପାଇଁ, ତଳୁ ଉପରକୁ ସ୍ୱାଇପ୍ କରନ୍ତୁ କିମ୍ବା ଆପ୍ ଆଇକନର ଉପରେ ଯେ କୌଣସି ସ୍ଥାନରେ ଟାପ୍ କରନ୍ତୁ"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index 1156782..5c31ce7 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"ਲੁਕਾਓ"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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>
@@ -1084,8 +1085,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"ਨਵੇਂ ਕੰਟਰੋਲ ਦੇਖਣ ਲਈ ਪਾਵਰ ਬਟਨ ਦਬਾਈ ਰੱਖੋ"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"ਕੰਟਰੋਲ ਸ਼ਾਮਲ ਕਰੋ"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"ਕੰਟਰੋਲਾਂ ਦਾ ਸੰਪਾਦਨ ਕਰੋ"</string>
-    <!-- no translation found for one_handed_tutorial_title (6312198435090726656) -->
-    <skip />
-    <!-- no translation found for one_handed_tutorial_description (7674850272340517174) -->
-    <skip />
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"ਇੱਕ ਹੱਥ ਮੋਡ ਵਰਤਣਾ"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"ਬਾਹਰ ਜਾਣ ਲਈ, ਸਕ੍ਰੀਨ ਦੇ ਹੇਠਾਂ ਤੋਂ ਉੱਪਰ ਵੱਲ ਸਵਾਈਪ ਕਰੋ ਜਾਂ ਐਪ \'ਤੇ ਕਿਤੇ ਵੀ ਟੈਪ ਕਰੋ"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 1f6effe..b7ee750 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -1081,7 +1081,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Ukryj"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index 14d7a84..715b0e4 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Carregando recomendações"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Mídia"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Ocultar a sessão atual."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Ocultar"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Dispensar"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Retomar"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Configurações"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inativo, verifique o app"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index a6f7f00..1a59de4 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"A carregar recomendações…"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Multimédia"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Oculte a sessão atual."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Ocultar"</string>
+    <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">"Definições"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inativa. Consulte a app."</string>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index 14d7a84..715b0e4 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Carregando recomendações"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Mídia"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Ocultar a sessão atual."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Ocultar"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Dispensar"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Retomar"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Configurações"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inativo, verifique o app"</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index 0f15cfc..49c3ba0 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -1075,7 +1075,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Ascunde"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 6196f88..7fb456d 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -1081,7 +1081,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Скрыть"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index 8aca9f4..1cd1ceb 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -1069,7 +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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"සඟවන්න"</string>
+    <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 45ee562..9d7e7d1 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -1081,7 +1081,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Skryť"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 c7ad8ab..ecbf1d9 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -1081,7 +1081,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Skrij"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 3e0aecc..70245b8 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Fshih"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 985041b..1d02ca6 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -1075,7 +1075,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Сакриј"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 2018a38..7a7448b 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Dölj"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 6eecc0e..b8d95fc 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Ficha"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 a05e0e7..60c1cf9 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"மறை"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 7d76abd..6cfe03d 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"దాచు"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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-television/config.xml b/packages/SystemUI/res/values-television/config.xml
index 1696aab..7b1479a 100644
--- a/packages/SystemUI/res/values-television/config.xml
+++ b/packages/SystemUI/res/values-television/config.xml
@@ -43,4 +43,7 @@
         <item>com.android.systemui.toast.ToastUI</item>
         <item>com.android.systemui.onehanded.OneHandedUI</item>
     </string-array>
+
+    <!-- Show a separate icon for low and high volume on the volume dialog -->
+    <bool name="config_showLowMediaVolumeIcon">true</bool>
 </resources>
diff --git a/packages/SystemUI/res/values-television/styles.xml b/packages/SystemUI/res/values-television/styles.xml
index b01c5d8..4cf7034a 100644
--- a/packages/SystemUI/res/values-television/styles.xml
+++ b/packages/SystemUI/res/values-television/styles.xml
@@ -22,4 +22,8 @@
         <item name="android:windowEnterAnimation">@null</item>
         <item name="android:windowExitAnimation">@null</item>
     </style>
+
+    <style name="volume_dialog_theme" parent="qs_theme">
+        <item name="android:colorAccent">@color/tv_volume_dialog_accent</item>
+    </style>
 </resources>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index 765c224..a2c1127 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"ซ่อน"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 c2cae90..e5f0532 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Itago"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 74b44f2..4943e2d 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Gizle"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 38c331a..447912e 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -1081,7 +1081,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Приховати"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 69ddabb..20b5518 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"چھپائیں"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index ed704b0..cf1da75 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -227,7 +227,7 @@
     <string name="data_connection_roaming" msgid="375650836665414797">"Rouming"</string>
     <string name="data_connection_edge" msgid="6316755666481405762">"EDGE"</string>
     <string name="accessibility_data_connection_wifi" msgid="4422160347472742434">"Wi-Fi"</string>
-    <string name="accessibility_no_sim" msgid="1140839832913084973">"SIM karta solinmagan."</string>
+    <string name="accessibility_no_sim" msgid="1140839832913084973">"SIM kartasiz."</string>
     <string name="accessibility_cell_data" msgid="172950885786007392">"Mobil internet"</string>
     <string name="accessibility_cell_data_on" msgid="691666434519443162">"Mobil internet yoniq"</string>
     <string name="cell_data_off_content_description" msgid="9165555931499878044">"Mobil internet yoqilmagan"</string>
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Tavsiyalar yuklanmoqda"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Media"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Joriy seans berkitilsin."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Berkitish"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Yopish"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Davom etish"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Sozlamalar"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Nofaol. Ilovani tekshiring"</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index 5cc2099..a12a08d 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Ẩn"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 f670bf6..3382365 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"隐藏"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 2e26d61..1d55ca2 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"隱藏"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 0049d2e..e62c164 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"隱藏"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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 c8c99b1..bd73912 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -1069,7 +1069,8 @@
     <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>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Fihla"</string>
+    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
+    <skip />
     <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/colors_tv.xml b/packages/SystemUI/res/values/colors_tv.xml
index 53cd971..cb49918 100644
--- a/packages/SystemUI/res/values/colors_tv.xml
+++ b/packages/SystemUI/res/values/colors_tv.xml
@@ -24,7 +24,11 @@
 
     <!-- Background color for audio recording indicator (G800) -->
     <color name="tv_audio_recording_indicator_background">#FF3C4043</color>
-    <color name="tv_audio_recording_indicator_pulse">#4DFFFFFF</color>
+    <color name="tv_audio_recording_indicator_icon_background">#CC000000</color>
+    <color name="tv_audio_recording_indicator_stroke">#33FFFFFF</color>
 
     <color name="red">#FFCC0000</color>
+    <color name="tv_volume_dialog_background">#E61F232B</color>
+    <color name="tv_volume_dialog_circle">#08FFFFFF</color>
+    <color name="tv_volume_dialog_accent">#FFDADCE0</color>
 </resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index fba43a6..ce1ca5a 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -565,13 +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/donottranslate.xml b/packages/SystemUI/res/values/donottranslate.xml
index a1c52e5..f05be06 100644
--- a/packages/SystemUI/res/values/donottranslate.xml
+++ b/packages/SystemUI/res/values/donottranslate.xml
@@ -21,5 +21,5 @@
     <string name="system_ui_date_pattern" translatable="false">@*android:string/system_ui_date_pattern</string>
 
     <!-- Date format for the always on display.  -->
-    <item type="string" name="system_ui_aod_date_pattern" translatable="false">@*android:string/system_ui_date_pattern</item>
+    <item type="string" name="system_ui_aod_date_pattern" translatable="false">EEEMMMd</item>
 </resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 9e5b94e..ee07e61 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -387,6 +387,9 @@
         <item name="android:homeAsUpIndicator">@drawable/ic_arrow_back</item>
     </style>
 
+    <!-- Overridden by values-television/styles.xml with tv-specific settings -->
+    <style name="volume_dialog_theme" parent="qs_theme"/>
+
     <style name="systemui_theme_remote_input" parent="@android:style/Theme.DeviceDefault.Light">
         <item name="android:colorAccent">@color/remote_input_accent</item>
     </style>
diff --git a/media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ContextUtils.java
similarity index 66%
copy from media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl
copy to packages/SystemUI/shared/src/com/android/systemui/shared/system/ContextUtils.java
index b1f99e6..1de740a 100644
--- a/media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ContextUtils.java
@@ -14,17 +14,15 @@
  * limitations under the License.
  */
 
-package android.media;
+package com.android.systemui.shared.system;
 
-import android.media.AudioDeviceAttributes;
+import android.annotation.UserIdInt;
+import android.content.Context;
 
-/**
- * AIDL for AudioService to signal audio strategy-preferred device updates.
- *
- * {@hide}
- */
-oneway interface IStrategyPreferredDeviceDispatcher {
+public class ContextUtils {
 
-    void dispatchPrefDeviceChanged(int strategyId, in AudioDeviceAttributes device);
-
+    /** Get the user associated with this context */
+    public static @UserIdInt int getUserId(Context context) {
+        return context.getUserId();
+    }
 }
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..5f00a59 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().getRootComponent().createViewInstanceCreatorFactory());
         mViewConfiguration = ViewConfiguration.get(context);
         mKeyguardStateController = Dependency.get(KeyguardStateController.class);
         mSecondaryLockScreenController = new AdminSecondaryLockScreenController(context, this,
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..c04775a 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -47,6 +47,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,11 +66,11 @@
 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;
@@ -85,10 +87,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;
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java
index 2deeb12..5f88156 100644
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java
+++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java
@@ -39,21 +39,15 @@
  */
 @Singleton
 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 +81,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 +121,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..1515272 100644
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
@@ -172,24 +172,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/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..627f559 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.GlobalRootComponent;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 
 import javax.inject.Inject;
 
@@ -81,8 +85,17 @@
             ((ContextInitializer) contentProvider).setContextAvailableCallback(
                     context -> {
                         SystemUIFactory.createFromConfig(context);
-                        SystemUIFactory.getInstance().getRootComponent().inject(
-                                contentProvider);
+                        GlobalRootComponent rootComponent =
+                                SystemUIFactory.getInstance().getRootComponent();
+                        try {
+                            Method injectMethod = rootComponent.getClass()
+                                    .getMethod("inject", contentProvider.getClass());
+                            injectMethod.invoke(rootComponent, contentProvider);
+                        } catch (NoSuchMethodException
+                                | IllegalAccessException
+                                | InvocationTargetException e) {
+                            // no-op
+                        }
                     }
             );
         }
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index c84701c..4f78f65 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -32,7 +32,7 @@
 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.dump.DumpManager;
 import com.android.systemui.util.NotificationChannels;
 
@@ -57,7 +57,7 @@
     private SystemUI[] mServices;
     private boolean mServicesStarted;
     private SystemUIAppComponentFactory.ContextAvailableCallback mContextAvailableCallback;
-    private SystemUIRootComponent mRootComponent;
+    private GlobalRootComponent mRootComponent;
 
     public SystemUIApplication() {
         super();
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 1a15c0a..e34a653 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -28,8 +28,11 @@
 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.demomode.DemoModeController;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -53,7 +56,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,7 +93,10 @@
     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.
@@ -96,13 +104,13 @@
         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;
     }
 
@@ -144,12 +152,14 @@
             StatusBar statusBar,
             NotificationWakeUpCoordinator wakeUpCoordinator,
             KeyguardBypassController keyguardBypassController,
-            StatusBarStateController statusBarStateController) {
+            StatusBarStateController statusBarStateController,
+            DemoModeController demoModeController) {
         return new NotificationIconAreaController(context, statusBar, statusBarStateController,
                 wakeUpCoordinator, keyguardBypassController,
                 Dependency.get(NotificationMediaManager.class),
                 Dependency.get(NotificationListener.class),
                 Dependency.get(DozeParameters.class),
-                Dependency.get(BubbleController.class));
+                Dependency.get(BubbleController.class),
+                demoModeController);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index 0135e4c..1752579 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -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 816bcf8..d5f74a8 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
@@ -53,8 +53,8 @@
             ActivityInfo.CONFIG_DENSITY | ActivityInfo.CONFIG_ORIENTATION;
 
     @VisibleForTesting
-    protected WindowMagnificationController mWindowMagnificationController;
-    protected final ModeSwitchesController mModeSwitchesController;
+    protected WindowMagnificationAnimationController mWindowMagnificationAnimationController;
+    private final ModeSwitchesController mModeSwitchesController;
     private final Handler mHandler;
     private final AccessibilityManager mAccessibilityManager;
     private final CommandQueue mCommandQueue;
@@ -72,6 +72,11 @@
                 Context.ACCESSIBILITY_SERVICE);
         mCommandQueue = commandQueue;
         mModeSwitchesController = modeSwitchesController;
+        final WindowMagnificationController controller = new WindowMagnificationController(mContext,
+                mHandler, new SfVsyncFrameCallbackProvider(), null,
+                new SurfaceControl.Transaction(), this);
+        mWindowMagnificationAnimationController = new WindowMagnificationAnimationController(
+                mContext, controller);
     }
 
     @Override
@@ -81,9 +86,7 @@
             return;
         }
         mLastConfiguration.setTo(newConfig);
-        if (mWindowMagnificationController != null) {
-            mWindowMagnificationController.onConfigurationChanged(configDiff);
-        }
+        mWindowMagnificationAnimationController.onConfigurationChanged(configDiff);
         if (mModeSwitchesController != null) {
             mModeSwitchesController.onConfigurationChanged(configDiff);
         }
@@ -97,39 +100,25 @@
     @MainThread
     void enableWindowMagnification(int displayId, float scale, float centerX, float centerY) {
         //TODO: b/144080869 support multi-display.
-        if (mWindowMagnificationController == null) {
-            mWindowMagnificationController = new WindowMagnificationController(mContext,
-                    mHandler,
-                    new SfVsyncFrameCallbackProvider(),
-                    null, new SurfaceControl.Transaction(),
-                    this);
-        }
-        mWindowMagnificationController.enableWindowMagnification(scale, centerX, centerY);
+        mWindowMagnificationAnimationController.enableWindowMagnification(scale, centerX, centerY);
     }
 
     @MainThread
     void setScale(int displayId, float scale) {
         //TODO: b/144080869 support multi-display.
-        if (mWindowMagnificationController != null) {
-            mWindowMagnificationController.setScale(scale);
-        }
+        mWindowMagnificationAnimationController.setScale(scale);
     }
 
     @MainThread
     void moveWindowMagnifier(int displayId, float offsetX, float offsetY) {
         //TODO: b/144080869 support multi-display.
-        if (mWindowMagnificationController != null) {
-            mWindowMagnificationController.moveWindowMagnifier(offsetX, offsetY);
-        }
+        mWindowMagnificationAnimationController.moveWindowMagnifier(offsetX, offsetY);
     }
 
     @MainThread
     void disableWindowMagnification(int displayId) {
         //TODO: b/144080869 support multi-display.
-        if (mWindowMagnificationController != null) {
-            mWindowMagnificationController.deleteWindowMagnification();
-        }
-        mWindowMagnificationController = null;
+        mWindowMagnificationAnimationController.deleteWindowMagnification();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java
new file mode 100644
index 0000000..ae51623
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java
@@ -0,0 +1,272 @@
+/*
+ * 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.accessibility;
+
+import android.animation.Animator;
+import android.animation.ValueAnimator;
+import android.annotation.IntDef;
+import android.content.Context;
+import android.content.res.Resources;
+import android.util.Log;
+import android.view.animation.AccelerateInterpolator;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.R;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Provides same functionality of {@link WindowMagnificationController}. Some methods run with
+ * the animation.
+ */
+class WindowMagnificationAnimationController implements ValueAnimator.AnimatorUpdateListener,
+        Animator.AnimatorListener {
+
+    private static final String TAG = "WindowMagnificationBridge";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({STATE_DISABLED, STATE_ENABLED, STATE_DISABLING, STATE_ENABLING})
+    @interface MagnificationState {}
+
+    //The window magnification is disabled.
+    private static final int STATE_DISABLED = 0;
+    //The window magnification is enabled.
+    private static final int STATE_ENABLED = 1;
+    //The window magnification is going to be disabled when the animation is end.
+    private  static final int STATE_DISABLING = 2;
+    //The animation is running for enabling the window magnification.
+    private static final int STATE_ENABLING = 3;
+
+    private final WindowMagnificationController mController;
+    private final ValueAnimator mValueAnimator;
+    private final AnimationSpec mStartSpec = new AnimationSpec();
+    private final AnimationSpec mEndSpec = new AnimationSpec();
+    private final Context mContext;
+
+    @MagnificationState
+    private int mState = STATE_DISABLED;
+
+    WindowMagnificationAnimationController(
+            Context context, WindowMagnificationController controller) {
+        this(context, controller, newValueAnimator(context.getResources()));
+    }
+
+    @VisibleForTesting
+    WindowMagnificationAnimationController(Context context,
+            WindowMagnificationController controller, ValueAnimator valueAnimator) {
+        mContext = context;
+        mController = controller;
+        mValueAnimator = valueAnimator;
+        mValueAnimator.addUpdateListener(this);
+        mValueAnimator.addListener(this);
+    }
+
+    /**
+     * Wraps {@link WindowMagnificationController#enableWindowMagnification(float, float, float)}
+     * with transition animation. If the window magnification is not enabled, the scale will start
+     * from 1.0 and the center won't be changed during the animation. If {@link #mState} is
+     * {@code STATE_DISABLING}, the animation runs in reverse.
+     *
+     * @param scale   the target scale, or {@link Float#NaN} to leave unchanged.
+     * @param centerX the screen-relative X coordinate around which to center,
+     *                or {@link Float#NaN} to leave unchanged.
+     * @param centerY the screen-relative Y coordinate around which to center,
+     *                or {@link Float#NaN} to leave unchanged.
+     *
+     * @see #onAnimationUpdate(ValueAnimator)
+     */
+    void enableWindowMagnification(float scale, float centerX, float centerY) {
+        if (mState == STATE_ENABLING) {
+            mValueAnimator.cancel();
+        }
+        setupEnableAnimationSpecs(scale, centerX, centerY);
+
+        if (mEndSpec.equals(mStartSpec)) {
+            setState(STATE_ENABLED);
+        } else {
+            if (mState == STATE_DISABLING) {
+                mValueAnimator.reverse();
+            } else {
+                mValueAnimator.start();
+            }
+            setState(STATE_ENABLING);
+        }
+    }
+
+    private void setupEnableAnimationSpecs(float scale, float centerX, float centerY) {
+        final float currentScale = mController.getScale();
+        final float currentCenterX = mController.getCenterX();
+        final float currentCenterY = mController.getCenterY();
+
+        if (mState == STATE_DISABLED) {
+            //We don't need to offset the center during the animation.
+            mStartSpec.set(/* scale*/ 1.0f, centerX, centerY);
+            mEndSpec.set(Float.isNaN(scale) ? mContext.getResources().getInteger(
+                    R.integer.magnification_default_scale) : scale, centerX, centerY);
+        } else {
+            mStartSpec.set(currentScale, currentCenterX, currentCenterY);
+            mEndSpec.set(Float.isNaN(scale) ? currentScale : scale,
+                    Float.isNaN(centerX) ? currentCenterX : centerX,
+                    Float.isNaN(centerY) ? currentCenterY : centerY);
+        }
+        if (DEBUG) {
+            Log.d(TAG, "SetupEnableAnimationSpecs : mStartSpec = " + mStartSpec + ", endSpec = "
+                    + mEndSpec);
+        }
+    }
+
+    /**
+     * Wraps {@link WindowMagnificationController#setScale(float)}. If the animation is
+     * running, it has no effect.
+     */
+    void setScale(float scale) {
+        if (mValueAnimator.isRunning()) {
+            return;
+        }
+        mController.setScale(scale);
+    }
+
+    /**
+     * Wraps {@link WindowMagnificationController#deleteWindowMagnification()}} with transition
+     * animation. If the window magnification is enabling, it runs the animation in reverse.
+     */
+    void deleteWindowMagnification() {
+        if (mState == STATE_DISABLED || mState == STATE_DISABLING) {
+            return;
+        }
+        mStartSpec.set(/* scale*/ 1.0f, Float.NaN, Float.NaN);
+        mEndSpec.set(/* scale*/ mController.getScale(), Float.NaN, Float.NaN);
+
+        mValueAnimator.reverse();
+        setState(STATE_DISABLING);
+    }
+
+    /**
+     * Wraps {@link WindowMagnificationController#moveWindowMagnifier(float, float)}. If the
+     * animation is running, it has no effect.
+     * @param offsetX the amount in pixels to offset the window magnifier in the X direction, in
+     *                current screen pixels.
+     * @param offsetY the amount in pixels to offset the window magnifier in the Y direction, in
+     *                current screen pixels.
+     */
+    void moveWindowMagnifier(float offsetX, float offsetY) {
+        if (mValueAnimator.isRunning()) {
+            return;
+        }
+        mController.moveWindowMagnifier(offsetX, offsetY);
+    }
+
+    void onConfigurationChanged(int configDiff) {
+        mController.onConfigurationChanged(configDiff);
+    }
+
+    private void setState(@MagnificationState int state) {
+        if (DEBUG) {
+            Log.d(TAG, "setState from " + mState + " to " + state);
+        }
+        mState = state;
+    }
+
+    @Override
+    public void onAnimationStart(Animator animation) {
+    }
+
+    @Override
+    public void onAnimationEnd(Animator animation) {
+        if (mState == STATE_DISABLING) {
+            mController.deleteWindowMagnification();
+            setState(STATE_DISABLED);
+        } else if (mState == STATE_ENABLING) {
+            setState(STATE_ENABLED);
+        } else {
+            Log.w(TAG, "onAnimationEnd unexpected state:" + mState);
+        }
+    }
+
+    @Override
+    public void onAnimationCancel(Animator animation) {
+    }
+
+    @Override
+    public void onAnimationRepeat(Animator animation) {
+    }
+
+    @Override
+    public void onAnimationUpdate(ValueAnimator animation) {
+        final float fract = animation.getAnimatedFraction();
+        final float sentScale = mStartSpec.mScale + (mEndSpec.mScale - mStartSpec.mScale) * fract;
+        final float centerX =
+                mStartSpec.mCenterX + (mEndSpec.mCenterX - mStartSpec.mCenterX) * fract;
+        final float centerY =
+                mStartSpec.mCenterY + (mEndSpec.mCenterY - mStartSpec.mCenterY) * fract;
+        mController.enableWindowMagnification(sentScale, centerX, centerY);
+    }
+
+    private static ValueAnimator newValueAnimator(Resources resources) {
+        final ValueAnimator valueAnimator = new ValueAnimator();
+        valueAnimator.setDuration(
+                resources.getInteger(com.android.internal.R.integer.config_longAnimTime));
+        valueAnimator.setInterpolator(new AccelerateInterpolator(2.5f));
+        valueAnimator.setFloatValues(0.0f, 1.0f);
+        return valueAnimator;
+    }
+
+    private static class AnimationSpec {
+        private float mScale = Float.NaN;
+        private float mCenterX = Float.NaN;
+        private float mCenterY = Float.NaN;
+
+        @Override
+        public boolean equals(Object other) {
+            if (this == other) {
+                return true;
+            }
+
+            if (other == null || getClass() != other.getClass()) {
+                return false;
+            }
+
+            final AnimationSpec s = (AnimationSpec) other;
+            return mScale == s.mScale && mCenterX == s.mCenterX && mCenterY == s.mCenterY;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = (mScale != +0.0f ? Float.floatToIntBits(mScale) : 0);
+            result = 31 * result + (mCenterX != +0.0f ? Float.floatToIntBits(mCenterX) : 0);
+            result = 31 * result + (mCenterY != +0.0f ? Float.floatToIntBits(mCenterY) : 0);
+            return result;
+        }
+
+        void set(float scale, float centerX, float centerY) {
+            mScale = scale;
+            mCenterX = centerX;
+            mCenterY = centerY;
+        }
+
+        @Override
+        public String toString() {
+            return "AnimationSpec{"
+                    + "mScale=" + mScale
+                    + ", mCenterX=" + mCenterX
+                    + ", mCenterY=" + mCenterY
+                    + '}';
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 798b751..494a0f64 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -150,7 +150,7 @@
 
         mMirrorViewGeometryVsyncCallback =
                 l -> {
-                    if (mMirrorView != null && mMirrorSurface != null) {
+                    if (isWindowVisible() && mMirrorSurface != null) {
                         calculateSourceBounds(mMagnificationFrame, mScale);
                         // The final destination for the magnification surface should be at 0,0
                         // since the ViewRootImpl's position will change
@@ -203,13 +203,13 @@
      * @param configDiff a bit mask of the differences between the configurations
      */
     void onConfigurationChanged(int configDiff) {
+        if (!isWindowVisible()) {
+            return;
+        }
         if ((configDiff & ActivityInfo.CONFIG_DENSITY) != 0) {
             updateDimensions();
-            // TODO(b/145780606): update toggle button UI.
-            if (mMirrorView != null) {
-                mWm.removeView(mMirrorView);
-                createMirrorWindow();
-            }
+            mWm.removeView(mMirrorView);
+            createMirrorWindow();
         } else if ((configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0) {
             onRotate();
         }
@@ -502,7 +502,7 @@
     /**
      * Enables window magnification with specified parameters.
      *
-     * @param scale   the target scale
+     * @param scale   the target scale, or {@link Float#NaN} to leave unchanged
      * @param centerX the screen-relative X coordinate around which to center,
      *                or {@link Float#NaN} to leave unchanged.
      * @param centerY the screen-relative Y coordinate around which to center,
@@ -513,10 +513,10 @@
                 : centerX - mMagnificationFrame.exactCenterX();
         final float offsetY = Float.isNaN(centerY) ? 0
                 : centerY - mMagnificationFrame.exactCenterY();
-        mScale = scale;
+        mScale = Float.isNaN(scale) ? mScale : scale;
         setMagnificationFrameBoundary();
         updateMagnificationFramePosition((int) offsetX, (int) offsetY);
-        if (mMirrorView == null) {
+        if (!isWindowVisible()) {
             createMirrorWindow();
             showControls();
         } else {
@@ -527,10 +527,10 @@
     /**
      * Sets the scale of the magnified region if it's visible.
      *
-     * @param scale the target scale
+     * @param scale the target scale, or {@link Float#NaN} to leave unchanged
      */
     void setScale(float scale) {
-        if (mMirrorView == null || mScale == scale) {
+        if (!isWindowVisible() || mScale == scale) {
             return;
         }
         enableWindowMagnification(scale, Float.NaN, Float.NaN);
@@ -552,4 +552,35 @@
             modifyWindowMagnification(mTransaction);
         }
     }
+
+    /**
+     * Gets the scale.
+     * @return {@link Float#NaN} if the window is invisible.
+     */
+    float getScale() {
+        return isWindowVisible() ? mScale : Float.NaN;
+    }
+
+    /**
+     * Returns the screen-relative X coordinate of the center of the magnified bounds.
+     *
+     * @return the X coordinate. {@link Float#NaN} if the window is invisible.
+     */
+    float getCenterX() {
+        return isWindowVisible() ? mMagnificationFrame.exactCenterX() : Float.NaN;
+    }
+
+    /**
+     * Returns the screen-relative Y coordinate of the center of the magnified bounds.
+     *
+     * @return the Y coordinate. {@link Float#NaN} if the window is invisible.
+     */
+    float getCenterY() {
+        return isWindowVisible() ? mMagnificationFrame.exactCenterY() : Float.NaN;
+    }
+
+    //The window is visible when it is existed.
+    private boolean isWindowVisible() {
+        return mMirrorView != null;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java
index 7e5b426..93a8df4 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java
@@ -25,7 +25,9 @@
     private int mUid;
     private String mPackageName;
     private long mTimeStarted;
-    private String mState;
+    private StringBuilder mState;
+    // This is only used for items with mCode == AppOpsManager.OP_RECORD_AUDIO
+    private boolean mSilenced;
 
     public AppOpItem(int code, int uid, String packageName, long timeStarted) {
         this.mCode = code;
@@ -36,9 +38,8 @@
                 .append("AppOpItem(")
                 .append("Op code=").append(code).append(", ")
                 .append("UID=").append(uid).append(", ")
-                .append("Package name=").append(packageName)
-                .append(")")
-                .toString();
+                .append("Package name=").append(packageName).append(", ")
+                .append("Paused=");
     }
 
     public int getCode() {
@@ -57,8 +58,16 @@
         return mTimeStarted;
     }
 
+    public void setSilenced(boolean silenced) {
+        mSilenced = silenced;
+    }
+
+    public boolean isSilenced() {
+        return mSilenced;
+    }
+
     @Override
     public String toString() {
-        return mState;
+        return mState.append(mSilenced).append(")").toString();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
index 6512624..8187a223 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
@@ -19,6 +19,8 @@
 import android.app.AppOpsManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.media.AudioManager;
+import android.media.AudioRecordingConfiguration;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.UserHandle;
@@ -63,6 +65,7 @@
     private static final boolean DEBUG = false;
 
     private final AppOpsManager mAppOps;
+    private final AudioManager mAudioManager;
     private H mBGHandler;
     private final List<AppOpsController.Callback> mCallbacks = new ArrayList<>();
     private final SparseArray<Set<Callback>> mCallbacksByCode = new SparseArray<>();
@@ -73,6 +76,9 @@
     private final List<AppOpItem> mActiveItems = new ArrayList<>();
     @GuardedBy("mNotedItems")
     private final List<AppOpItem> mNotedItems = new ArrayList<>();
+    @GuardedBy("mActiveItems")
+    private final SparseArray<ArrayList<AudioRecordingConfiguration>> mRecordingsByUid =
+            new SparseArray<>();
 
     protected static final int[] OPS = new int[] {
             AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION,
@@ -88,7 +94,8 @@
             Context context,
             @Background Looper bgLooper,
             DumpManager dumpManager,
-            PermissionFlagsCache cache
+            PermissionFlagsCache cache,
+            AudioManager audioManager
     ) {
         mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
         mFlagsCache = cache;
@@ -97,6 +104,7 @@
         for (int i = 0; i < numOps; i++) {
             mCallbacksByCode.put(OPS[i], new ArraySet<>());
         }
+        mAudioManager = audioManager;
         dumpManager.registerDumpable(TAG, this);
     }
 
@@ -111,12 +119,19 @@
         if (listening) {
             mAppOps.startWatchingActive(OPS, this);
             mAppOps.startWatchingNoted(OPS, this);
+            mAudioManager.registerAudioRecordingCallback(mAudioRecordingCallback, mBGHandler);
+            mBGHandler.post(() -> mAudioRecordingCallback.onRecordingConfigChanged(
+                    mAudioManager.getActiveRecordingConfigurations()));
+
         } else {
             mAppOps.stopWatchingActive(this);
             mAppOps.stopWatchingNoted(this);
+            mAudioManager.unregisterAudioRecordingCallback(mAudioRecordingCallback);
+
             mBGHandler.removeCallbacksAndMessages(null); // null removes all
             synchronized (mActiveItems) {
                 mActiveItems.clear();
+                mRecordingsByUid.clear();
             }
             synchronized (mNotedItems) {
                 mNotedItems.clear();
@@ -189,9 +204,12 @@
             AppOpItem item = getAppOpItemLocked(mActiveItems, code, uid, packageName);
             if (item == null && active) {
                 item = new AppOpItem(code, uid, packageName, System.currentTimeMillis());
+                if (code == AppOpsManager.OP_RECORD_AUDIO) {
+                    item.setSilenced(isAnyRecordingPausedLocked(uid));
+                }
                 mActiveItems.add(item);
                 if (DEBUG) Log.w(TAG, "Added item: " + item.toString());
-                return true;
+                return !item.isSilenced();
             } else if (item != null && !active) {
                 mActiveItems.remove(item);
                 if (DEBUG) Log.w(TAG, "Removed item: " + item.toString());
@@ -215,7 +233,7 @@
             active = getAppOpItemLocked(mActiveItems, code, uid, packageName) != null;
         }
         if (!active) {
-            notifySuscribers(code, uid, packageName, false);
+            notifySuscribersWorker(code, uid, packageName, false);
         }
     }
 
@@ -324,7 +342,7 @@
                 AppOpItem item = mActiveItems.get(i);
                 if ((userId == UserHandle.USER_ALL
                         || UserHandle.getUserId(item.getUid()) == userId)
-                        && isUserVisible(item)) {
+                        && isUserVisible(item) && !item.isSilenced()) {
                     list.add(item);
                 }
             }
@@ -343,6 +361,10 @@
         return list;
     }
 
+    private void notifySuscribers(int code, int uid, String packageName, boolean active) {
+        mBGHandler.post(() -> notifySuscribersWorker(code, uid, packageName, active));
+    }
+
     @Override
     public void onOpActiveChanged(int code, int uid, String packageName, boolean active) {
         if (DEBUG) {
@@ -360,7 +382,7 @@
         // If active is false, we only send the update if the op is not actively noted (prevent
         // early removal)
         if (!alsoNoted) {
-            mBGHandler.post(() -> notifySuscribers(code, uid, packageName, active));
+            notifySuscribers(code, uid, packageName, active);
         }
     }
 
@@ -378,11 +400,11 @@
             alsoActive = getAppOpItemLocked(mActiveItems, code, uid, packageName) != null;
         }
         if (!alsoActive) {
-            mBGHandler.post(() -> notifySuscribers(code, uid, packageName, true));
+            notifySuscribers(code, uid, packageName, true);
         }
     }
 
-    private void notifySuscribers(int code, int uid, String packageName, boolean active) {
+    private void notifySuscribersWorker(int code, int uid, String packageName, boolean active) {
         if (mCallbacksByCode.contains(code) && isUserVisible(code, uid, packageName)) {
             if (DEBUG) Log.d(TAG, "Notifying of change in package " + packageName);
             for (Callback cb: mCallbacksByCode.get(code)) {
@@ -408,6 +430,61 @@
 
     }
 
+    private boolean isAnyRecordingPausedLocked(int uid) {
+        List<AudioRecordingConfiguration> configs = mRecordingsByUid.get(uid);
+        if (configs == null) return false;
+        int configsNum = configs.size();
+        for (int i = 0; i < configsNum; i++) {
+            AudioRecordingConfiguration config = configs.get(i);
+            if (config.isClientSilenced()) return true;
+        }
+        return false;
+    }
+
+    private void updateRecordingPausedStatus() {
+        synchronized (mActiveItems) {
+            int size = mActiveItems.size();
+            for (int i = 0; i < size; i++) {
+                AppOpItem item = mActiveItems.get(i);
+                if (item.getCode() == AppOpsManager.OP_RECORD_AUDIO) {
+                    boolean paused = isAnyRecordingPausedLocked(item.getUid());
+                    if (item.isSilenced() != paused) {
+                        item.setSilenced(paused);
+                        notifySuscribers(
+                                item.getCode(),
+                                item.getUid(),
+                                item.getPackageName(),
+                                !item.isSilenced()
+                        );
+                    }
+                }
+            }
+        }
+    }
+
+    private AudioManager.AudioRecordingCallback mAudioRecordingCallback =
+            new AudioManager.AudioRecordingCallback() {
+        @Override
+        public void onRecordingConfigChanged(List<AudioRecordingConfiguration> configs) {
+            synchronized (mActiveItems) {
+                mRecordingsByUid.clear();
+                final int recordingsCount = configs.size();
+                for (int i = 0; i < recordingsCount; i++) {
+                    AudioRecordingConfiguration recording = configs.get(i);
+
+                    ArrayList<AudioRecordingConfiguration> recordings = mRecordingsByUid.get(
+                            recording.getClientUid());
+                    if (recordings == null) {
+                        recordings = new ArrayList<>();
+                        mRecordingsByUid.put(recording.getClientUid(), recordings);
+                    }
+                    recordings.add(recording);
+                }
+            }
+            updateRecordingPausedStatus();
+        }
+    };
+
     protected class H extends Handler {
         H(Looper looper) {
             super(looper);
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java
index 7ae3e73..2df48fc 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java
@@ -35,7 +35,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.shared.system.QuickStepContract;
-import com.android.systemui.statusbar.phone.NavigationModeController;
+import com.android.systemui.navigationbar.NavigationModeController;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
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/AssistModule.java b/packages/SystemUI/src/com/android/systemui/assist/AssistModule.java
index 6f5a17d..0dc06f2 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistModule.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistModule.java
@@ -25,7 +25,7 @@
 import androidx.slice.Clock;
 
 import com.android.internal.app.AssistUtils;
-import com.android.systemui.statusbar.NavigationBarController;
+import com.android.systemui.navigationbar.NavigationBarController;
 
 import java.util.EnumMap;
 import java.util.Map;
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistantSessionEvent.kt b/packages/SystemUI/src/com/android/systemui/assist/AssistantSessionEvent.kt
index 8b953fa..be089b1 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistantSessionEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistantSessionEvent.kt
@@ -21,7 +21,7 @@
 
 enum class AssistantSessionEvent(private val id: Int) : UiEventLogger.UiEventEnum {
     @UiEvent(doc = "Unknown assistant session event")
-    ASSISTANT_SESSION_UNKNOWN(523),
+    ASSISTANT_SESSION_UNKNOWN(0),
 
     @UiEvent(doc = "Assistant session dismissed due to timeout")
     ASSISTANT_SESSION_TIMEOUT_DISMISS(524),
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..722e3124 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
@@ -41,7 +41,7 @@
 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.navigationbar.NavigationBarController;
 
 import java.util.Locale;
 
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..ade106f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -280,7 +280,7 @@
                     fpm.getSensorProperties();
             for (FingerprintSensorProperties props : fingerprintSensorProperties) {
                 if (props.sensorType == FingerprintSensorProperties.TYPE_UDFPS) {
-                    mUdfpsController = new UdfpsController(mContext, mWindowManager);
+                    mUdfpsController = new UdfpsController(mContext);
                     break;
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
index 95bbea1..d4e6506b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.biometrics;
 
+import android.annotation.NonNull;
 import android.content.Context;
 import android.os.UserHandle;
 import android.text.InputType;
@@ -27,7 +28,9 @@
 import android.widget.TextView;
 
 import com.android.internal.widget.LockPatternChecker;
+import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockscreenCredential;
+import com.android.internal.widget.VerifyCredentialResponse;
 import com.android.systemui.R;
 
 /**
@@ -104,18 +107,21 @@
                 return;
             }
 
+            // Request LockSettingsService to return the Gatekeeper Password in the
+            // VerifyCredentialResponse so that we can request a Gatekeeper HAT with the
+            // Gatekeeper Password and operationId.
             mPendingLockCheck = LockPatternChecker.verifyCredential(mLockPatternUtils,
-                    password, mOperationId, mEffectiveUserId, this::onCredentialVerified);
+                    password, mEffectiveUserId, LockPatternUtils.VERIFY_FLAG_RETURN_GK_PW,
+                    this::onCredentialVerified);
         }
     }
 
     @Override
-    protected void onCredentialVerified(byte[] attestation, int timeoutMs) {
-        super.onCredentialVerified(attestation, timeoutMs);
+    protected void onCredentialVerified(@NonNull VerifyCredentialResponse response,
+            int timeoutMs) {
+        super.onCredentialVerified(response, timeoutMs);
 
-        final boolean matched = attestation != null;
-
-        if (matched) {
+        if (response.isMatched()) {
             mImm.hideSoftInputFromWindow(getWindowToken(), 0 /* flags */);
         } else {
             mPasswordField.setText("");
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java
index 6d16f43..ad89ae8 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.biometrics;
 
+import android.annotation.NonNull;
 import android.content.Context;
 import android.util.AttributeSet;
 
@@ -23,6 +24,7 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockPatternView;
 import com.android.internal.widget.LockscreenCredential;
+import com.android.internal.widget.VerifyCredentialResponse;
 import com.android.systemui.R;
 
 import java.util.List;
@@ -61,22 +63,25 @@
 
             if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
                 // Pattern size is less than the minimum, do not count it as a failed attempt.
-                onPatternVerified(null /* attestation */, 0 /* timeoutMs */);
+                onPatternVerified(VerifyCredentialResponse.ERROR, 0 /* timeoutMs */);
                 return;
             }
 
             try (LockscreenCredential credential = LockscreenCredential.createPattern(pattern)) {
+                // Request LockSettingsService to return the Gatekeeper Password in the
+                // VerifyCredentialResponse so that we can request a Gatekeeper HAT with the
+                // Gatekeeper Password and operationId.
                 mPendingLockCheck = LockPatternChecker.verifyCredential(
                         mLockPatternUtils,
                         credential,
-                        mOperationId,
                         mEffectiveUserId,
+                        LockPatternUtils.VERIFY_FLAG_RETURN_GK_PW,
                         this::onPatternVerified);
             }
         }
 
-        private void onPatternVerified(byte[] attestation, int timeoutMs) {
-            AuthCredentialPatternView.this.onCredentialVerified(attestation, timeoutMs);
+        private void onPatternVerified(@NonNull VerifyCredentialResponse response, int timeoutMs) {
+            AuthCredentialPatternView.this.onCredentialVerified(response, timeoutMs);
             if (timeoutMs > 0) {
                 mLockPatternView.setEnabled(false);
             } else {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
index 2695c69..a8f6f85 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.biometrics;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.AlertDialog;
 import android.app.admin.DevicePolicyManager;
 import android.content.Context;
@@ -38,12 +41,10 @@
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
-import androidx.annotation.IntDef;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 import androidx.annotation.StringRes;
 
 import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.VerifyCredentialResponse;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 
@@ -283,14 +284,18 @@
 
     protected void onErrorTimeoutFinish() {}
 
-    protected void onCredentialVerified(byte[] attestation, int timeoutMs) {
-
-        final boolean matched = attestation != null;
-
-        if (matched) {
+    protected void onCredentialVerified(@NonNull VerifyCredentialResponse response, int timeoutMs) {
+        if (response.isMatched()) {
             mClearErrorRunnable.run();
             mLockPatternUtils.userPresent(mEffectiveUserId);
-            mCallback.onCredentialMatched(attestation);
+
+            // The response passed into this method contains the Gatekeeper Password. We still
+            // have to request Gatekeeper to create a Hardware Auth Token with the
+            // Gatekeeper Password and Challenge (keystore operationId in this case)
+            final VerifyCredentialResponse gkResponse = mLockPatternUtils.verifyGatekeeperPassword(
+                    response.getGatekeeperPw(), mOperationId, mEffectiveUserId);
+
+            mCallback.onCredentialMatched(gkResponse.getGatekeeperHAT());
         } else {
             if (timeoutMs > 0) {
                 mHandler.removeCallbacks(mClearErrorRunnable);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 739c2b1..e3126b8 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -17,21 +17,27 @@
 package com.android.systemui.biometrics;
 
 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.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 java.io.FileWriter;
@@ -43,17 +49,31 @@
  */
 class UdfpsController {
     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 UdfpsView mView;
+    private final WindowManager.LayoutParams mLayoutParams;
+    // Debugfs path to control the high-brightness mode.
+    private final String mHbmPath;
+    private final String mHbmEnableCommand;
+    private final String mHbmDisableCommand;
+    // 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 {
@@ -70,22 +90,23 @@
 
     @SuppressLint("ClickableViewAccessibility")
     private final UdfpsView.OnTouchListener mOnTouchListener = (v, event) -> {
+        UdfpsView view = (UdfpsView) v;
         switch (event.getAction()) {
             case MotionEvent.ACTION_DOWN:
             case MotionEvent.ACTION_MOVE:
-                boolean isValidTouch = mView.isValidTouch(event.getX(), event.getY(),
+                boolean isValidTouch = view.isValidTouch(event.getX(), event.getY(),
                         event.getPressure());
-                if (!mView.isFingerDown() && isValidTouch) {
+                if (!view.isFingerDown() && isValidTouch) {
                     onFingerDown((int) event.getX(), (int) event.getY(), event.getTouchMinor(),
                             event.getTouchMajor());
-                } else if (mView.isFingerDown() && !isValidTouch) {
+                } else if (view.isFingerDown() && !isValidTouch) {
                     onFingerUp();
                 }
                 return true;
 
             case MotionEvent.ACTION_UP:
             case MotionEvent.ACTION_CANCEL:
-                if (mView.isFingerDown()) {
+                if (view.isFingerDown()) {
                     onFingerUp();
                 }
                 return true;
@@ -95,43 +116,51 @@
         }
     };
 
-    UdfpsController(Context context, WindowManager windowManager) {
-        mContext = context;
+    UdfpsController(Context context) {
         mFingerprintManager = context.getSystemService(FingerprintManager.class);
-        mWindowManager = windowManager;
+        mWindowManager = context.getSystemService(WindowManager.class);
+        mContentResolver = context.getContentResolver();
         mHandler = new Handler(Looper.getMainLooper());
-        start();
-    }
 
-    private void start() {
-        Log.v(TAG, "start");
-
-        Point displaySize = new Point();
-        mWindowManager.getDefaultDisplay().getRealSize(displaySize);
-        // 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);
+        mLayoutParams = createLayoutParams(context);
+        LinearLayout layout = new LinearLayout(context);
         layout.setLayoutParams(mLayoutParams);
-        mView = (UdfpsView) LayoutInflater.from(mContext).inflate(R.layout.udfps_view, layout,
+        mView = (UdfpsView) LayoutInflater.from(context).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);
+        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);
+
+        // 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, 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;
@@ -162,7 +191,34 @@
         });
     }
 
+    // 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());
         try {
             FileWriter fw = new FileWriter(mHbmPath);
             fw.write(mHbmEnableCommand);
@@ -185,4 +241,54 @@
             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_NOT_TOUCH_MODAL
+                        | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
+                PixelFormat.TRANSLUCENT);
+        lp.setTitle(TAG);
+        lp.windowAnimations = 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..d4e86d5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
@@ -79,7 +79,7 @@
 
         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 */);
@@ -136,6 +136,10 @@
                 && y < (mSensorY + mSensorRadius * mSensorTouchAreaCoefficient);
     }
 
+    void setScrimAlpha(int alpha) {
+        mScrimPaint.setAlpha(alpha);
+    }
+
     boolean isFingerDown() {
         return mIsFingerDown;
     }
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/dagger/BubbleModule.java b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
index 10d301d..fcb215c 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
@@ -30,11 +30,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/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
index 3d31070..21bcfcd 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,38 @@
 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.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.navigationbar.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;
 
@@ -159,8 +175,49 @@
     @Singleton
     @Provides
     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
@@ -230,6 +287,12 @@
     }
 
     /** */
+    @Provides
+    public SystemActions providesSystemActions(Context context) {
+        return new SystemActions(context);
+    }
+
+    /** */
     @Singleton
     @Provides
     public Choreographer providesChoreographer() {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
similarity index 75%
rename from packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java
rename to packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
index 9fdbb6d..9c55095 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
@@ -16,9 +16,6 @@
 
 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;
@@ -26,14 +23,12 @@
 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;
@@ -42,6 +37,7 @@
 /**
  * Root component for Dagger injection.
  */
+// TODO(b/162923491): Move most of these modules to SysUIComponent.
 @Singleton
 @Component(modules = {
         DefaultComponentBinder.class,
@@ -52,21 +48,32 @@
         SystemServicesModule.class,
         SystemUIBinder.class,
         SystemUIModule.class,
-        SystemUIDefaultModule.class})
-public interface SystemUIRootComponent {
+        SystemUIDefaultModule.class,
+        WMModule.class})
+public interface GlobalRootComponent {
 
     /**
-     * Builder for a SystemUIRootComponent.
+     * Builder for a GlobalRootComponent.
      */
     @Component.Builder
     interface Builder {
         @BindsInstance
         Builder context(Context context);
 
-        SystemUIRootComponent build();
+        GlobalRootComponent build();
     }
 
     /**
+     * Builder for a WMComponent.
+     */
+    WMComponent.Builder getWMComponentBuilder();
+
+    /**
+     * Builder for a SysuiComponent.
+     */
+    SysUIComponent.Builder getSysUIComponent();
+
+    /**
      * Provides a BootCompleteCache.
      */
     @Singleton
@@ -95,28 +102,15 @@
     DumpManager createDumpManager();
 
     /**
-     * FragmentCreator generates all Fragments that need injection.
-     */
-    @Singleton
-    FragmentService.FragmentCreator createFragmentCreator();
-
-
-    /**
      * Creates a InitController.
      */
     @Singleton
     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 +120,5 @@
     /**
      * Member injection into the supplied argument.
      */
-    void inject(ContentProvider contentProvider);
-
-    /**
-     * Member injection into the supplied argument.
-     */
     void inject(KeyguardSliceProvider keyguardSliceProvider);
 }
diff --git a/services/core/java/com/android/server/protolog/common/BitmaskConversionException.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
similarity index 66%
copy from services/core/java/com/android/server/protolog/common/BitmaskConversionException.java
copy to packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 7bb27b2..9b527bf 100644
--- a/services/core/java/com/android/server/protolog/common/BitmaskConversionException.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -14,13 +14,22 @@
  * limitations under the License.
  */
 
-package com.android.server.protolog.common;
+package com.android.systemui.dagger;
+
+import dagger.Subcomponent;
 
 /**
- * Error while converting a bitmask representing a list of LogDataTypes.
+ * Dagger Subcomponent for Core SysUI.
  */
-public class BitmaskConversionException extends RuntimeException {
-    BitmaskConversionException(String msg) {
-        super(msg);
+@SysUISingleton
+@Subcomponent(modules = {})
+public interface SysUIComponent {
+
+    /**
+     * Builder for a SysUIComponent.
+     */
+    @Subcomponent.Builder
+    interface Builder {
+        SysUIComponent build();
     }
 }
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/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
index 803e56d..b4e6e0e 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;
@@ -72,7 +75,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,
+            WindowManagerShellModule.class
+        },
+        subcomponents = {
+            SysUIComponent.class
+        })
 public abstract class SystemUIDefaultModule {
 
     @Singleton
@@ -92,12 +102,22 @@
 
     @Provides
     @Singleton
-    static BatteryController provideBatteryController(Context context,
-            EnhancedEstimates enhancedEstimates, PowerManager powerManager,
-            BroadcastDispatcher broadcastDispatcher, @Main Handler mainHandler,
+    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;
     }
@@ -154,5 +174,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 fce545b..a46e182 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;
@@ -38,6 +40,7 @@
 import com.android.systemui.statusbar.notification.people.PeopleHubModule;
 import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent;
 import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
+import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent;
 import com.android.systemui.statusbar.phone.KeyguardLiftController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
@@ -63,6 +66,7 @@
 @Module(includes = {
             AssistModule.class,
             ConcurrencyModule.class,
+            DemoModeModule.class,
             LogModule.class,
             PeopleHubModule.class,
             SensorModule.class,
@@ -72,7 +76,9 @@
         subcomponents = {StatusBarComponent.class,
                 NotificationRowComponent.class,
                 DozeComponent.class,
-                ExpandableNotificationRowComponent.class})
+                ExpandableNotificationRowComponent.class,
+                NotificationShelfComponent.class,
+                FragmentService.FragmentCreator.class})
 public abstract class SystemUIModule {
 
     @Binds
diff --git a/services/core/java/com/android/server/protolog/common/BitmaskConversionException.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
similarity index 66%
copy from services/core/java/com/android/server/protolog/common/BitmaskConversionException.java
copy to packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index 7bb27b2..929b61a 100644
--- a/services/core/java/com/android/server/protolog/common/BitmaskConversionException.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -14,13 +14,22 @@
  * limitations under the License.
  */
 
-package com.android.server.protolog.common;
+package com.android.systemui.dagger;
+
+import dagger.Subcomponent;
 
 /**
- * Error while converting a bitmask representing a list of LogDataTypes.
+ * Dagger Subcomponent for WindowManager.
  */
-public class BitmaskConversionException extends RuntimeException {
-    BitmaskConversionException(String msg) {
-        super(msg);
+@WMSingleton
+@Subcomponent(modules = {})
+public interface WMComponent {
+
+    /**
+     * Builder for a WMComponent.
+     */
+    @Subcomponent.Builder
+    interface Builder {
+        WMComponent build();
     }
 }
diff --git a/media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl b/packages/SystemUI/src/com/android/systemui/dagger/WMModule.java
similarity index 67%
copy from media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl
copy to packages/SystemUI/src/com/android/systemui/dagger/WMModule.java
index b1f99e6..2894780 100644
--- a/media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMModule.java
@@ -14,17 +14,13 @@
  * limitations under the License.
  */
 
-package android.media;
+package com.android.systemui.dagger;
 
-import android.media.AudioDeviceAttributes;
+import dagger.Module;
 
 /**
- * AIDL for AudioService to signal audio strategy-preferred device updates.
- *
- * {@hide}
+ * Dagger module for including the WMComponent.
  */
-oneway interface IStrategyPreferredDeviceDispatcher {
-
-    void dispatchPrefDeviceChanged(int strategyId, in AudioDeviceAttributes device);
-
+@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..9270b88
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/demomode/dagger/DemoModeModule.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.demomode.dagger;
+
+import android.content.Context;
+
+import com.android.systemui.demomode.DemoModeController;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.util.settings.GlobalSettings;
+
+import javax.inject.Singleton;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Dagger module providing {@link DemoModeController}
+ */
+@Module
+public abstract class DemoModeModule {
+    /** Provides DemoModeController */
+    @Provides
+    @Singleton
+    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/fragments/FragmentService.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
index ae20076..e888869 100644
--- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
+++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
@@ -21,9 +21,7 @@
 import android.view.View;
 
 import com.android.systemui.Dumpable;
-import com.android.systemui.dagger.SystemUIRootComponent;
 import com.android.systemui.qs.QSFragment;
-import com.android.systemui.statusbar.phone.NavigationBarFragment;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import java.io.FileDescriptor;
@@ -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/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/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/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 83c95b7..fc789a6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -29,9 +29,10 @@
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.statusbar.phone.NavigationModeController;
+import com.android.systemui.navigationbar.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;
 
@@ -64,7 +65,8 @@
             TrustManager trustManager,
             @UiBackground Executor uiBgExecutor,
             DeviceConfigProxy deviceConfig,
-            NavigationModeController navigationModeController) {
+            NavigationModeController navigationModeController,
+            InjectionInflationController injectionInflationController) {
         return new KeyguardViewMediator(
                 context,
                 falsingManager,
@@ -78,6 +80,7 @@
                 powerManager,
                 trustManager,
                 deviceConfig,
-                navigationModeController);
+                navigationModeController,
+                injectionInflationController);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index 7f610d1..b993faa 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -10,6 +10,7 @@
 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.qualifiers.Main
 import com.android.systemui.plugins.ActivityStarter
@@ -21,6 +22,7 @@
 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
@@ -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)
                 }
             }
@@ -167,20 +167,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 +221,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 +258,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 +275,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 +313,7 @@
             currentStartLocation = startLocation
             currentEndLocation = endLocation
             currentTransitionProgress = progress
-            for (mediaPlayer in mediaPlayers.values) {
+            for (mediaPlayer in MediaPlayerData.players()) {
                 updatePlayerToState(mediaPlayer, immediately)
             }
             maybeResetSettingsCog()
@@ -386,7 +362,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 +424,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 +446,7 @@
     }
 
     fun closeGuts() {
-        mediaPlayers.values.forEach {
+        MediaPlayerData.players().forEach {
             it.closeGuts(true)
         }
     }
@@ -497,3 +473,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/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index 8a51c85..bff334e 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
@@ -44,6 +45,7 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.statusbar.notification.MediaNotificationProcessor
 import com.android.systemui.statusbar.notification.row.HybridGroupManager
 import com.android.systemui.util.Assert
@@ -55,6 +57,8 @@
 import java.util.concurrent.Executor
 import javax.inject.Inject
 import javax.inject.Singleton
+import kotlin.collections.ArrayList
+import kotlin.collections.LinkedHashMap
 
 // URI fields to try loading album art from
 private val ART_URIS = arrayOf(
@@ -97,12 +101,24 @@
     dumpManager: DumpManager,
     mediaTimeoutListener: MediaTimeoutListener,
     mediaResumeListener: MediaResumeListener,
+    private val activityStarter: ActivityStarter,
     private var useMediaResumption: Boolean,
     private val useQsMediaPlayer: Boolean
 ) : Dumpable {
 
     private val listeners: 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(
@@ -113,10 +129,11 @@
         dumpManager: DumpManager,
         broadcastDispatcher: BroadcastDispatcher,
         mediaTimeoutListener: MediaTimeoutListener,
-        mediaResumeListener: MediaResumeListener
+        mediaResumeListener: MediaResumeListener,
+        activityStarter: ActivityStarter
     ) : this(context, backgroundExecutor, foregroundExecutor, mediaControllerFactory,
             broadcastDispatcher, dumpManager, mediaTimeoutListener, mediaResumeListener,
-            Utils.useMediaResumption(context), Utils.useQsMediaPlayer(context))
+            activityStarter, Utils.useMediaResumption(context), Utils.useQsMediaPlayer(context))
 
     private val appChangeReceiver = object : BroadcastReceiver() {
         override fun onReceive(context: Context, intent: Intent) {
@@ -328,7 +345,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
@@ -403,10 +421,13 @@
                 }
                 val runnable = if (action.actionIntent != null) {
                     Runnable {
-                        try {
-                            action.actionIntent.send()
-                        } catch (e: PendingIntent.CanceledException) {
-                            Log.d(TAG, "Intent canceled", e)
+                        if (action.isAuthenticationRequired()) {
+                            activityStarter.dismissKeyguardThenExecute ({
+                                var result = sendPendingIntent(action.actionIntent)
+                                result
+                            }, {}, true)
+                        } else {
+                            sendPendingIntent(action.actionIntent)
                         }
                     }
                 } else {
@@ -420,6 +441,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
@@ -427,8 +451,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))
         }
     }
 
@@ -449,6 +473,15 @@
         return null
     }
 
+    private fun sendPendingIntent(intent: PendingIntent): Boolean {
+        return try {
+            intent.send()
+            true
+        } catch (e: PendingIntent.CanceledException) {
+            Log.d(TAG, "Intent canceled", e)
+            false
+        }
+    }
     /**
      * Load a bitmap from a URI
      * @param uri the uri to load
@@ -527,8 +560,9 @@
     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),
@@ -564,6 +598,13 @@
         }
     }
 
+    private fun isBlockedFromResume(packageName: String?): Boolean {
+        if (packageName == null) {
+            return true
+        }
+        return appsBlockedFromResume.contains(packageName)
+    }
+
     fun setMediaResumptionEnabled(isEnabled: Boolean) {
         if (useMediaResumption == isEnabled) {
             return
@@ -606,6 +647,7 @@
             println("listeners: $listeners")
             println("mediaEntries: $mediaEntries")
             println("useMediaResumption: $useMediaResumption")
+            println("appsBlockedFromResume: $appsBlockedFromResume")
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
index 4ec746f..c41712c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
@@ -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/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..73cdde8
--- /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.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 javax.inject.Singleton;
+
+import dagger.Lazy;
+
+
+/** A controller to handle navigation bars. */
+@Singleton
+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..6e301c9 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;
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/OneHandedDisplayAreaOrganizer.java b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedDisplayAreaOrganizer.java
index 8550959..2e91697 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;
@@ -61,6 +62,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;
@@ -156,8 +159,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..ba50db1 100644
--- a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedGestureHandler.java
@@ -40,7 +40,7 @@
 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;
 
diff --git a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedManagerImpl.java b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedManagerImpl.java
index a3921ee..42524bb 100644
--- a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedManagerImpl.java
@@ -24,13 +24,13 @@
 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.model.SysUiState;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.TaskStackChangeListener;
@@ -50,6 +50,8 @@
 @Singleton
 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;
@@ -117,8 +119,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..279bbc8 100644
--- a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedSettingsUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedSettingsUtil.java
@@ -22,19 +22,13 @@
 import android.net.Uri;
 import android.provider.Settings;
 
-import com.android.internal.annotations.VisibleForTesting;
-
 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
 public final class OneHandedSettingsUtil {
     private static final String TAG = "OneHandedSettingsUtil";
 
@@ -65,11 +59,6 @@
      */
     public static final int ONE_HANDED_TIMEOUT_LONG_IN_SECONDS = 12;
 
-    @VisibleForTesting
-    @Inject
-    OneHandedSettingsUtil() {
-    }
-
     /**
      * Register one handed preference settings observer
      *
@@ -150,4 +139,6 @@
         pw.print(innerPrefix + "tapsAppToExit=");
         pw.println(getSettingsTapsAppToExit(resolver));
     }
+
+    private OneHandedSettingsUtil() {}
 }
diff --git a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedTutorialHandler.java b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedTutorialHandler.java
index b28730d0..de34b2e 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;
@@ -50,6 +51,8 @@
 @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;
@@ -81,8 +84,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..2767a8d 100644
--- a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedUI.java
+++ b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedUI.java
@@ -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/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
index 0167028..f1c8b0c 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
@@ -186,20 +186,23 @@
     private class PipManagerPinnedStackListener extends PinnedStackListener {
         @Override
         public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
-            if (mState == STATE_PIP) {
-                if (mImeVisible != imeVisible) {
-                    if (imeVisible) {
-                        // Save the IME height adjustment, and offset to not occlude the IME
-                        mPipBounds.offset(0, -imeHeight);
-                        mImeHeightAdjustment = imeHeight;
-                    } else {
-                        // Apply the inverse adjustment when the IME is hidden
-                        mPipBounds.offset(0, mImeHeightAdjustment);
+            mHandler.post(() -> {
+                mPipBoundsHandler.onImeVisibilityChanged(imeVisible, imeHeight);
+                if (mState == STATE_PIP) {
+                    if (mImeVisible != imeVisible) {
+                        if (imeVisible) {
+                            // Save the IME height adjustment, and offset to not occlude the IME
+                            mPipBounds.offset(0, -imeHeight);
+                            mImeHeightAdjustment = imeHeight;
+                        } else {
+                            // Apply the inverse adjustment when the IME is hidden
+                            mPipBounds.offset(0, mImeHeightAdjustment);
+                        }
+                        mImeVisible = imeVisible;
+                        resizePinnedStack(STATE_PIP);
                     }
-                    mImeVisible = imeVisible;
-                    resizePinnedStack(STATE_PIP);
                 }
-            }
+            });
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
index f8655cc..52a2cec 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
@@ -3,7 +3,9 @@
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.TypedArray;
+import android.graphics.drawable.Animatable2;
 import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
@@ -44,6 +46,24 @@
     private int mPosition = -1;
     private boolean mAnimating;
 
+    private final Animatable2.AnimationCallback mAnimationCallback =
+            new Animatable2.AnimationCallback() {
+
+                @Override
+                public void onAnimationEnd(Drawable drawable) {
+                    super.onAnimationEnd(drawable);
+                    if (DEBUG) Log.d(TAG, "onAnimationEnd - queued: " + mQueuedPositions.size());
+                    if (drawable instanceof AnimatedVectorDrawable) {
+                        ((AnimatedVectorDrawable) drawable).unregisterAnimationCallback(
+                                mAnimationCallback);
+                    }
+                    mAnimating = false;
+                    if (mQueuedPositions.size() != 0) {
+                        setPosition(mQueuedPositions.remove(0));
+                    }
+                }
+            };
+
     public PageIndicator(Context context, AttributeSet attrs) {
         super(context, attrs);
 
@@ -197,10 +217,8 @@
         final AnimatedVectorDrawable avd = (AnimatedVectorDrawable) getContext().getDrawable(res);
         imageView.setImageDrawable(avd);
         avd.forceAnimationOnUI();
+        avd.registerAnimationCallback(mAnimationCallback);
         avd.start();
-        // TODO: Figure out how to user an AVD animation callback instead, which doesn't
-        // seem to be working right now...
-        postDelayed(mAnimationDone, ANIMATION_DURATION);
     }
 
     private int getTransition(boolean fromB, boolean isMajorAState, boolean isMajor) {
@@ -264,15 +282,4 @@
             getChildAt(i).layout(left, 0, mPageIndicatorWidth + left, mPageIndicatorHeight);
         }
     }
-
-    private final Runnable mAnimationDone = new Runnable() {
-        @Override
-        public void run() {
-            if (DEBUG) Log.d(TAG, "onAnimationEnd - queued: " + mQueuedPositions.size());
-            mAnimating = false;
-            if (mQueuedPositions.size() != 0) {
-                setPosition(mQueuedPositions.remove(0));
-            }
-        }
-    };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index d03082e..d222489 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -61,12 +61,18 @@
 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.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 +87,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;
@@ -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..4da2bb6 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -26,6 +26,8 @@
 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.statusbar.policy.CallbackController;
@@ -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 c535230..6747281 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -50,6 +50,7 @@
 import android.media.MediaActionSound;
 import android.net.Uri;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
@@ -556,11 +557,18 @@
     private void takeScreenshotInternal(Consumer<Uri> finisher, Rect crop) {
         // copy the input Rect, since SurfaceControl.screenshot can mutate it
         Rect screenRect = new Rect(crop);
-        int rot = mDisplay.getRotation();
         int width = crop.width();
         int height = crop.height();
-        saveScreenshot(SurfaceControl.screenshot(crop, width, height, rot), finisher, screenRect,
-                Insets.NONE, true);
+        final IBinder displayToken = SurfaceControl.getInternalDisplayToken();
+        final SurfaceControl.DisplayCaptureArgs captureArgs =
+                new SurfaceControl.DisplayCaptureArgs.Builder(displayToken)
+                        .setSourceCrop(crop)
+                        .setSize(width, height)
+                        .build();
+        final SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =
+                SurfaceControl.captureDisplay(captureArgs);
+        final Bitmap screenshot = screenshotBuffer == null ? null : screenshotBuffer.asBitmap();
+        saveScreenshot(screenshot, finisher, screenRect, Insets.NONE, true);
     }
 
     private void saveScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect,
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/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/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/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index 6b023c0..9586795 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -161,7 +161,9 @@
                 ActivityManager.getService().resumeAppSwitches();
             } catch (RemoteException e) {
             }
-            return mCallback.handleRemoteViewClick(view, pendingIntent, () -> {
+            Notification.Action action = getActionFromView(view, entry, pendingIntent);
+            return mCallback.handleRemoteViewClick(view, pendingIntent,
+                    action == null ? false : action.isAuthenticationRequired(), () -> {
                 Pair<Intent, ActivityOptions> options = response.getLaunchOptions(view);
                 options.second.setLaunchWindowingMode(
                         WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
@@ -170,23 +172,45 @@
             });
         }
 
+        private @Nullable Notification.Action getActionFromView(View view,
+                NotificationEntry entry, PendingIntent actionIntent) {
+            Integer actionIndex = (Integer)
+                    view.getTag(com.android.internal.R.id.notification_action_index_tag);
+            if (actionIndex == null) {
+                return null;
+            }
+            if (entry == null) {
+                Log.w(TAG, "Couldn't determine notification for click.");
+                return null;
+            }
+
+            // Notification may be updated before this function is executed, and thus play safe
+            // here and verify that the action object is still the one that where the click happens.
+            StatusBarNotification statusBarNotification = entry.getSbn();
+            Notification.Action[] actions = statusBarNotification.getNotification().actions;
+            if (actions == null || actionIndex >= actions.length) {
+                Log.w(TAG, "statusBarNotification.getNotification().actions is null or invalid");
+                return null ;
+            }
+            final Notification.Action action =
+                    statusBarNotification.getNotification().actions[actionIndex];
+            if (!Objects.equals(action.actionIntent, actionIntent)) {
+                Log.w(TAG, "actionIntent does not match");
+                return null;
+            }
+            return action;
+        }
+
         private void logActionClick(
                 View view,
                 NotificationEntry entry,
                 PendingIntent actionIntent) {
-            Integer actionIndex = (Integer)
-                    view.getTag(com.android.internal.R.id.notification_action_index_tag);
-            if (actionIndex == null) {
-                // Custom action button, not logging.
+            Notification.Action action = getActionFromView(view, entry, actionIntent);
+            if (action == null) {
                 return;
             }
             ViewParent parent = view.getParent();
-            if (entry == null) {
-                Log.w(TAG, "Couldn't determine notification for click.");
-                return;
-            }
-            StatusBarNotification statusBarNotification = entry.getSbn();
-            String key = statusBarNotification.getKey();
+            String key = entry.getSbn().getKey();
             int buttonIndex = -1;
             // If this is a default template, determine the index of the button.
             if (view.getId() == com.android.internal.R.id.action0 &&
@@ -198,19 +222,6 @@
             final int rank = mEntryManager
                     .getActiveNotificationUnfiltered(key).getRanking().getRank();
 
-            // Notification may be updated before this function is executed, and thus play safe
-            // here and verify that the action object is still the one that where the click happens.
-            Notification.Action[] actions = statusBarNotification.getNotification().actions;
-            if (actions == null || actionIndex >= actions.length) {
-                Log.w(TAG, "statusBarNotification.getNotification().actions is null or invalid");
-                return;
-            }
-            final Notification.Action action =
-                    statusBarNotification.getNotification().actions[actionIndex];
-            if (!Objects.equals(action.actionIntent, actionIntent)) {
-                Log.w(TAG, "actionIntent does not match");
-                return;
-            }
             NotificationVisibility.NotificationLocation location =
                     NotificationLogger.getNotificationLocation(
                             mEntryManager.getActiveNotificationUnfiltered(key));
@@ -813,11 +824,12 @@
          *
          * @param view
          * @param pendingIntent
+         * @param appRequestedAuth
          * @param defaultHandler
          * @return  true iff the click was handled
          */
         boolean handleRemoteViewClick(View view, PendingIntent pendingIntent,
-                ClickHandler defaultHandler);
+                boolean appRequestedAuth, ClickHandler defaultHandler);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index 0445c98..cdf1f10 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -38,7 +38,6 @@
 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
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/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index d798692..8f3033e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -18,7 +18,6 @@
 
 import static com.android.systemui.Interpolators.FAST_OUT_SLOW_IN_REVERSE;
 import static com.android.systemui.statusbar.phone.NotificationIconContainer.IconState.NO_VALUE;
-import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
 
 import android.content.Context;
 import android.content.res.Configuration;
@@ -26,7 +25,6 @@
 import android.graphics.Rect;
 import android.os.SystemProperties;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.util.MathUtils;
 import android.view.DisplayCutout;
 import android.view.View;
@@ -36,10 +34,8 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.Dependency;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
@@ -48,14 +44,10 @@
 import com.android.systemui.statusbar.notification.stack.AmbientState;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.notification.stack.ViewState;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.NotificationIconContainer;
 
-import javax.inject.Inject;
-import javax.inject.Named;
-
 /**
  * A notification shelf view that is placed inside the notification scroller. It manages the
  * overflow icons that don't fit into the regular list anymore.
@@ -69,7 +61,6 @@
             = SystemProperties.getBoolean("debug.icon_scroll_animations", true);
     private static final int TAG_CONTINUOUS_CLIPPING = R.id.continuous_clipping_tag;
     private static final String TAG = "NotificationShelf";
-    private final KeyguardBypassController mBypassController;
 
     private NotificationIconContainer mShelfIcons;
     private int[] mTmp = new int[2];
@@ -77,9 +68,8 @@
     private int mIconAppearTopPadding;
     private float mHiddenShelfIconSize;
     private int mStatusBarHeight;
-    private int mStatusBarPaddingStart;
     private AmbientState mAmbientState;
-    private NotificationStackScrollLayout mHostLayout;
+    private NotificationStackScrollLayoutController mHostLayoutController;
     private int mMaxLayoutHeight;
     private int mPaddingBetweenElements;
     private int mNotGoneIndex;
@@ -88,7 +78,6 @@
     private int mScrollFastThreshold;
     private int mIconSize;
     private int mStatusBarState;
-    private float mMaxShelfEnd;
     private int mRelativeOffset;
     private boolean mInteractive;
     private float mOpenedAmount;
@@ -99,13 +88,10 @@
     private Rect mClipRect = new Rect();
     private int mCutoutHeight;
     private int mGapHeight;
+    private NotificationShelfController mController;
 
-    @Inject
-    public NotificationShelf(@Named(VIEW_CONTEXT) Context context,
-            AttributeSet attrs,
-            KeyguardBypassController keyguardBypassController) {
+    public NotificationShelf(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mBypassController = keyguardBypassController;
     }
 
     @Override
@@ -128,29 +114,16 @@
         initDimens();
     }
 
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        ((SysuiStatusBarStateController) Dependency.get(StatusBarStateController.class))
-                .addCallback(this, SysuiStatusBarStateController.RANK_SHELF);
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        Dependency.get(StatusBarStateController.class).removeCallback(this);
-    }
-
-    public void bind(AmbientState ambientState, NotificationStackScrollLayout hostLayout) {
+    public void bind(AmbientState ambientState,
+            NotificationStackScrollLayoutController hostLayoutController) {
         mAmbientState = ambientState;
-        mHostLayout = hostLayout;
+        mHostLayoutController = hostLayoutController;
     }
 
     private void initDimens() {
         Resources res = getResources();
         mIconAppearTopPadding = res.getDimensionPixelSize(R.dimen.notification_icon_appear_padding);
         mStatusBarHeight = res.getDimensionPixelOffset(R.dimen.status_bar_height);
-        mStatusBarPaddingStart = res.getDimensionPixelOffset(R.dimen.status_bar_padding_start);
         mPaddingBetweenElements = res.getDimensionPixelSize(R.dimen.notification_divider_height);
 
         ViewGroup.LayoutParams layoutParams = getLayoutParams();
@@ -276,8 +249,8 @@
         float firstElementRoundness = 0.0f;
         ActivatableNotificationView previousAnv = null;
 
-        for (int i = 0; i < mHostLayout.getChildCount(); i++) {
-            ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i);
+        for (int i = 0; i < mHostLayoutController.getChildCount(); i++) {
+            ExpandableView child = (ExpandableView) mHostLayoutController.getChildAt(i);
 
             if (!child.needsClippingToShelf() || child.getVisibility() == GONE) {
                 continue;
@@ -315,9 +288,7 @@
                     transitionAmount = inShelfAmount;
                 }
                 // We don't want to modify the color if the notification is hun'd
-                boolean canModifyColor = mAmbientState.isShadeExpanded()
-                        && !(mAmbientState.isOnKeyguard() && mBypassController.getBypassEnabled());
-                if (isLastChild && canModifyColor) {
+                if (isLastChild && mController.canModifyColorOfNotifications()) {
                     if (colorOfViewBeforeLast == NO_COLOR) {
                         colorOfViewBeforeLast = ownColorUntinted;
                     }
@@ -384,8 +355,8 @@
         mShelfIcons.setSpeedBumpIndex(mAmbientState.getSpeedBumpIndex());
         mShelfIcons.calculateIconTranslations();
         mShelfIcons.applyIconStates();
-        for (int i = 0; i < mHostLayout.getChildCount(); i++) {
-            View child = mHostLayout.getChildAt(i);
+        for (int i = 0; i < mHostLayoutController.getChildCount(); i++) {
+            View child = mHostLayoutController.getChildAt(i);
             if (!(child instanceof ExpandableNotificationRow)
                     || child.getVisibility() == GONE) {
                 continue;
@@ -408,8 +379,8 @@
      * swipes quickly.
      */
     private void clipTransientViews() {
-        for (int i = 0; i < mHostLayout.getTransientViewCount(); i++) {
-            View transientView = mHostLayout.getTransientView(i);
+        for (int i = 0; i < mHostLayoutController.getTransientViewCount(); i++) {
+            View transientView = mHostLayoutController.getTransientView(i);
             if (transientView instanceof ExpandableView) {
                 ExpandableView transientExpandableView = (ExpandableView) transientView;
                 updateNotificationClipHeight(transientExpandableView, getTranslationY(), -1);
@@ -648,7 +619,7 @@
             // We need to persist this, since after the expansion, the behavior should still be the
             // same.
             float position = mAmbientState.getIntrinsicPadding()
-                    + mHostLayout.getPositionInLinearLayout(view);
+                    + mHostLayoutController.getPositionInLinearLayout(view);
             int maxShelfStart = mMaxLayoutHeight - getIntrinsicHeight();
             if (position < maxShelfStart && position + view.getIntrinsicHeight() >= maxShelfStart
                     && view.getTranslationY() < position) {
@@ -999,10 +970,6 @@
         return mInteractive;
     }
 
-    public void setMaxShelfEnd(float maxShelfEnd) {
-        mMaxShelfEnd = maxShelfEnd;
-    }
-
     public void setAnimationsEnabled(boolean enabled) {
         mAnimationsEnabled = enabled;
         if (!enabled) {
@@ -1044,6 +1011,10 @@
         updateBackgroundColors();
     }
 
+    public void setController(NotificationShelfController notificationShelfController) {
+        mController = notificationShelfController;
+    }
+
     private class ShelfState extends ExpandableViewState {
         private float openedAmount;
         private boolean hasItemsInStableShelf;
@@ -1056,7 +1027,6 @@
             }
 
             super.applyToView(view);
-            setMaxShelfEnd(maxShelfEnd);
             setOpenedAmount(openedAmount);
             updateAppearance();
             setHasItemsInStableShelf(hasItemsInStableShelf);
@@ -1070,7 +1040,6 @@
             }
 
             super.animateTo(child, properties);
-            setMaxShelfEnd(maxShelfEnd);
             setOpenedAmount(openedAmount);
             updateAppearance();
             setHasItemsInStableShelf(hasItemsInStableShelf);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java
new file mode 100644
index 0000000..77abcfa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java
@@ -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.statusbar;
+
+import android.view.View;
+
+import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController;
+import com.android.systemui.statusbar.notification.row.dagger.NotificationRowScope;
+import com.android.systemui.statusbar.notification.stack.AmbientState;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.phone.NotificationIconContainer;
+import com.android.systemui.statusbar.phone.StatusBarNotificationPresenter;
+
+import javax.inject.Inject;
+
+/**
+ * Controller class for {@link NotificationShelf}.
+ */
+@NotificationRowScope
+public class NotificationShelfController {
+    private final NotificationShelf mView;
+    private final ActivatableNotificationViewController mActivatableNotificationViewController;
+    private final KeyguardBypassController mKeyguardBypassController;
+    private final SysuiStatusBarStateController mStatusBarStateController;
+    private final View.OnAttachStateChangeListener mOnAttachStateChangeListener;
+    private AmbientState mAmbientState;
+
+    @Inject
+    public NotificationShelfController(NotificationShelf notificationShelf,
+            ActivatableNotificationViewController activatableNotificationViewController,
+            KeyguardBypassController keyguardBypassController,
+            SysuiStatusBarStateController statusBarStateController) {
+        mView = notificationShelf;
+        mActivatableNotificationViewController = activatableNotificationViewController;
+        mKeyguardBypassController = keyguardBypassController;
+        mStatusBarStateController = statusBarStateController;
+        mOnAttachStateChangeListener = new View.OnAttachStateChangeListener() {
+            @Override
+            public void onViewAttachedToWindow(View v) {
+                mStatusBarStateController.addCallback(
+                        mView, SysuiStatusBarStateController.RANK_SHELF);
+            }
+
+            @Override
+            public void onViewDetachedFromWindow(View v) {
+                mStatusBarStateController.removeCallback(mView);
+            }
+        };
+    }
+
+    public void init() {
+        mActivatableNotificationViewController.init();
+        mView.setController(this);
+        mView.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
+        if (mView.isAttachedToWindow()) {
+            mOnAttachStateChangeListener.onViewAttachedToWindow(mView);
+        }
+    }
+
+    public NotificationShelf getView() {
+        return mView;
+    }
+
+    public boolean canModifyColorOfNotifications() {
+        return mAmbientState.isShadeExpanded()
+                && !(mAmbientState.isOnKeyguard() && mKeyguardBypassController.getBypassEnabled());
+    }
+
+    public NotificationIconContainer getShelfIcons() {
+        return mView.getShelfIcons();
+    }
+
+    public void setCollapsedIcons(NotificationIconContainer notificationIcons) {
+        mView.setCollapsedIcons(notificationIcons);
+    }
+
+    public void bind(AmbientState ambientState,
+            NotificationStackScrollLayoutController notificationStackScrollLayoutController) {
+        mView.bind(ambientState, notificationStackScrollLayoutController);
+        mAmbientState = ambientState;
+    }
+
+    public int getHeight() {
+        return mView.getHeight();
+    }
+
+    public void updateState(AmbientState ambientState) {
+        mAmbientState = ambientState;
+        mView.updateState(ambientState);
+    }
+
+    public int getIntrinsicHeight() {
+        return mView.getIntrinsicHeight();
+    }
+
+    public void setOnActivatedListener(StatusBarNotificationPresenter presenter) {
+        mView.setOnActivatedListener(presenter);
+    }
+
+    public void setOnClickListener(View.OnClickListener onClickListener) {
+        mView.setOnClickListener(onClickListener);
+    }
+
+    public int getNotGoneIndex() {
+        return mView.getNotGoneIndex();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index 02a8fee..f272be4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -491,7 +491,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 7b25853..ff13c4e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
@@ -36,7 +36,7 @@
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.ExpandableView
 import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.phone.ShadeController
@@ -93,7 +93,7 @@
         private set
     private val mTouchSlop: Float
     private lateinit var expansionCallback: ExpansionCallback
-    private lateinit var stackScroller: NotificationStackScrollLayout
+    private lateinit var stackScrollerController: NotificationStackScrollLayoutController
     private val mTemp2 = IntArray(2)
     private var mDraggedFarEnough: Boolean = false
     private var mStartingChild: ExpandableView? = null
@@ -315,23 +315,23 @@
     private fun findView(x: Float, y: Float): ExpandableView? {
         var totalX = x
         var totalY = y
-        stackScroller.getLocationOnScreen(mTemp2)
+        stackScrollerController.getLocationOnScreen(mTemp2)
         totalX += mTemp2[0].toFloat()
         totalY += mTemp2[1].toFloat()
-        val childAtRawPosition = stackScroller.getChildAtRawPosition(totalX, totalY)
+        val childAtRawPosition = stackScrollerController.getChildAtRawPosition(totalX, totalY)
         return if (childAtRawPosition != null && childAtRawPosition.isContentExpandable) {
             childAtRawPosition
         } else null
     }
 
     fun setUp(
-        stackScroller: NotificationStackScrollLayout,
+        stackScrollerController: NotificationStackScrollLayoutController,
         expansionCallback: ExpansionCallback,
         shadeController: ShadeController
     ) {
         this.expansionCallback = expansionCallback
         this.shadeController = shadeController
-        this.stackScroller = stackScroller
+        this.stackScrollerController = stackScrollerController
     }
 
     fun setPulsing(pulsing: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index 2bef355..f93078f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -23,6 +23,8 @@
 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;
@@ -276,7 +278,7 @@
     }
 
     @Override
-    public void addCallback(StateListener listener) {
+    public void addCallback(@NonNull StateListener listener) {
         synchronized (mListeners) {
             addListenerInternalLocked(listener, Integer.MAX_VALUE);
         }
@@ -316,7 +318,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 7cda235..27e4ade 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SuperStatusBarViewFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SuperStatusBarViewFactory.java
@@ -21,7 +21,7 @@
 import android.view.ViewGroup;
 
 import com.android.systemui.R;
-import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
+import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent;
 import com.android.systemui.statusbar.phone.LockIcon;
 import com.android.systemui.statusbar.phone.LockscreenLockIconController;
 import com.android.systemui.statusbar.phone.NotificationPanelView;
@@ -41,22 +41,22 @@
 
     private final Context mContext;
     private final InjectionInflationController mInjectionInflationController;
-    private final NotificationRowComponent.Builder mNotificationRowComponentBuilder;
     private final LockscreenLockIconController mLockIconController;
+    private final NotificationShelfComponent.Builder mNotificationShelfComponentBuilder;
 
     private NotificationShadeWindowView mNotificationShadeWindowView;
     private StatusBarWindowView mStatusBarWindowView;
-    private NotificationShelf mNotificationShelf;
+    private NotificationShelfController mNotificationShelfController;
 
     @Inject
     public SuperStatusBarViewFactory(Context context,
             InjectionInflationController injectionInflationController,
-            NotificationRowComponent.Builder notificationRowComponentBuilder,
+            NotificationShelfComponent.Builder notificationShelfComponentBuilder,
             LockscreenLockIconController lockIconController) {
         mContext = context;
         mInjectionInflationController = injectionInflationController;
-        mNotificationRowComponentBuilder = notificationRowComponentBuilder;
         mLockIconController = lockIconController;
+        mNotificationShelfComponentBuilder = notificationShelfComponentBuilder;
     }
 
     /**
@@ -114,25 +114,27 @@
      *                  isn't immediately attached, but the layout params of this view is used
      *                  during inflation.
      */
-    public NotificationShelf getNotificationShelf(ViewGroup container) {
-        if (mNotificationShelf != null) {
-            return mNotificationShelf;
+    public NotificationShelfController getNotificationShelfController(ViewGroup container) {
+        if (mNotificationShelfController != null) {
+            return mNotificationShelfController;
         }
 
-        mNotificationShelf = (NotificationShelf) mInjectionInflationController.injectable(
-                LayoutInflater.from(mContext)).inflate(R.layout.status_bar_notification_shelf,
-                container, /* attachToRoot= */ false);
+        NotificationShelf view = (NotificationShelf) LayoutInflater.from(mContext)
+                .inflate(R.layout.status_bar_notification_shelf, container, /* attachToRoot= */
+                        false);
 
-        NotificationRowComponent component = mNotificationRowComponentBuilder
-                .activatableNotificationView(mNotificationShelf)
-                .build();
-        component.getActivatableNotificationViewController().init();
-
-        if (mNotificationShelf == null) {
+        if (view == null) {
             throw new IllegalStateException(
                     "R.layout.status_bar_notification_shelf could not be properly inflated");
         }
-        return mNotificationShelf;
+
+        NotificationShelfComponent component = mNotificationShelfComponentBuilder
+                .notificationShelf(view)
+                .build();
+        mNotificationShelfController = component.getNotificationShelfController();
+        mNotificationShelfController.init();
+
+        return mNotificationShelfController;
     }
 
     public NotificationPanelView getNotificationPanelView() {
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..991b79a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
@@ -33,6 +33,7 @@
 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;
@@ -44,7 +45,6 @@
 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;
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 f982cf0..0469176 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -22,7 +22,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator
 import com.android.systemui.statusbar.phone.DozeParameters
 import com.android.systemui.statusbar.phone.KeyguardBypassController
@@ -33,6 +33,7 @@
 
 import javax.inject.Inject
 import javax.inject.Singleton
+import kotlin.math.min
 
 @Singleton
 class NotificationWakeUpCoordinator @Inject constructor(
@@ -53,7 +54,7 @@
             return coordinator.mLinearVisibilityAmount
         }
     }
-    private lateinit var mStackScroller: NotificationStackScrollLayout
+    private lateinit var mStackScrollerController: NotificationStackScrollLayoutController
     private var mVisibilityInterpolator = Interpolators.FAST_OUT_SLOW_IN_REVERSE
 
     private var mLinearDozeAmount: Float = 0.0f
@@ -79,7 +80,7 @@
                 if (mNotificationsVisible && !mNotificationsVisibleForExpansion &&
                     !bypassController.bypassEnabled) {
                     // We're waking up while pulsing, let's make sure the animation looks nice
-                    mStackScroller.wakeUpFromPulse()
+                    mStackScrollerController.wakeUpFromPulse()
                 }
                 if (bypassController.bypassEnabled && !mNotificationsVisible) {
                     // Let's make sure our huns become visible once we are waking up in case
@@ -156,10 +157,10 @@
         })
     }
 
-    fun setStackScroller(stackScroller: NotificationStackScrollLayout) {
-        mStackScroller = stackScroller
-        pulseExpanding = stackScroller.isPulseExpanding
-        stackScroller.setOnPulseHeightChangedListener {
+    fun setStackScroller(stackScrollerController: NotificationStackScrollLayoutController) {
+        mStackScrollerController = stackScrollerController
+        pulseExpanding = stackScrollerController.isPulseExpanding
+        stackScrollerController.setOnPulseHeightChangedListener {
             val nowExpanding = isPulseExpanding()
             val changed = nowExpanding != pulseExpanding
             pulseExpanding = nowExpanding
@@ -169,7 +170,7 @@
         }
     }
 
-    fun isPulseExpanding(): Boolean = mStackScroller.isPulseExpanding
+    fun isPulseExpanding(): Boolean = mStackScrollerController.isPulseExpanding
 
     /**
      * @param visible should notifications be visible
@@ -249,7 +250,7 @@
         val changed = linear != mLinearDozeAmount
         mLinearDozeAmount = linear
         mDozeAmount = eased
-        mStackScroller.setDozeAmount(mDozeAmount)
+        mStackScrollerController.setDozeAmount(mDozeAmount)
         updateHideAmount()
         if (changed && linear == 0.0f) {
             setNotificationsVisible(visible = false, animate = false, increaseSpeed = false)
@@ -330,18 +331,18 @@
     }
 
     fun getWakeUpHeight(): Float {
-        return mStackScroller.wakeUpHeight
+        return mStackScrollerController.wakeUpHeight
     }
 
     private fun updateHideAmount() {
-        val linearAmount = Math.min(1.0f - mLinearVisibilityAmount, mLinearDozeAmount)
-        val amount = Math.min(1.0f - mVisibilityAmount, mDozeAmount)
-        mStackScroller.setHideAmount(linearAmount, amount)
+        val linearAmount = min(1.0f - mLinearVisibilityAmount, mLinearDozeAmount)
+        val amount = min(1.0f - mVisibilityAmount, mDozeAmount)
+        mStackScrollerController.setHideAmount(linearAmount, amount)
         notificationsFullyHidden = linearAmount == 1.0f
     }
 
     private fun notifyAnimationStart(awake: Boolean) {
-        mStackScroller.notifyHideAnimationStart(!awake)
+        mStackScrollerController.notifyHideAnimationStart(!awake)
     }
 
     override fun onDozingChanged(isDozing: Boolean) {
@@ -355,7 +356,7 @@
      * from a pulse and determines how much the notifications are expanded.
      */
     fun setPulseHeight(height: Float): Float {
-        val overflow = mStackScroller.setPulseHeight(height)
+        val overflow = mStackScrollerController.setPulseHeight(height)
         //  no overflow for the bypass experience
         return if (bypassController.bypassEnabled) 0.0f else overflow
     }
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..2f12088 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
@@ -82,14 +82,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 +181,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 +196,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/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
index ee2bb6b1..3e11067 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
@@ -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/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index 8849824..f90ec0b 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
@@ -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/init/NotifPipelineInitializer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
index 9782c3e..1c02c62 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
@@ -29,8 +29,7 @@
 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;
@@ -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/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..00fd09d 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
@@ -18,18 +18,19 @@
 
 import android.view.textclassifier.Log
 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
 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..201be59
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
@@ -0,0 +1,88 @@
+/*
+ * 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 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 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))
+        }
+
+        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
+) {
+    fun create(listContainer: NotificationListContainer): ShadeViewManager {
+        return ShadeViewManager(listContainer, logger, viewBarn)
+    }
+}
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/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 22d0357..d7fa54f 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
@@ -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.
@@ -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) {
@@ -1598,7 +1597,6 @@
             RowContentBindStage rowContentBindStage,
             OnExpandClickListener onExpandClickListener,
             NotificationMediaManager notificationMediaManager,
-            CoordinateOnClickListener onAppOpsClickListener,
             CoordinateOnClickListener onFeedbackClickListener,
             FalsingManager falsingManager,
             StatusBarStateController statusBarStateController,
@@ -1619,7 +1617,6 @@
         mRowContentBindStage = rowContentBindStage;
         mOnExpandClickListener = onExpandClickListener;
         mMediaManager = notificationMediaManager;
-        setAppOpsOnClickListener(onAppOpsClickListener);
         setOnFeedbackClickListener(onFeedbackClickListener);
         mFalsingManager = falsingManager;
         mStatusbarStateController = statusBarStateController;
@@ -1677,14 +1674,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 +1710,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..690dce9 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,7 +69,6 @@
 
     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;
@@ -72,6 +78,7 @@
 
     @Inject
     public ExpandableNotificationRowController(ExpandableNotificationRow view,
+            NotificationListContainer listContainer,
             ActivatableNotificationViewController activatableNotificationViewController,
             NotificationMediaManager mediaManager, PluginManager pluginManager,
             SystemClock clock, @AppName String appName, @NotificationKey String notificationKey,
@@ -86,6 +93,7 @@
             OnDismissCallback onDismissCallback, FalsingManager falsingManager,
             PeopleNotificationIdentifier peopleNotificationIdentifier) {
         mView = view;
+        mListContainer = listContainer;
         mActivatableNotificationViewController = activatableNotificationViewController;
         mMediaManager = mediaManager;
         mPluginManager = pluginManager;
@@ -101,7 +109,6 @@
         mStatusBarStateController = statusBarStateController;
         mNotificationGutsManager = notificationGutsManager;
         mOnDismissCallback = onDismissCallback;
-        mOnAppOpsClickListener = mNotificationGutsManager::openGuts;
         mOnFeedbackClickListener = mNotificationGutsManager::openGuts;
         mAllowLongPress = allowLongPress;
         mFalsingManager = falsingManager;
@@ -123,7 +130,6 @@
                 mRowContentBindStage,
                 mOnExpandClickListener,
                 mMediaManager,
-                mOnAppOpsClickListener,
                 mOnFeedbackClickListener,
                 mFalsingManager,
                 mStatusBarStateController,
@@ -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/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/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 729b131..05d44f6 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
@@ -268,8 +268,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 +307,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
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/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/dagger/NotificationShelfComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationShelfComponent.java
new file mode 100644
index 0000000..af8d6ec
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationShelfComponent.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row.dagger;
+
+import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.NotificationShelfController;
+import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
+
+import dagger.Binds;
+import dagger.BindsInstance;
+import dagger.Module;
+import dagger.Subcomponent;
+
+/**
+ * Dagger subcomponent for NotificationShelf.
+ */
+@Subcomponent(modules = {ActivatableNotificationViewModule.class,
+        NotificationShelfComponent.NotificationShelfModule.class})
+@NotificationRowScope
+public interface NotificationShelfComponent {
+    /**
+     * Builder for {@link NotificationShelfComponent}.
+     */
+    @Subcomponent.Builder
+    interface Builder {
+        @BindsInstance
+        Builder notificationShelf(NotificationShelf view);
+        NotificationShelfComponent build();
+    }
+
+    /**
+     * Creates a NotificationShelfController.
+     */
+    @NotificationRowScope
+    NotificationShelfController getNotificationShelfController();
+    /**
+     * Dagger Module that extracts interesting properties from a NotificationShelf.
+     */
+    @Module
+    abstract class NotificationShelfModule {
+
+        /** NotificationShelf is provided as an instance of ActivatableNotificationView. */
+        @Binds
+        abstract ActivatableNotificationView bindNotificationShelf(NotificationShelf view);
+    }
+}
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/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index 93c2377..7e4266c 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
@@ -1303,20 +1303,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/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 66a541a..d2f8a39 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
@@ -19,7 +19,6 @@
 import static android.service.notification.NotificationStats.DISMISSAL_SHADE;
 import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
 
-import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
 import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
 import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_SILENT;
 import static com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.ANCHOR_SCROLLING;
@@ -113,6 +112,7 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.NotificationShelfController;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -180,8 +180,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 {
+        ConfigurationListener, Dumpable, DynamicPrivacyController.Listener {
 
     public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
     private static final String TAG = "StackScroller";
@@ -209,11 +208,11 @@
     private final Paint mBackgroundPaint = new Paint();
     private final boolean mShouldDrawNotificationBackground;
     private boolean mHighPriorityBeforeSpeedBump;
-    private final boolean mAllowLongPress;
     private boolean mDismissRtl;
 
     private float mExpandedHeight;
     private int mOwnScrollY;
+
     private View mScrollAnchorView;
     private int mScrollAnchorViewY;
     private int mMaxLayoutHeight;
@@ -538,6 +537,7 @@
     private int mGapHeight;
 
     private int mWaterfallTopInset;
+    private NotificationStackScrollLayoutController mController;
 
     private SysuiColorExtractor.OnColorsChangedListener mOnColorsChangedListener =
             (colorExtractor, which) -> {
@@ -545,11 +545,23 @@
                 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,
             AttributeSet attrs,
-            @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
             NotificationRoundnessManager notificationRoundnessManager,
             DynamicPrivacyController dynamicPrivacyController,
             SysuiStatusBarStateController statusBarStateController,
@@ -572,14 +584,11 @@
         super(context, attrs, 0, 0);
         Resources res = getResources();
 
-        mAllowLongPress = allowLongPress;
-
         mRoundnessManager = notificationRoundnessManager;
 
         mLockscreenUserManager = notificationLockscreenUserManager;
         mNotificationGutsManager = notificationGutsManager;
         mHeadsUpManager = headsUpManager;
-        mHeadsUpManager.addListener(mRoundnessManager);
         mHeadsUpManager.setAnimationStateHandler(this::setHeadsUpGoingAwayAnimationsAllowed);
         mKeyguardBypassController = keyguardBypassController;
         mFalsingManager = falsingManager;
@@ -611,9 +620,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);
 
@@ -640,13 +646,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;
@@ -696,7 +697,7 @@
         }
     }
 
-    private void updateDismissRtlSetting(boolean dismissRtl) {
+    void updateDismissRtlSetting(boolean dismissRtl) {
         mDismissRtl = dismissRtl;
         for (int i = 0; i < getChildCount(); i++) {
             View child = getChildAt(i);
@@ -714,9 +715,6 @@
         inflateEmptyShadeView();
         inflateFooterView();
         mVisualStabilityManager.setVisibilityLocationProvider(this::isInVisibleLocation);
-        if (mAllowLongPress) {
-            setLongPressListener(mNotificationGutsManager::openGuts);
-        }
     }
 
     /**
@@ -840,7 +838,6 @@
         Dependency.get(ConfigurationController.class).removeCallback(this);
     }
 
-    @Override
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public NotificationSwipeActionHelper getSwipeActionHelper() {
         return mSwipeHelper;
@@ -1156,14 +1153,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();
@@ -1862,7 +1861,6 @@
         }
     }
 
-    @Override
     @ShadeViewRefactor(RefactorComponent.ADAPTER)
     public ViewGroup getViewParentForNotification(NotificationEntry entry) {
         return this;
@@ -2552,7 +2550,6 @@
                 previous);
     }
 
-    @Override
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public boolean hasPulsingNotifications() {
         return mPulsing;
@@ -3056,7 +3053,6 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
-    @Override
     public void cleanUpViewStateForEntry(NotificationEntry entry) {
         View child = entry.getRow();
         if (child == mSwipeHelper.getTranslatingParentView()) {
@@ -3323,7 +3319,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)
@@ -3356,9 +3354,9 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
-    private void onViewAddedInternal(ExpandableView child) {
+    void onViewAddedInternal(ExpandableView child) {
+        child.setOnHeightChangedListener(mOnChildHeightChangedListener);
         updateHideSensitiveForChild(child);
-        child.setOnHeightChangedListener(this);
         generateAddAnimation(child, false /* fromMoreCard */);
         updateAnimationState(child);
         updateChronometerForChild(child);
@@ -3380,18 +3378,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;
@@ -3416,33 +3407,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());
@@ -3458,12 +3445,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()) {
@@ -3481,7 +3467,6 @@
         }
     }
 
-    @Override
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     public void changeViewPosition(ExpandableView child, int newIndex) {
         Assert.isMainThread();
@@ -4477,12 +4462,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 */,
@@ -4492,19 +4477,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);
@@ -4553,20 +4538,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
@@ -4607,15 +4592,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();
@@ -4638,8 +4622,7 @@
         requestChildrenUpdate();
     }
 
-    @Override
-    public void onReset(ExpandableView view) {
+    void onChildHeightReset(ExpandableView view) {
         updateAnimationState(view);
         updateChronometerForChild(view);
     }
@@ -4680,13 +4663,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();
@@ -4730,7 +4713,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) {
@@ -4795,7 +4778,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;
@@ -4871,7 +4854,7 @@
      * @param lightTheme True if light theme should be used.
      */
     @ShadeViewRefactor(RefactorComponent.DECORATOR)
-    public void updateDecorViews(boolean lightTheme) {
+    private void updateDecorViews(boolean lightTheme) {
         if (lightTheme == mUsingLightTheme) {
             return;
         }
@@ -4886,7 +4869,7 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    public void goToFullShade(long delay) {
+    void goToFullShade(long delay) {
         mGoToFullShadeNeedsAnimation = true;
         mGoToFullShadeDelay = delay;
         mNeedsAnimation = true;
@@ -4899,13 +4882,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;
     }
 
@@ -4939,7 +4922,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();
@@ -4981,7 +4964,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) {
@@ -5009,7 +4992,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);
@@ -5020,7 +5003,7 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    public void setEmptyShadeView(EmptyShadeView emptyShadeView) {
+    void setEmptyShadeView(EmptyShadeView emptyShadeView) {
         int index = -1;
         if (mEmptyShadeView != null) {
             index = indexOfChild(mEmptyShadeView);
@@ -5031,7 +5014,7 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    public void updateEmptyShadeView(boolean visible) {
+    void updateEmptyShadeView(boolean visible) {
         mEmptyShadeView.setVisible(visible, mIsExpanded && mAnimationsEnabled);
 
         int oldTextRes = mEmptyShadeView.getTextResource();
@@ -5215,33 +5198,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);
@@ -5283,7 +5261,6 @@
         requestChildrenUpdate();
     }
 
-    @Override
     public void setWillExpand(boolean willExpand) {
         mWillExpand = willExpand;
     }
@@ -5419,28 +5396,23 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    public void setShelf(NotificationShelf shelf) {
+    public void setShelfController(NotificationShelfController notificationShelfController) {
         int index = -1;
         if (mShelf != null) {
             index = indexOfChild(mShelf);
             removeView(mShelf);
         }
-        mShelf = shelf;
+        mShelf = notificationShelfController.getView();
         addView(mShelf, index);
-        mAmbientState.setShelf(shelf);
-        mStateAnimator.setShelf(shelf);
-        shelf.bind(mAmbientState, this);
+        mAmbientState.setShelf(mShelf);
+        mStateAnimator.setShelf(mShelf);
+        notificationShelfController.bind(mAmbientState, mController);
         if (ANCHOR_SCROLLING) {
             mScrollAnchorView = mShelf;
         }
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    public NotificationShelf getNotificationShelf() {
-        return mShelf;
-    }
-
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void setMaxDisplayedNotifications(int maxDisplayedNotifications) {
         if (mMaxDisplayedNotifications != maxDisplayedNotifications) {
             mMaxDisplayedNotifications = maxDisplayedNotifications;
@@ -5753,7 +5725,6 @@
         }
     }
 
-    @Override
     public void setNotificationActivityStarter(
             NotificationActivityStarter notificationActivityStarter) {
         mNotificationActivityStarter = notificationActivityStarter;
@@ -5908,6 +5879,16 @@
         return MathUtils.smoothStep(0, totalDistance, dragDownAmount);
     }
 
+    public void setController(
+            NotificationStackScrollLayoutController notificationStackScrollLayoutController) {
+        mController = notificationStackScrollLayoutController;
+        mController.getNoticationRoundessManager().setAnimatedChildren(mChildrenToAddAnimated);
+    }
+
+    public NotificationStackScrollLayoutController getController() {
+        return mController;
+    }
+
     /**
      * A listener that is notified when the empty space below the notifications is clicked on
      */
@@ -6002,7 +5983,6 @@
         }
     }
 
-    @Override
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void resetExposedMenuView(boolean animate, boolean force) {
         mSwipeHelper.resetExposedMenuView(animate, force);
@@ -6754,7 +6734,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
new file mode 100644
index 0000000..53d3b75
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -0,0 +1,713 @@
+/*
+ * 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 com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
+import static com.android.systemui.statusbar.phone.NotificationIconAreaController.HIGH_PRIORITY;
+
+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.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.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.tuner.TunerService;
+
+import java.util.function.BiConsumer;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * Controller for {@link NotificationStackScrollLayout}.
+ */
+@StatusBarComponent.StatusBarScope
+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 NotificationListContainerImpl mNotificationListContainer =
+            new NotificationListContainerImpl();
+    private NotificationStackScrollLayout mView;
+
+    @Inject
+    public NotificationStackScrollLayoutController(
+            @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
+            NotificationGutsManager notificationGutsManager,
+            HeadsUpManagerPhone headsUpManager,
+            NotificationRoundnessManager notificationRoundnessManager,
+            TunerService tunerService) {
+        mAllowLongPress = allowLongPress;
+        mNotificationGutsManager = notificationGutsManager;
+        mHeadsUpManager = headsUpManager;
+        mNotificationRoundnessManager = notificationRoundnessManager;
+        mTunerService = tunerService;
+    }
+
+    public void attach(NotificationStackScrollLayout view) {
+        mView = view;
+        mView.setController(this);
+
+        if (mAllowLongPress) {
+            mView.setLongPressListener(mNotificationGutsManager::openGuts);
+        }
+
+        mHeadsUpManager.addListener(mNotificationRoundnessManager); // TODO: why is this here?
+
+        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));
+            }
+        }, HIGH_PRIORITY, Settings.Secure.NOTIFICATION_DISMISS_RTL);
+    }
+
+    public void addOnExpandedHeightChangedListener(BiConsumer<Float, Float> listener) {
+        mView.addOnExpandedHeightChangedListener(listener);
+    }
+
+    public void removeOnExpandedHeightChangedListener(BiConsumer<Float, Float> listener) {
+        mView.removeOnExpandedHeightChangedListener(listener);
+    }
+
+    public void addOnLayoutChangeListener(View.OnLayoutChangeListener listener) {
+        mView.addOnLayoutChangeListener(listener);
+    }
+
+    public void removeOnLayoutChangeListener(View.OnLayoutChangeListener listener) {
+        mView.removeOnLayoutChangeListener(listener);
+    }
+
+    public void setHeadsUpAppearanceController(HeadsUpAppearanceController controller) {
+        mView.setHeadsUpAppearanceController(controller);
+    }
+
+    public void requestLayout() {
+        mView.requestLayout();
+    }
+
+    public Display getDisplay() {
+        return mView.getDisplay();
+    }
+
+    public WindowInsets getRootWindowInsets() {
+        return mView.getRootWindowInsets();
+    }
+
+    public int getRight() {
+        return mView.getRight();
+    }
+
+    public boolean isLayoutRtl() {
+        return mView.isLayoutRtl();
+    }
+
+    public float getLeft() {
+        return  mView.getLeft();
+    }
+
+    public float getTranslationX() {
+        return mView.getTranslationX();
+    }
+
+    public void setOnHeightChangedListener(
+            ExpandableView.OnHeightChangedListener listener) {
+        mView.setOnHeightChangedListener(listener);
+    }
+
+    public void setOverscrollTopChangedListener(
+            NotificationStackScrollLayout.OnOverscrollTopChangedListener listener) {
+        mView.setOverscrollTopChangedListener(listener);
+    }
+
+    public void setOnEmptySpaceClickListener(
+            NotificationStackScrollLayout.OnEmptySpaceClickListener listener) {
+        mView.setOnEmptySpaceClickListener(listener);
+    }
+
+    public void setTrackingHeadsUp(ExpandableNotificationRow expandableNotificationRow) {
+        mView.setTrackingHeadsUp(expandableNotificationRow);
+    }
+
+    public void wakeUpFromPulse() {
+        mView.wakeUpFromPulse();
+    }
+
+    public boolean isPulseExpanding() {
+        return mView.isPulseExpanding();
+    }
+
+    public void setOnPulseHeightChangedListener(Runnable listener) {
+        mView.setOnPulseHeightChangedListener(listener);
+    }
+
+    public void setDozeAmount(float amount) {
+        mView.setDozeAmount(amount);
+    }
+
+    public float getWakeUpHeight() {
+        return mView.getWakeUpHeight();
+    }
+
+    public void setHideAmount(float linearAmount, float amount) {
+        mView.setHideAmount(linearAmount, amount);
+    }
+
+    public void notifyHideAnimationStart(boolean hide) {
+        mView.notifyHideAnimationStart(hide);
+    }
+
+    public float setPulseHeight(float height) {
+        return mView.setPulseHeight(height);
+    }
+
+    public void getLocationOnScreen(int[] outLocation) {
+        mView.getLocationOnScreen(outLocation);
+    }
+
+    public ExpandableView getChildAtRawPosition(float x, float y) {
+        return mView.getChildAtRawPosition(x, y);
+    }
+
+    public FrameLayout.LayoutParams getLayoutParams() {
+        return (FrameLayout.LayoutParams) mView.getLayoutParams();
+    }
+
+    public void setLayoutParams(FrameLayout.LayoutParams lp) {
+        mView.setLayoutParams(lp);
+    }
+
+    public void setIsFullWidth(boolean isFullWidth) {
+        mView.setIsFullWidth(isFullWidth);
+    }
+
+    public boolean isAddOrRemoveAnimationPending() {
+        return mView.isAddOrRemoveAnimationPending();
+    }
+
+    public int getVisibleNotificationCount() {
+        return mView.getVisibleNotificationCount();
+    }
+
+    public int getIntrinsicContentHeight() {
+        return mView.getIntrinsicContentHeight();
+    }
+
+    public void setIntrinsicPadding(int intrinsicPadding) {
+        mView.setIntrinsicPadding(intrinsicPadding);
+    }
+
+    public int getHeight() {
+        return mView.getHeight();
+    }
+
+    public int getChildCount() {
+        return mView.getChildCount();
+    }
+
+    public ExpandableView getChildAt(int i) {
+        return (ExpandableView) mView.getChildAt(i);
+    }
+
+    public void goToFullShade(long delay) {
+        mView.goToFullShade(delay);
+    }
+
+    public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
+            boolean cancelAnimators) {
+        mView.setOverScrollAmount(amount, onTop, animate, cancelAnimators);
+    }
+
+    public void setOverScrollAmount(float amount, boolean onTop, boolean animate) {
+        mView.setOverScrollAmount(amount, onTop, animate);
+    }
+
+    public void setOverScrolledPixels(float numPixels, boolean onTop, boolean animate) {
+        mView.setOverScrolledPixels(numPixels, onTop, animate);
+    }
+
+    public void resetScrollPosition() {
+        mView.resetScrollPosition();
+    }
+
+    public void setShouldShowShelfOnly(boolean shouldShowShelfOnly) {
+        mView.setShouldShowShelfOnly(shouldShowShelfOnly);
+    }
+
+    public void cancelLongPress() {
+        mView.cancelLongPress();
+    }
+
+    public float getX() {
+        return mView.getX();
+    }
+
+    public boolean isBelowLastNotification(float x, float y) {
+        return mView.isBelowLastNotification(x, y);
+    }
+
+    public float getWidth() {
+        return mView.getWidth();
+    }
+
+    public float getOpeningHeight() {
+        return mView.getOpeningHeight();
+    }
+
+    public float getBottomMostNotificationBottom() {
+        return mView.getBottomMostNotificationBottom();
+    }
+
+    public void checkSnoozeLeavebehind() {
+        mView.checkSnoozeLeavebehind();
+    }
+
+    public void setQsExpanded(boolean expanded) {
+        mView.setQsExpanded(expanded);
+    }
+
+    public void setScrollingEnabled(boolean enabled) {
+        mView.setScrollingEnabled(enabled);
+    }
+
+    public void setQsExpansionFraction(float expansionFraction) {
+        mView.setQsExpansionFraction(expansionFraction);
+    }
+
+    public float calculateAppearFractionBypass() {
+        return mView.calculateAppearFractionBypass();
+    }
+
+    public void updateTopPadding(float qsHeight, boolean animate) {
+        mView.updateTopPadding(qsHeight, animate);
+    }
+
+    public void resetCheckSnoozeLeavebehind() {
+        mView.resetCheckSnoozeLeavebehind();
+    }
+
+    public boolean isScrolledToBottom() {
+        return mView.isScrolledToBottom();
+    }
+
+    public int getNotGoneChildCount() {
+        return mView.getNotGoneChildCount();
+    }
+
+    public float getIntrinsicPadding() {
+        return mView.getIntrinsicPadding();
+    }
+
+    public float getLayoutMinHeight() {
+        return mView.getLayoutMinHeight();
+    }
+
+    public int getEmptyBottomMargin() {
+        return mView.getEmptyBottomMargin();
+    }
+
+    public float getTopPaddingOverflow() {
+        return mView.getTopPaddingOverflow();
+    }
+
+    public int getTopPadding() {
+        return mView.getTopPadding();
+    }
+
+    public float getEmptyShadeViewHeight() {
+        return mView.getEmptyShadeViewHeight();
+    }
+
+    public void setAlpha(float alpha) {
+        mView.setAlpha(alpha);
+    }
+
+    public float getCurrentOverScrollAmount(boolean top) {
+        return mView.getCurrentOverScrollAmount(top);
+    }
+
+    public float getCurrentOverScrolledPixels(boolean top) {
+        return mView.getCurrentOverScrolledPixels(top);
+    }
+
+    public float calculateAppearFraction(float height) {
+        return mView.calculateAppearFraction(height);
+    }
+
+    public void onExpansionStarted() {
+        mView.onExpansionStarted();
+    }
+
+    public void onExpansionStopped() {
+        mView.onExpansionStopped();
+    }
+
+    public void onPanelTrackingStarted() {
+        mView.onPanelTrackingStarted();
+    }
+
+    public void onPanelTrackingStopped() {
+        mView.onPanelTrackingStopped();
+    }
+
+    public void setHeadsUpBoundaries(int height, int bottomBarHeight) {
+        mView.setHeadsUpBoundaries(height, bottomBarHeight);
+    }
+
+    public void setUnlockHintRunning(boolean running) {
+        mView.setUnlockHintRunning(running);
+    }
+
+    public float getPeekHeight() {
+        return mView.getPeekHeight();
+    }
+
+    public boolean isFooterViewNotGone() {
+        return mView.isFooterViewNotGone();
+    }
+
+    public boolean isFooterViewContentVisible() {
+        return mView.isFooterViewContentVisible();
+    }
+
+    public int getFooterViewHeightWithPadding() {
+        return mView.getFooterViewHeightWithPadding();
+    }
+
+    public void updateEmptyShadeView(boolean visible) {
+        mView.updateEmptyShadeView(visible);
+    }
+
+    public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
+        mView.setHeadsUpAnimatingAway(headsUpAnimatingAway);
+    }
+
+    public HeadsUpTouchHelper.Callback getHeadsUpCallback() {
+        return mView.getHeadsUpCallback();
+    }
+
+    public void forceNoOverlappingRendering(boolean force) {
+        mView.forceNoOverlappingRendering(force);
+    }
+
+    public void setTranslationX(float translation) {
+        mView.setTranslationX(translation);
+    }
+
+    public void setExpandingVelocity(float velocity) {
+        mView.setExpandingVelocity(velocity);
+    }
+
+    public void setExpandedHeight(float expandedHeight) {
+        mView.setExpandedHeight(expandedHeight);
+    }
+
+    public void setQsContainer(ViewGroup view) {
+        mView.setQsContainer(view);
+    }
+
+    public void setAnimationsEnabled(boolean enabled) {
+        mView.setAnimationsEnabled(enabled);
+    }
+
+    public void setDozing(boolean dozing, boolean animate, PointF wakeUpTouchLocation) {
+        mView.setDozing(dozing, animate, wakeUpTouchLocation);
+    }
+
+    public void setPulsing(boolean pulsing, boolean animatePulse) {
+        mView.setPulsing(pulsing, animatePulse);
+    }
+
+    public boolean hasActiveClearableNotifications(
+            @NotificationStackScrollLayout.SelectedRows int selection) {
+        return mView.hasActiveClearableNotifications(selection);
+    }
+
+    public RemoteInputController.Delegate createDelegate() {
+        return mView.createDelegate();
+    }
+
+    public void updateSectionBoundaries(String reason) {
+        mView.updateSectionBoundaries(reason);
+    }
+
+    public void updateSpeedBumpIndex() {
+        mView.updateSpeedBumpIndex();
+    }
+
+    public void updateFooter() {
+        mView.updateFooter();
+    }
+
+    public void updateIconAreaViews() {
+        mView.updateIconAreaViews();
+    }
+
+    public void onUpdateRowStates() {
+        mView.onUpdateRowStates();
+    }
+
+    public ActivatableNotificationView getActivatedChild() {
+        return mView.getActivatedChild();
+    }
+
+    public void setActivatedChild(ActivatableNotificationView view) {
+        mView.setActivatedChild(view);
+    }
+
+    public void runAfterAnimationFinished(Runnable r) {
+        mView.runAfterAnimationFinished(r);
+    }
+
+    public void setNotificationPanelController(
+            NotificationPanelViewController notificationPanelViewController) {
+        mView.setNotificationPanelController(notificationPanelViewController);
+    }
+
+    public void setIconAreaController(NotificationIconAreaController controller) {
+        mView.setIconAreaController(controller);
+    }
+
+    public void setStatusBar(StatusBar statusBar) {
+        mView.setStatusBar(statusBar);
+    }
+
+    public void setGroupManager(NotificationGroupManager groupManager) {
+        mView.setGroupManager(groupManager);
+    }
+
+    public void setShelfController(NotificationShelfController notificationShelfController) {
+        mView.setShelfController(notificationShelfController);
+    }
+
+    public void setScrimController(ScrimController scrimController) {
+        mView.setScrimController(scrimController);
+    }
+
+    public ExpandableView getFirstChildNotGone() {
+        return mView.getFirstChildNotGone();
+    }
+
+    public void setInHeadsUpPinnedMode(boolean inPinnedMode) {
+        mView.setInHeadsUpPinnedMode(inPinnedMode);
+    }
+
+    public void generateHeadsUpAnimation(NotificationEntry entry, boolean isHeadsUp) {
+        mView.generateHeadsUpAnimation(entry, isHeadsUp);
+    }
+
+    public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) {
+        mView.generateHeadsUpAnimation(row, isHeadsUp);
+    }
+
+    public void setMaxTopPadding(int padding) {
+        mView.setMaxTopPadding(padding);
+    }
+
+    public int getTransientViewCount() {
+        return mView.getTransientViewCount();
+    }
+
+    public View getTransientView(int i) {
+        return mView.getTransientView(i);
+    }
+
+    public int getPositionInLinearLayout(ExpandableView row) {
+        return mView.getPositionInLinearLayout(row);
+    }
+
+    public NotificationStackScrollLayout getView() {
+        return mView;
+    }
+
+    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..9d920c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -47,6 +47,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;
@@ -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/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/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index 4afeba8..de74d4e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -36,6 +36,7 @@
 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;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index 51c02c9..8cdaa63 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -36,7 +36,7 @@
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 
@@ -52,7 +52,7 @@
     public static final int CONTENT_FADE_DELAY = 100;
     private final NotificationIconAreaController mNotificationIconAreaController;
     private final HeadsUpManagerPhone mHeadsUpManager;
-    private final NotificationStackScrollLayout mStackScroller;
+    private final NotificationStackScrollLayoutController mStackScrollerController;
     private final HeadsUpStatusBarView mHeadsUpStatusBarView;
     private final View mCenteredIconView;
     private final View mClockView;
@@ -93,7 +93,7 @@
     public HeadsUpAppearanceController(
             NotificationIconAreaController notificationIconAreaController,
             HeadsUpManagerPhone headsUpManager,
-            View notificationShadeView,
+            NotificationStackScrollLayoutController notificationStackScrollLayoutController,
             SysuiStatusBarStateController statusBarStateController,
             KeyguardBypassController keyguardBypassController,
             KeyguardStateController keyguardStateController,
@@ -101,10 +101,9 @@
             NotificationPanelViewController notificationPanelViewController, View statusBarView) {
         this(notificationIconAreaController, headsUpManager, statusBarStateController,
                 keyguardBypassController, wakeUpCoordinator, keyguardStateController,
-                commandQueue,
-                statusBarView.findViewById(R.id.heads_up_status_bar_view),
-                notificationShadeView.findViewById(R.id.notification_stack_scroller),
+                commandQueue, notificationStackScrollLayoutController,
                 notificationPanelViewController,
+                statusBarView.findViewById(R.id.heads_up_status_bar_view),
                 statusBarView.findViewById(R.id.clock),
                 statusBarView.findViewById(R.id.operator_name_frame),
                 statusBarView.findViewById(R.id.centered_icon_area));
@@ -119,9 +118,9 @@
             NotificationWakeUpCoordinator wakeUpCoordinator,
             KeyguardStateController keyguardStateController,
             CommandQueue commandQueue,
-            HeadsUpStatusBarView headsUpStatusBarView,
-            NotificationStackScrollLayout stackScroller,
+            NotificationStackScrollLayoutController stackScrollerController,
             NotificationPanelViewController notificationPanelViewController,
+            HeadsUpStatusBarView headsUpStatusBarView,
             View clockView,
             View operatorNameView,
             View centeredIconView) {
@@ -132,14 +131,14 @@
         mCenteredIconView = centeredIconView;
         headsUpStatusBarView.setOnDrawingRectChangedListener(
                 () -> updateIsolatedIconLocation(true /* requireUpdate */));
-        mStackScroller = stackScroller;
+        mStackScrollerController = stackScrollerController;
         mNotificationPanelViewController = notificationPanelViewController;
         notificationPanelViewController.addTrackingHeadsUpListener(mSetTrackingHeadsUp);
         notificationPanelViewController.addVerticalTranslationListener(mUpdatePanelTranslation);
         notificationPanelViewController.setHeadsUpAppearanceController(this);
-        mStackScroller.addOnExpandedHeightChangedListener(mSetExpandedHeight);
-        mStackScroller.addOnLayoutChangeListener(mStackScrollLayoutChangeListener);
-        mStackScroller.setHeadsUpAppearanceController(this);
+        mStackScrollerController.addOnExpandedHeightChangedListener(mSetExpandedHeight);
+        mStackScrollerController.addOnLayoutChangeListener(mStackScrollLayoutChangeListener);
+        mStackScrollerController.setHeadsUpAppearanceController(this);
         mClockView = clockView;
         mOperatorNameView = operatorNameView;
         mDarkIconDispatcher = Dependency.get(DarkIconDispatcher.class);
@@ -153,7 +152,7 @@
                     updateTopEntry();
 
                     // trigger scroller to notify the latest panel translation
-                    mStackScroller.requestLayout();
+                    mStackScrollerController.requestLayout();
                 }
                 mHeadsUpStatusBarView.removeOnLayoutChangeListener(this);
             }
@@ -174,8 +173,8 @@
         mNotificationPanelViewController.removeTrackingHeadsUpListener(mSetTrackingHeadsUp);
         mNotificationPanelViewController.removeVerticalTranslationListener(mUpdatePanelTranslation);
         mNotificationPanelViewController.setHeadsUpAppearanceController(null);
-        mStackScroller.removeOnExpandedHeightChangedListener(mSetExpandedHeight);
-        mStackScroller.removeOnLayoutChangeListener(mStackScrollLayoutChangeListener);
+        mStackScrollerController.removeOnExpandedHeightChangedListener(mSetExpandedHeight);
+        mStackScrollerController.removeOnLayoutChangeListener(mStackScrollLayoutChangeListener);
         mDarkIconDispatcher.removeDarkReceiver(this);
     }
 
@@ -219,12 +218,12 @@
         }
 
         int realDisplaySize = 0;
-        if (mStackScroller.getDisplay() != null) {
-            mStackScroller.getDisplay().getRealSize(mPoint);
+        if (mStackScrollerController.getDisplay() != null) {
+            mStackScrollerController.getDisplay().getRealSize(mPoint);
             realDisplaySize = mPoint.x;
         }
 
-        WindowInsets windowInset = mStackScroller.getRootWindowInsets();
+        WindowInsets windowInset = mStackScrollerController.getRootWindowInsets();
         DisplayCutout cutout = (windowInset != null) ? windowInset.getDisplayCutout() : null;
         int sysWinLeft = (windowInset != null) ? windowInset.getStableInsetLeft() : 0;
         int sysWinRight = (windowInset != null) ? windowInset.getStableInsetRight() : 0;
@@ -233,17 +232,17 @@
         int leftInset = Math.max(sysWinLeft, cutoutLeft);
         int rightInset = Math.max(sysWinRight, cutoutRight);
 
-        return leftInset + mStackScroller.getRight() + rightInset - realDisplaySize;
+        return leftInset + mStackScrollerController.getRight() + rightInset - realDisplaySize;
     }
 
     public void updatePanelTranslation() {
         float newTranslation;
-        if (mStackScroller.isLayoutRtl()) {
+        if (mStackScrollerController.isLayoutRtl()) {
             newTranslation = getRtlTranslation();
         } else {
-            newTranslation = mStackScroller.getLeft();
+            newTranslation = mStackScrollerController.getLeft();
         }
-        newTranslation += mStackScroller.getTranslationX();
+        newTranslation += mStackScrollerController.getTranslationX();
         mHeadsUpStatusBarView.setPanelTranslation(newTranslation);
     }
 
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..982773a 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,7 @@
 import com.android.internal.view.AppearanceRegion;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
+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;
@@ -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/ManagedProfileControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
index 07e9f94..8e9dcb4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
@@ -24,6 +24,8 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 
+import androidx.annotation.NonNull;
+
 import com.android.systemui.broadcast.BroadcastDispatcher;
 
 import java.util.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/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index 9d3e915..0e413fb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -5,6 +5,7 @@
 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;
@@ -20,13 +21,15 @@
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.bubbles.BubbleController;
+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;
 import com.android.systemui.statusbar.CrossFadeHelper;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationMediaManager;
-import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.NotificationShelfController;
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -35,6 +38,7 @@
 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;
 
@@ -44,7 +48,8 @@
  */
 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;
@@ -74,6 +79,8 @@
     private final Rect mTintArea = new Rect();
     private ViewGroup mNotificationScrollLayout;
     private Context mContext;
+    private final DemoModeController mDemoModeController;
+
     private int mAodIconAppearTranslation;
 
     private boolean mAnimationsEnabled;
@@ -104,7 +111,8 @@
             NotificationMediaManager notificationMediaManager,
             NotificationListener notificationListener,
             DozeParameters dozeParameters,
-            BubbleController bubbleController) {
+            BubbleController bubbleController,
+            DemoModeController demoModeController) {
         mStatusBar = statusBar;
         mContrastColorUtil = ContrastColorUtil.getInstance(context);
         mContext = context;
@@ -117,6 +125,8 @@
         mBypassController = keyguardBypassController;
         mBubbleController = bubbleController;
         notificationListener.addNotificationSettingsListener(mSettingsListener);
+        mDemoModeController = demoModeController;
+        mDemoModeController.addCallback(this);
 
         initializeNotificationAreaViews(context);
         reloadAodColor();
@@ -160,9 +170,9 @@
         }
     }
 
-    public void setupShelf(NotificationShelf shelf) {
-        mShelfIcons = shelf.getShelfIcons();
-        shelf.setCollapsedIcons(mNotificationIcons);
+    public void setupShelf(NotificationShelfController notificationShelfController) {
+        mShelfIcons = notificationShelfController.getShelfIcons();
+        notificationShelfController.setCollapsedIcons(mNotificationIcons);
     }
 
     public void onDensityOrFontScaleChanged(Context context) {
@@ -666,4 +676,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 a87311a..42fbe59 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -86,6 +86,7 @@
 import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.NotificationShelfController;
 import com.android.systemui.statusbar.PulseExpansionHandler;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.StatusBarState;
@@ -105,6 +106,7 @@
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
 import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
@@ -174,6 +176,7 @@
     private final ZenModeController mZenModeController;
     private final ConfigurationController mConfigurationController;
     private final FlingAnimationUtils.Builder mFlingAnimationUtilsBuilder;
+    private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
 
     // Cap and total height of Roboto font. Needs to be adjusted when font for the big clock is
     // changed.
@@ -264,7 +267,6 @@
     private KeyguardStatusView mKeyguardStatusView;
     private View mQsNavbarScrim;
     private NotificationsQuickSettingsContainer mNotificationContainerParent;
-    private NotificationStackScrollLayout mNotificationStackScroller;
     private boolean mAnimateNextPositionUpdate;
 
     private int mTrackingPointer;
@@ -454,6 +456,7 @@
 
     private boolean mAnimatingQS;
     private int mOldLayoutDirection;
+    private NotificationShelfController mNotificationShelfController;
 
     private View.AccessibilityDelegate mAccessibilityDelegate = new View.AccessibilityDelegate() {
         @Override
@@ -498,7 +501,8 @@
             MediaHierarchyManager mediaHierarchyManager,
             BiometricUnlockController biometricUnlockController,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
-            Provider<KeyguardClockSwitchController> keyguardClockSwitchControllerProvider) {
+            Provider<KeyguardClockSwitchController> keyguardClockSwitchControllerProvider,
+            NotificationStackScrollLayoutController notificationStackScrollLayoutController) {
         super(view, falsingManager, dozeLog, keyguardStateController,
                 (SysuiStatusBarStateController) statusBarStateController, vibratorHelper,
                 latencyTracker, flingAnimationUtilsBuilder, statusBarTouchableRegionManager);
@@ -511,6 +515,7 @@
         mMediaHierarchyManager = mediaHierarchyManager;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
         mKeyguardClockSwitchControllerProvider = keyguardClockSwitchControllerProvider;
+        mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
         mView.setWillNotDraw(!DEBUG);
         mInjectionInflationController = injectionInflationController;
         mFalsingManager = falsingManager;
@@ -590,21 +595,26 @@
         keyguardClockSwitchController.setBigClockContainer(mBigClockContainer);
 
         mNotificationContainerParent = mView.findViewById(R.id.notification_container_parent);
-        mNotificationStackScroller = mView.findViewById(R.id.notification_stack_scroller);
-        mNotificationStackScroller.setOnHeightChangedListener(mOnHeightChangedListener);
-        mNotificationStackScroller.setOverscrollTopChangedListener(mOnOverscrollTopChangedListener);
-        mNotificationStackScroller.setOnEmptySpaceClickListener(mOnEmptySpaceClickListener);
-        addTrackingHeadsUpListener(mNotificationStackScroller::setTrackingHeadsUp);
+        NotificationStackScrollLayout stackScrollLayout = mView.findViewById(
+                R.id.notification_stack_scroller);
+        mNotificationStackScrollLayoutController.attach(stackScrollLayout);
+        mNotificationStackScrollLayoutController.setOnHeightChangedListener(
+                mOnHeightChangedListener);
+        mNotificationStackScrollLayoutController.setOverscrollTopChangedListener(
+                mOnOverscrollTopChangedListener);
+        mNotificationStackScrollLayoutController.setOnEmptySpaceClickListener(
+                mOnEmptySpaceClickListener);
+        addTrackingHeadsUpListener(mNotificationStackScrollLayoutController::setTrackingHeadsUp);
         mKeyguardBottomArea = mView.findViewById(R.id.keyguard_bottom_area);
         mQsNavbarScrim = mView.findViewById(R.id.qs_navbar_scrim);
         mLastOrientation = mResources.getConfiguration().orientation;
 
         initBottomArea();
 
-        mWakeUpCoordinator.setStackScroller(mNotificationStackScroller);
+        mWakeUpCoordinator.setStackScroller(mNotificationStackScrollLayoutController);
         mQsFrame = mView.findViewById(R.id.qs_frame);
         mPulseExpansionHandler.setUp(
-                mNotificationStackScroller, mExpansionCallback, mShadeController);
+                mNotificationStackScrollLayoutController, mExpansionCallback, mShadeController);
         mWakeUpCoordinator.addListener(new NotificationWakeUpCoordinator.WakeUpListener() {
             @Override
             public void onFullyHiddenChanged(boolean isFullyHidden) {
@@ -688,11 +698,11 @@
         }
 
         int panelWidth = mResources.getDimensionPixelSize(R.dimen.notification_panel_width);
-        lp = (FrameLayout.LayoutParams) mNotificationStackScroller.getLayoutParams();
+        lp = mNotificationStackScrollLayoutController.getLayoutParams();
         if (lp.width != panelWidth || lp.gravity != panelGravity) {
             lp.width = panelWidth;
             lp.gravity = panelGravity;
-            mNotificationStackScroller.setLayoutParams(lp);
+            mNotificationStackScrollLayoutController.setLayoutParams(lp);
         }
     }
 
@@ -770,7 +780,7 @@
 
     private void setIsFullWidth(boolean isFullWidth) {
         mIsFullWidth = isFullWidth;
-        mNotificationStackScroller.setIsFullWidth(isFullWidth);
+        mNotificationStackScrollLayoutController.setIsFullWidth(isFullWidth);
     }
 
     private void startQsSizeChangeAnimation(int oldHeight, final int newHeight) {
@@ -804,7 +814,7 @@
      * showing.
      */
     private void positionClockAndNotifications() {
-        boolean animate = mNotificationStackScroller.isAddOrRemoveAnimationPending();
+        boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
         boolean animateClock = animate || mAnimateNextPositionUpdate;
         int stackScrollerPadding;
         if (mBarState != StatusBarState.KEYGUARD) {
@@ -814,12 +824,12 @@
             int bottomPadding = Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding);
             int clockPreferredY = mKeyguardStatusView.getClockPreferredY(totalHeight);
             boolean bypassEnabled = mKeyguardBypassController.getBypassEnabled();
-            final boolean
-                    hasVisibleNotifications =
-                    !bypassEnabled && mNotificationStackScroller.getVisibleNotificationCount() != 0;
+            final boolean hasVisibleNotifications = !bypassEnabled
+                    && mNotificationStackScrollLayoutController.getVisibleNotificationCount() != 0;
             mKeyguardStatusView.setHasVisibleNotifications(hasVisibleNotifications);
             mClockPositionAlgorithm.setup(mStatusBarMinHeight, totalHeight - bottomPadding,
-                    mNotificationStackScroller.getIntrinsicContentHeight(), getExpandedFraction(),
+                    mNotificationStackScrollLayoutController.getIntrinsicContentHeight(),
+                    getExpandedFraction(),
                     totalHeight, (int) (mKeyguardStatusView.getHeight() - mShelfHeight / 2.0f
                             - mDarkIconSize / 2.0f), clockPreferredY, hasCustomClock(),
                     hasVisibleNotifications, mInterpolatedDarkAmount, mEmptyDragAmount,
@@ -833,7 +843,7 @@
             updateClock();
             stackScrollerPadding = mClockPositionResult.stackScrollerPaddingExpanded;
         }
-        mNotificationStackScroller.setIntrinsicPadding(stackScrollerPadding);
+        mNotificationStackScrollLayoutController.setIntrinsicPadding(stackScrollerPadding);
         mKeyguardBottomArea.setAntiBurnInOffsetX(mClockPositionResult.clockX);
 
         mStackScrollerMeasuringPass++;
@@ -858,36 +868,51 @@
         float minPadding = mClockPositionAlgorithm.getMinStackScrollerPadding();
         int notificationPadding = Math.max(
                 1, mResources.getDimensionPixelSize(R.dimen.notification_divider_height));
-        NotificationShelf shelf = mNotificationStackScroller.getNotificationShelf();
+        NotificationShelf shelf = mNotificationShelfController.getView();
         float
                 shelfSize =
                 shelf.getVisibility() == View.GONE ? 0
                         : shelf.getIntrinsicHeight() + notificationPadding;
         float
                 availableSpace =
-                mNotificationStackScroller.getHeight() - minPadding - shelfSize - Math.max(
-                        mIndicationBottomPadding, mAmbientIndicationBottomPadding)
+                mNotificationStackScrollLayoutController.getHeight() - minPadding - shelfSize
+                        - Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding)
                         - mKeyguardStatusView.getLogoutButtonHeight();
         int count = 0;
         ExpandableView previousView = null;
-        for (int i = 0; i < mNotificationStackScroller.getChildCount(); i++) {
-            ExpandableView child = (ExpandableView) mNotificationStackScroller.getChildAt(i);
+        for (int i = 0; i < mNotificationStackScrollLayoutController.getChildCount(); i++) {
+            ExpandableView child = mNotificationStackScrollLayoutController.getChildAt(i);
+            if (!(child instanceof ExpandableNotificationRow)) {
+                continue;
+            }
+            ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+            boolean
+                    suppressedSummary =
+                    mGroupManager != null && mGroupManager.isSummaryOfSuppressedGroup(
+                            row.getEntry().getSbn());
+            if (suppressedSummary) {
+                continue;
+            }
             if (!canShowViewOnLockscreen(child)) {
                 continue;
             }
+            if (row.isRemoved()) {
+                continue;
+            }
             availableSpace -= child.getMinHeight(true /* ignoreTemporaryStates */);
             availableSpace -= count == 0 ? 0 : notificationPadding;
-            availableSpace -= mNotificationStackScroller.calculateGapHeight(previousView, child,
-                    count);
+            availableSpace -= mNotificationStackScrollLayoutController
+                    .calculateGapHeight(previousView, child, count);
             previousView = child;
             if (availableSpace >= 0 && count < maximum) {
                 count++;
             } else if (availableSpace > -shelfSize) {
                 // if we are exactly the last view, then we can show us still!
-                for (int j = i + 1; j < mNotificationStackScroller.getChildCount(); j++) {
-                    ExpandableView view = (ExpandableView) mNotificationStackScroller.getChildAt(j);
-                    if (view instanceof ExpandableNotificationRow &&
-                            canShowViewOnLockscreen(view)) {
+                int childCount = mNotificationStackScrollLayoutController.getChildCount();
+                for (int j = i + 1; j < childCount; j++) {
+                    ExpandableView view = mNotificationStackScrollLayoutController.getChildAt(j);
+                    if (view instanceof ExpandableNotificationRow
+                            && canShowViewOnLockscreen(view)) {
                         return count;
                     }
                 }
@@ -952,7 +977,7 @@
     }
 
     public void animateToFullShade(long delay) {
-        mNotificationStackScroller.goToFullShade(delay);
+        mNotificationStackScrollLayoutController.goToFullShade(delay);
         mView.requestLayout();
         mAnimateNextPositionUpdate = true;
     }
@@ -978,9 +1003,9 @@
         } else {
             closeQs();
         }
-        mNotificationStackScroller.setOverScrollAmount(0f, true /* onTop */, animate,
+        mNotificationStackScrollLayoutController.setOverScrollAmount(0f, true /* onTop */, animate,
                 !animate /* cancelAnimators */);
-        mNotificationStackScroller.resetScrollPosition();
+        mNotificationStackScrollLayoutController.resetScrollPosition();
     }
 
     @Override
@@ -991,7 +1016,7 @@
 
         if (mQsExpanded) {
             mQsExpandImmediate = true;
-            mNotificationStackScroller.setShouldShowShelfOnly(true);
+            mNotificationStackScrollLayoutController.setShouldShowShelfOnly(true);
         }
         super.collapse(delayed, speedUpFactor);
     }
@@ -1027,7 +1052,7 @@
     public void expandWithQs() {
         if (mQsExpansionEnabled) {
             mQsExpandImmediate = true;
-            mNotificationStackScroller.setShouldShowShelfOnly(true);
+            mNotificationStackScrollLayoutController.setShouldShowShelfOnly(true);
         }
         if (isFullyCollapsed()) {
             expand(true /* animate */);
@@ -1088,7 +1113,7 @@
                     onQsExpansionStarted();
                     mInitialHeightOnTouch = mQsExpansionHeight;
                     mQsTracking = true;
-                    mNotificationStackScroller.cancelLongPress();
+                    mNotificationStackScrollLayoutController.cancelLongPress();
                 }
                 break;
             case MotionEvent.ACTION_POINTER_UP:
@@ -1124,7 +1149,7 @@
                     mInitialHeightOnTouch = mQsExpansionHeight;
                     mInitialTouchY = y;
                     mInitialTouchX = x;
-                    mNotificationStackScroller.cancelLongPress();
+                    mNotificationStackScrollLayoutController.cancelLongPress();
                     return true;
                 }
                 break;
@@ -1144,9 +1169,11 @@
 
     @Override
     protected boolean isInContentBounds(float x, float y) {
-        float stackScrollerX = mNotificationStackScroller.getX();
-        return !mNotificationStackScroller.isBelowLastNotification(x - stackScrollerX, y)
-                && stackScrollerX < x && x < stackScrollerX + mNotificationStackScroller.getWidth();
+        float stackScrollerX = mNotificationStackScrollLayoutController.getX();
+        return !mNotificationStackScrollLayoutController
+                .isBelowLastNotification(x - stackScrollerX, y)
+                && stackScrollerX < x
+                && x < stackScrollerX + mNotificationStackScrollLayoutController.getWidth();
     }
 
     private void initDownStates(MotionEvent event) {
@@ -1254,7 +1281,7 @@
 
     @Override
     protected float getOpeningHeight() {
-        return mNotificationStackScroller.getOpeningHeight();
+        return mNotificationStackScrollLayoutController.getOpeningHeight();
     }
 
 
@@ -1290,7 +1317,7 @@
                 < mStatusBarMinHeight) {
             mMetricsLogger.count(COUNTER_PANEL_OPEN_QS, 1);
             mQsExpandImmediate = true;
-            mNotificationStackScroller.setShouldShowShelfOnly(true);
+            mNotificationStackScrollLayoutController.setShouldShowShelfOnly(true);
             requestPanelHeightUpdate();
 
             // Normally, we start listening when the panel is expanded, but here we need to start
@@ -1302,7 +1329,7 @@
 
     private boolean isInQsArea(float x, float y) {
         return (x >= mQsFrame.getX() && x <= mQsFrame.getX() + mQsFrame.getWidth()) && (
-                y <= mNotificationStackScroller.getBottomMostNotificationBottom()
+                y <= mNotificationStackScrollLayoutController.getBottomMostNotificationBottom()
                         || y <= mQs.getView().getY() + mQs.getView().getHeight());
     }
 
@@ -1492,7 +1519,7 @@
         float height = mQsExpansionHeight - overscrollAmount;
         setQsExpansion(height);
         requestPanelHeightUpdate();
-        mNotificationStackScroller.checkSnoozeLeavebehind();
+        mNotificationStackScrollLayoutController.checkSnoozeLeavebehind();
 
         // When expanding QS, let's authenticate the user if possible,
         // this will speed up notification actions.
@@ -1665,8 +1692,8 @@
     }
 
     private void updateQsState() {
-        mNotificationStackScroller.setQsExpanded(mQsExpanded);
-        mNotificationStackScroller.setScrollingEnabled(
+        mNotificationStackScrollLayoutController.setQsExpanded(mQsExpanded);
+        mNotificationStackScrollLayoutController.setScrollingEnabled(
                 mBarState != StatusBarState.KEYGUARD && (!mQsExpanded
                         || mQsExpansionFromOverscroll));
         updateEmptyShadeView();
@@ -1726,7 +1753,7 @@
         float qsExpansionFraction = getQsExpansionFraction();
         mQs.setQsExpansion(qsExpansionFraction, getHeaderTranslation());
         mMediaHierarchyManager.setQsExpansion(qsExpansionFraction);
-        mNotificationStackScroller.setQsExpansionFraction(qsExpansionFraction);
+        mNotificationStackScrollLayoutController.setQsExpansionFraction(qsExpansionFraction);
     }
 
     private String determineAccessibilityPaneTitle() {
@@ -1785,18 +1812,18 @@
             return mClockPositionResult.stackScrollerPadding;
         }
         int collapsedPosition = mHeadsUpInset;
-        if (!mNotificationStackScroller.isPulseExpanding()) {
+        if (!mNotificationStackScrollLayoutController.isPulseExpanding()) {
             return collapsedPosition;
         } else {
             int expandedPosition = mClockPositionResult.stackScrollerPadding;
             return (int) MathUtils.lerp(collapsedPosition, expandedPosition,
-                    mNotificationStackScroller.calculateAppearFractionBypass());
+                    mNotificationStackScrollLayoutController.calculateAppearFractionBypass());
         }
     }
 
 
     protected void requestScrollerTopPaddingUpdate(boolean animate) {
-        mNotificationStackScroller.updateTopPadding(calculateQsTopPadding(), animate);
+        mNotificationStackScrollLayoutController.updateTopPadding(calculateQsTopPadding(), animate);
         if (mKeyguardShowing && mKeyguardBypassController.getBypassEnabled()) {
             // update the position of the header
             updateQsExpansion();
@@ -1808,7 +1835,7 @@
         if (mQs != null) {
             mQs.setShowCollapsedOnKeyguard(
                     mKeyguardShowing && mKeyguardBypassController.getBypassEnabled()
-                            && mNotificationStackScroller.isPulseExpanding());
+                            && mNotificationStackScrollLayoutController.isPulseExpanding());
         }
     }
 
@@ -1904,7 +1931,7 @@
             public void onAnimationEnd(Animator animation) {
                 mAnimatingQS = false;
                 notifyExpandingFinished();
-                mNotificationStackScroller.resetCheckSnoozeLeavebehind();
+                mNotificationStackScrollLayoutController.resetCheckSnoozeLeavebehind();
                 mQsExpansionAnimator = null;
                 if (onFinishRunnable != null) {
                     onFinishRunnable.run();
@@ -1943,8 +1970,8 @@
     protected boolean canCollapsePanelOnTouch() {
         if (!isInSettings()) {
             return mBarState == StatusBarState.KEYGUARD
-                    || mNotificationStackScroller.isScrolledToBottom()
-                    || mIsPanelCollapseOnQQS;
+                    || mIsPanelCollapseOnQQS
+                    || mNotificationStackScrollLayoutController.isScrolledToBottom();
         } else {
             return true;
         }
@@ -1962,7 +1989,7 @@
     private int getMaxPanelHeightNonBypass() {
         int min = mStatusBarMinHeight;
         if (!(mBarState == StatusBarState.KEYGUARD)
-                && mNotificationStackScroller.getNotGoneChildCount() == 0) {
+                && mNotificationStackScrollLayoutController.getNotGoneChildCount() == 0) {
             int minHeight = (int) (mQsMinExpansionHeight + getOverExpansionAmount());
             min = Math.max(min, minHeight);
         }
@@ -1988,7 +2015,7 @@
         int position =
                 mClockPositionAlgorithm.getExpandedClockPosition()
                         + mKeyguardStatusView.getHeight();
-        if (mNotificationStackScroller.getVisibleNotificationCount() != 0) {
+        if (mNotificationStackScrollLayoutController.getVisibleNotificationCount() != 0) {
             position += mShelfHeight / 2.0f + mDarkIconSize / 2.0f;
         }
         return position;
@@ -2027,8 +2054,8 @@
                 // minimum QS expansion + minStackHeight
                 float
                         panelHeightQsCollapsed =
-                        mNotificationStackScroller.getIntrinsicPadding()
-                                + mNotificationStackScroller.getLayoutMinHeight();
+                        mNotificationStackScrollLayoutController.getIntrinsicPadding()
+                                + mNotificationStackScrollLayoutController.getLayoutMinHeight();
                 float panelHeightQsExpanded = calculatePanelHeightQsExpanded();
                 t =
                         (expandedHeight - panelHeightQsCollapsed) / (panelHeightQsExpanded
@@ -2060,16 +2087,16 @@
     }
 
     private int calculatePanelHeightShade() {
-        int emptyBottomMargin = mNotificationStackScroller.getEmptyBottomMargin();
-        int maxHeight = mNotificationStackScroller.getHeight() - emptyBottomMargin;
-        maxHeight += mNotificationStackScroller.getTopPaddingOverflow();
+        int emptyBottomMargin = mNotificationStackScrollLayoutController.getEmptyBottomMargin();
+        int maxHeight = mNotificationStackScrollLayoutController.getHeight() - emptyBottomMargin;
+        maxHeight += mNotificationStackScrollLayoutController.getTopPaddingOverflow();
 
         if (mBarState == StatusBarState.KEYGUARD) {
             int
                     minKeyguardPanelBottom =
                     mClockPositionAlgorithm.getExpandedClockPosition()
                             + mKeyguardStatusView.getHeight()
-                            + mNotificationStackScroller.getIntrinsicContentHeight();
+                            + mNotificationStackScrollLayoutController.getIntrinsicContentHeight();
             return Math.max(maxHeight, minKeyguardPanelBottom);
         } else {
             return maxHeight;
@@ -2079,15 +2106,16 @@
     private int calculatePanelHeightQsExpanded() {
         float
                 notificationHeight =
-                mNotificationStackScroller.getHeight()
-                        - mNotificationStackScroller.getEmptyBottomMargin()
-                        - mNotificationStackScroller.getTopPadding();
+                mNotificationStackScrollLayoutController.getHeight()
+                        - mNotificationStackScrollLayoutController.getEmptyBottomMargin()
+                        - mNotificationStackScrollLayoutController.getTopPadding();
 
         // When only empty shade view is visible in QS collapsed state, simulate that we would have
         // it in expanded QS state as well so we don't run into troubles when fading the view in/out
         // and expanding/collapsing the whole panel from/to quick settings.
-        if (mNotificationStackScroller.getNotGoneChildCount() == 0 && mShowEmptyShadeView) {
-            notificationHeight = mNotificationStackScroller.getEmptyShadeViewHeight();
+        if (mNotificationStackScrollLayoutController.getNotGoneChildCount() == 0
+                && mShowEmptyShadeView) {
+            notificationHeight = mNotificationStackScrollLayoutController.getEmptyShadeViewHeight();
         }
         int maxQsHeight = mQsMaxExpansionHeight;
 
@@ -2102,12 +2130,13 @@
         float totalHeight = Math.max(maxQsHeight,
                 mBarState == StatusBarState.KEYGUARD ? mClockPositionResult.stackScrollerPadding
                         : 0) + notificationHeight
-                + mNotificationStackScroller.getTopPaddingOverflow();
-        if (totalHeight > mNotificationStackScroller.getHeight()) {
+                + mNotificationStackScrollLayoutController.getTopPaddingOverflow();
+        if (totalHeight > mNotificationStackScrollLayoutController.getHeight()) {
             float
                     fullyCollapsedHeight =
-                    maxQsHeight + mNotificationStackScroller.getLayoutMinHeight();
-            totalHeight = Math.max(fullyCollapsedHeight, mNotificationStackScroller.getHeight());
+                    maxQsHeight + mNotificationStackScrollLayoutController.getLayoutMinHeight();
+            totalHeight = Math.max(fullyCollapsedHeight,
+                    mNotificationStackScrollLayoutController.getHeight());
         }
         return (int) totalHeight;
     }
@@ -2122,7 +2151,7 @@
                 && !mKeyguardBypassController.getBypassEnabled()) {
             alpha *= mClockPositionResult.clockAlpha;
         }
-        mNotificationStackScroller.setAlpha(alpha);
+        mNotificationStackScrollLayoutController.setAlpha(alpha);
     }
 
     private float getFadeoutAlpha() {
@@ -2138,7 +2167,8 @@
 
     @Override
     protected float getOverExpansionAmount() {
-        float result = mNotificationStackScroller.getCurrentOverScrollAmount(true /* top */);
+        float result = mNotificationStackScrollLayoutController
+                .getCurrentOverScrollAmount(true /* top */);
         if (isNaN(result)) {
             Log.wtf(TAG, "OverExpansionAmount is NaN!");
         }
@@ -2148,7 +2178,8 @@
 
     @Override
     protected float getOverExpansionPixels() {
-        return mNotificationStackScroller.getCurrentOverScrolledPixels(true /* top */);
+        return mNotificationStackScrollLayoutController
+                .getCurrentOverScrolledPixels(true /* top */);
     }
 
     /**
@@ -2165,17 +2196,19 @@
         if (mBarState == StatusBarState.KEYGUARD && !mKeyguardBypassController.getBypassEnabled()) {
             return -mQs.getQsMinExpansionHeight();
         }
-        float appearAmount = mNotificationStackScroller.calculateAppearFraction(mExpandedHeight);
+        float appearAmount = mNotificationStackScrollLayoutController
+                .calculateAppearFraction(mExpandedHeight);
         float startHeight = -mQsExpansionHeight;
         if (mKeyguardBypassController.getBypassEnabled() && isOnKeyguard()
-                && mNotificationStackScroller.isPulseExpanding()) {
+                && mNotificationStackScrollLayoutController.isPulseExpanding()) {
             if (!mPulseExpansionHandler.isExpanding()
                     && !mPulseExpansionHandler.getLeavingLockscreen()) {
                 // If we aborted the expansion we need to make sure the header doesn't reappear
                 // again after the header has animated away
                 appearAmount = 0;
             } else {
-                appearAmount = mNotificationStackScroller.calculateAppearFractionBypass();
+                appearAmount = mNotificationStackScrollLayoutController
+                        .calculateAppearFractionBypass();
             }
             startHeight = -mQs.getQsMinExpansionHeight();
         }
@@ -2264,7 +2297,7 @@
     @Override
     protected void onExpandingStarted() {
         super.onExpandingStarted();
-        mNotificationStackScroller.onExpansionStarted();
+        mNotificationStackScrollLayoutController.onExpansionStarted();
         mIsExpanding = true;
         mQsExpandedWhenExpandingStarted = mQsFullyExpanded;
         mMediaHierarchyManager.setCollapsingShadeFromQS(mQsExpandedWhenExpandingStarted &&
@@ -2282,7 +2315,7 @@
     @Override
     protected void onExpandingFinished() {
         super.onExpandingFinished();
-        mNotificationStackScroller.onExpansionStopped();
+        mNotificationStackScrollLayoutController.onExpansionStopped();
         mHeadsUpManager.onExpandingFinished();
         mConversationNotificationManager.onNotificationPanelExpandStateChanged(isFullyCollapsed());
         mIsExpanding = false;
@@ -2308,7 +2341,7 @@
             setListening(true);
         }
         mQsExpandImmediate = false;
-        mNotificationStackScroller.setShouldShowShelfOnly(false);
+        mNotificationStackScrollLayoutController.setShouldShowShelfOnly(false);
         mTwoFingerQsExpandPossible = false;
         notifyListenersTrackingHeadsUp(null);
         mExpandingFromHeadsUp = false;
@@ -2340,15 +2373,16 @@
             return;
         }
         if (mBarState != StatusBarState.KEYGUARD) {
-            mNotificationStackScroller.setOnHeightChangedListener(null);
+            mNotificationStackScrollLayoutController.setOnHeightChangedListener(null);
             if (isPixels) {
-                mNotificationStackScroller.setOverScrolledPixels(overExpansion, true /* onTop */,
-                        false /* animate */);
+                mNotificationStackScrollLayoutController.setOverScrolledPixels(
+                        overExpansion, true /* onTop */, false /* animate */);
             } else {
-                mNotificationStackScroller.setOverScrollAmount(overExpansion, true /* onTop */,
-                        false /* animate */);
+                mNotificationStackScrollLayoutController.setOverScrollAmount(
+                        overExpansion, true /* onTop */, false /* animate */);
             }
-            mNotificationStackScroller.setOnHeightChangedListener(mOnHeightChangedListener);
+            mNotificationStackScrollLayoutController
+                    .setOnHeightChangedListener(mOnHeightChangedListener);
         }
     }
 
@@ -2358,12 +2392,12 @@
         super.onTrackingStarted();
         if (mQsFullyExpanded) {
             mQsExpandImmediate = true;
-            mNotificationStackScroller.setShouldShowShelfOnly(true);
+            mNotificationStackScrollLayoutController.setShouldShowShelfOnly(true);
         }
         if (mBarState == StatusBarState.KEYGUARD || mBarState == StatusBarState.SHADE_LOCKED) {
             mAffordanceHelper.animateHideLeftRightIcon();
         }
-        mNotificationStackScroller.onPanelTrackingStarted();
+        mNotificationStackScrollLayoutController.onPanelTrackingStarted();
     }
 
     @Override
@@ -2371,10 +2405,10 @@
         mFalsingManager.onTrackingStopped();
         super.onTrackingStopped(expand);
         if (expand) {
-            mNotificationStackScroller.setOverScrolledPixels(0.0f, true /* onTop */,
+            mNotificationStackScrollLayoutController.setOverScrolledPixels(0.0f, true /* onTop */,
                     true /* animate */);
         }
-        mNotificationStackScroller.onPanelTrackingStopped();
+        mNotificationStackScrollLayoutController.onPanelTrackingStopped();
         if (expand && (mBarState == StatusBarState.KEYGUARD
                 || mBarState == StatusBarState.SHADE_LOCKED)) {
             if (!mHintAnimationRunning) {
@@ -2384,7 +2418,8 @@
     }
 
     private void updateMaxHeadsUpTranslation() {
-        mNotificationStackScroller.setHeadsUpBoundaries(getHeight(), mNavigationBarBottomHeight);
+        mNotificationStackScrollLayoutController.setHeadsUpBoundaries(
+                getHeight(), mNavigationBarBottomHeight);
     }
 
     @Override
@@ -2400,19 +2435,19 @@
     @Override
     protected void onUnlockHintFinished() {
         super.onUnlockHintFinished();
-        mNotificationStackScroller.setUnlockHintRunning(false);
+        mNotificationStackScrollLayoutController.setUnlockHintRunning(false);
     }
 
     @Override
     protected void onUnlockHintStarted() {
         super.onUnlockHintStarted();
-        mNotificationStackScroller.setUnlockHintRunning(true);
+        mNotificationStackScrollLayoutController.setUnlockHintRunning(true);
     }
 
     @Override
     protected float getPeekHeight() {
-        if (mNotificationStackScroller.getNotGoneChildCount() > 0) {
-            return mNotificationStackScroller.getPeekHeight();
+        if (mNotificationStackScrollLayoutController.getNotGoneChildCount() > 0) {
+            return mNotificationStackScrollLayoutController.getPeekHeight();
         } else {
             return mQsMinExpansionHeight;
         }
@@ -2426,7 +2461,8 @@
         }
         // Let's make sure we're not appearing but the animation will end below the appear.
         // Otherwise quick settings would jump at the end of the animation.
-        float fraction = mNotificationStackScroller.calculateAppearFraction(targetHeight);
+        float fraction = mNotificationStackScrollLayoutController
+                .calculateAppearFraction(targetHeight);
         return fraction >= 1.0f;
     }
 
@@ -2438,18 +2474,19 @@
 
     @Override
     protected boolean fullyExpandedClearAllVisible() {
-        return mNotificationStackScroller.isFooterViewNotGone()
-                && mNotificationStackScroller.isScrolledToBottom() && !mQsExpandImmediate;
+        return mNotificationStackScrollLayoutController.isFooterViewNotGone()
+                && mNotificationStackScrollLayoutController.isScrolledToBottom()
+                && !mQsExpandImmediate;
     }
 
     @Override
     protected boolean isClearAllVisible() {
-        return mNotificationStackScroller.isFooterViewContentVisible();
+        return mNotificationStackScrollLayoutController.isFooterViewContentVisible();
     }
 
     @Override
     protected int getClearAllHeightWithPadding() {
-        return mNotificationStackScroller.getFooterViewHeightWithPadding();
+        return mNotificationStackScrollLayoutController.getFooterViewHeightWithPadding();
     }
 
     @Override
@@ -2500,7 +2537,8 @@
 
     private void updateEmptyShadeView() {
         // Hide "No notifications" in QS.
-        mNotificationStackScroller.updateEmptyShadeView(mShowEmptyShadeView && !mQsExpanded);
+        mNotificationStackScrollLayoutController.updateEmptyShadeView(
+                mShowEmptyShadeView && !mQsExpanded);
     }
 
     public void setQsScrimEnabled(boolean qsScrimEnabled) {
@@ -2591,7 +2629,7 @@
 
     public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
         mHeadsUpAnimatingAway = headsUpAnimatingAway;
-        mNotificationStackScroller.setHeadsUpAnimatingAway(headsUpAnimatingAway);
+        mNotificationStackScrollLayoutController.setHeadsUpAnimatingAway(headsUpAnimatingAway);
         updateHeadsUpVisibility();
     }
 
@@ -2603,7 +2641,7 @@
     public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
         super.setHeadsUpManager(headsUpManager);
         mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager,
-                mNotificationStackScroller.getHeadsUpCallback(),
+                mNotificationStackScrollLayoutController.getHeadsUpCallback(),
                 NotificationPanelViewController.this);
     }
 
@@ -2625,7 +2663,7 @@
 
     private void setClosingWithAlphaFadeout(boolean closing) {
         mClosingWithAlphaFadeOut = closing;
-        mNotificationStackScroller.forceNoOverlappingRendering(closing);
+        mNotificationStackScrollLayoutController.forceNoOverlappingRendering(closing);
     }
 
     /**
@@ -2635,22 +2673,24 @@
      * @param x the x-coordinate the touch event
      */
     protected void updateVerticalPanelPosition(float x) {
-        if (mNotificationStackScroller.getWidth() * 1.75f > mView.getWidth()) {
+        if (mNotificationStackScrollLayoutController.getWidth() * 1.75f > mView.getWidth()) {
             resetHorizontalPanelPosition();
             return;
         }
-        float leftMost = mPositionMinSideMargin + mNotificationStackScroller.getWidth() / 2;
+        float leftMost = mPositionMinSideMargin
+                + mNotificationStackScrollLayoutController.getWidth() / 2;
         float
                 rightMost =
                 mView.getWidth() - mPositionMinSideMargin
-                        - mNotificationStackScroller.getWidth() / 2;
-        if (Math.abs(x - mView.getWidth() / 2) < mNotificationStackScroller.getWidth() / 4) {
+                        - mNotificationStackScrollLayoutController.getWidth() / 2;
+        if (Math.abs(x - mView.getWidth() / 2)
+                < mNotificationStackScrollLayoutController.getWidth() / 4) {
             x = mView.getWidth() / 2;
         }
         x = Math.min(rightMost, Math.max(leftMost, x));
         float
-                center =
-                mNotificationStackScroller.getLeft() + mNotificationStackScroller.getWidth() / 2;
+                center = mNotificationStackScrollLayoutController.getLeft()
+                + mNotificationStackScrollLayoutController.getWidth() / 2;
         setHorizontalPanelTranslation(x - center);
     }
 
@@ -2659,7 +2699,7 @@
     }
 
     protected void setHorizontalPanelTranslation(float translation) {
-        mNotificationStackScroller.setTranslationX(translation);
+        mNotificationStackScrollLayoutController.setTranslationX(translation);
         mQsFrame.setTranslationX(translation);
         int size = mVerticalTranslationListener.size();
         for (int i = 0; i < size; i++) {
@@ -2669,13 +2709,14 @@
 
     protected void updateExpandedHeight(float expandedHeight) {
         if (mTracking) {
-            mNotificationStackScroller.setExpandingVelocity(getCurrentExpandVelocity());
+            mNotificationStackScrollLayoutController
+                    .setExpandingVelocity(getCurrentExpandVelocity());
         }
         if (mKeyguardBypassController.getBypassEnabled() && isOnKeyguard()) {
             // The expandedHeight is always the full panel Height when bypassing
             expandedHeight = getMaxPanelHeightNonBypass();
         }
-        mNotificationStackScroller.setExpandedHeight(expandedHeight);
+        mNotificationStackScrollLayoutController.setExpandedHeight(expandedHeight);
         updateKeyguardBottomAreaAlpha();
         updateBigClockAlpha();
         updateStatusBarIcons();
@@ -2835,7 +2876,7 @@
                             mHeightListener.onQsHeightChanged();
                         }
                     });
-            mNotificationStackScroller.setQsContainer((ViewGroup) mQs.getView());
+            mNotificationStackScrollLayoutController.setQsContainer((ViewGroup) mQs.getView());
             if (mQs instanceof QSFragment) {
                 mKeyguardStatusBar.setQSPanel(((QSFragment) mQs).getQsPanel());
             }
@@ -2859,7 +2900,7 @@
         if (disabled && mAffordanceHelper.isSwipingInProgress() && !mIsLaunchTransitionRunning) {
             mAffordanceHelper.reset(false /* animate */);
         }
-        mNotificationStackScroller.setAnimationsEnabled(!disabled);
+        mNotificationStackScrollLayoutController.setAnimationsEnabled(!disabled);
     }
 
     /**
@@ -2873,7 +2914,7 @@
         if (dozing == mDozing) return;
         mView.setDozing(dozing);
         mDozing = dozing;
-        mNotificationStackScroller.setDozing(mDozing, animate, wakeUpTouchLocation);
+        mNotificationStackScrollLayoutController.setDozing(mDozing, animate, wakeUpTouchLocation);
         mKeyguardBottomArea.setDozing(mDozing, animate);
 
         if (dozing) {
@@ -2901,7 +2942,7 @@
         if (!mPulsing && !mDozing) {
             mAnimateNextPositionUpdate = false;
         }
-        mNotificationStackScroller.setPulsing(pulsing, animatePulse);
+        mNotificationStackScrollLayoutController.setPulsing(pulsing, animatePulse);
         mKeyguardStatusView.setPulsing(pulsing);
     }
 
@@ -3006,7 +3047,7 @@
     }
 
     public boolean hasActiveClearableNotifications() {
-        return mNotificationStackScroller.hasActiveClearableNotifications(ROWS_ALL);
+        return mNotificationStackScrollLayoutController.hasActiveClearableNotifications(ROWS_ALL);
     }
 
     private void updateShowEmptyShadeView() {
@@ -3017,54 +3058,57 @@
     }
 
     public RemoteInputController.Delegate createRemoteInputDelegate() {
-        return mNotificationStackScroller.createDelegate();
+        return mNotificationStackScrollLayoutController.createDelegate();
     }
 
     void updateNotificationViews(String reason) {
-        mNotificationStackScroller.updateSectionBoundaries(reason);
-        mNotificationStackScroller.updateSpeedBumpIndex();
-        mNotificationStackScroller.updateFooter();
+        mNotificationStackScrollLayoutController.updateSectionBoundaries(reason);
+        mNotificationStackScrollLayoutController.updateSpeedBumpIndex();
+        mNotificationStackScrollLayoutController.updateFooter();
         updateShowEmptyShadeView();
-        mNotificationStackScroller.updateIconAreaViews();
+        mNotificationStackScrollLayoutController.updateIconAreaViews();
     }
 
     public void onUpdateRowStates() {
-        mNotificationStackScroller.onUpdateRowStates();
+        mNotificationStackScrollLayoutController.onUpdateRowStates();
     }
 
     public boolean hasPulsingNotifications() {
-        return mNotificationStackScroller.hasPulsingNotifications();
+        return mNotificationStackScrollLayoutController
+                .getNotificationListContainer().hasPulsingNotifications();
     }
 
     public ActivatableNotificationView getActivatedChild() {
-        return mNotificationStackScroller.getActivatedChild();
+        return mNotificationStackScrollLayoutController.getActivatedChild();
     }
 
     public void setActivatedChild(ActivatableNotificationView o) {
-        mNotificationStackScroller.setActivatedChild(o);
+        mNotificationStackScrollLayoutController.setActivatedChild(o);
     }
 
     public void runAfterAnimationFinished(Runnable r) {
-        mNotificationStackScroller.runAfterAnimationFinished(r);
+        mNotificationStackScrollLayoutController.runAfterAnimationFinished(r);
     }
 
     public void setScrollingEnabled(boolean b) {
-        mNotificationStackScroller.setScrollingEnabled(b);
+        mNotificationStackScrollLayoutController.setScrollingEnabled(b);
     }
 
     public void initDependencies(StatusBar statusBar, NotificationGroupManager groupManager,
-            NotificationShelf notificationShelf,
+            NotificationShelfController notificationShelfController,
             NotificationIconAreaController notificationIconAreaController,
             ScrimController scrimController) {
         setStatusBar(statusBar);
         setGroupManager(mGroupManager);
-        mNotificationStackScroller.setNotificationPanelController(this);
-        mNotificationStackScroller.setIconAreaController(notificationIconAreaController);
-        mNotificationStackScroller.setStatusBar(statusBar);
-        mNotificationStackScroller.setGroupManager(groupManager);
-        mNotificationStackScroller.setShelf(notificationShelf);
-        mNotificationStackScroller.setScrimController(scrimController);
+        mNotificationStackScrollLayoutController.setNotificationPanelController(this);
+        mNotificationStackScrollLayoutController
+                .setIconAreaController(notificationIconAreaController);
+        mNotificationStackScrollLayoutController.setStatusBar(statusBar);
+        mNotificationStackScrollLayoutController.setGroupManager(groupManager);
+        mNotificationStackScrollLayoutController.setShelfController(notificationShelfController);
+        mNotificationStackScrollLayoutController.setScrimController(scrimController);
         updateShowEmptyShadeView();
+        mNotificationShelfController = notificationShelfController;
     }
 
     public void showTransientIndication(int id) {
@@ -3216,6 +3260,10 @@
         return new OnConfigurationChangedListener();
     }
 
+    public NotificationStackScrollLayoutController getNotificationStackScrollLayoutController() {
+        return mNotificationStackScrollLayoutController;
+    }
+
     private class OnHeightChangedListener implements ExpandableView.OnHeightChangedListener {
         @Override
         public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
@@ -3228,7 +3276,8 @@
             if (needsAnimation && mInterpolatedDarkAmount == 0) {
                 mAnimateNextPositionUpdate = true;
             }
-            ExpandableView firstChildNotGone = mNotificationStackScroller.getFirstChildNotGone();
+            ExpandableView firstChildNotGone =
+                    mNotificationStackScrollLayoutController.getFirstChildNotGone();
             ExpandableNotificationRow
                     firstRow =
                     firstChildNotGone instanceof ExpandableNotificationRow
@@ -3454,13 +3503,13 @@
     private class MyOnHeadsUpChangedListener implements OnHeadsUpChangedListener {
         @Override
         public void onHeadsUpPinnedModeChanged(final boolean inPinnedMode) {
-            mNotificationStackScroller.setInHeadsUpPinnedMode(inPinnedMode);
+            mNotificationStackScrollLayoutController.setInHeadsUpPinnedMode(inPinnedMode);
             if (inPinnedMode) {
                 mHeadsUpExistenceChangedRunnable.run();
                 updateNotificationTranslucency();
             } else {
                 setHeadsUpAnimatingAway(true);
-                mNotificationStackScroller.runAfterAnimationFinished(
+                mNotificationStackScrollLayoutController.runAfterAnimationFinished(
                         mHeadsUpExistenceChangedRunnable);
             }
             updateGestureExclusionRect();
@@ -3472,8 +3521,8 @@
         @Override
         public void onHeadsUpPinned(NotificationEntry entry) {
             if (!isOnKeyguard()) {
-                mNotificationStackScroller.generateHeadsUpAnimation(entry.getHeadsUpAnimationView(),
-                        true);
+                mNotificationStackScrollLayoutController.generateHeadsUpAnimation(
+                        entry.getHeadsUpAnimationView(), true);
             }
         }
 
@@ -3485,7 +3534,7 @@
             // notification
             // will stick to the top without any interaction.
             if (isFullyCollapsed() && entry.isRowHeadsUp() && !isOnKeyguard()) {
-                mNotificationStackScroller.generateHeadsUpAnimation(
+                mNotificationStackScrollLayoutController.generateHeadsUpAnimation(
                         entry.getHeadsUpAnimationView(), false);
                 entry.setHeadsUpIsVisible();
             }
@@ -3493,7 +3542,7 @@
 
         @Override
         public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
-            mNotificationStackScroller.generateHeadsUpAnimation(entry, isHeadsUp);
+            mNotificationStackScrollLayoutController.generateHeadsUpAnimation(entry, isHeadsUp);
         }
     }
 
@@ -3508,7 +3557,7 @@
             if (mAccessibilityManager.isEnabled()) {
                 mView.setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
             }
-            mNotificationStackScroller.setMaxTopPadding(
+            mNotificationStackScrollLayoutController.setMaxTopPadding(
                     mQsMaxExpansionHeight + mQsNotificationTopPadding);
         }
     }
@@ -3570,7 +3619,7 @@
             } else if (oldState == StatusBarState.SHADE_LOCKED
                     && statusBarState == StatusBarState.KEYGUARD) {
                 animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD);
-                mNotificationStackScroller.resetScrollPosition();
+                mNotificationStackScrollLayoutController.resetScrollPosition();
                 // Only animate header if the header is visible. If not, it will partially
                 // animate out
                 // the top of QS
@@ -3646,7 +3695,7 @@
                 int oldTop, int oldRight, int oldBottom) {
             DejankUtils.startDetectingBlockingIpcs("NVP#onLayout");
             super.onLayoutChange(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom);
-            setIsFullWidth(mNotificationStackScroller.getWidth() == mView.getWidth());
+            setIsFullWidth(mNotificationStackScrollLayoutController.getWidth() == mView.getWidth());
 
             // Update Clock Pivot
             mKeyguardStatusView.setPivotX(mView.getWidth() / 2);
@@ -3662,7 +3711,7 @@
                     mQsExpansionHeight = mQsMinExpansionHeight;
                 }
                 mQsMaxExpansionHeight = mQs.getDesiredHeight();
-                mNotificationStackScroller.setMaxTopPadding(
+                mNotificationStackScrollLayoutController.setMaxTopPadding(
                         mQsMaxExpansionHeight + mQsNotificationTopPadding);
             }
             positionClockAndNotifications();
@@ -3724,7 +3773,7 @@
                     0, calculateQsTopPadding(), mView.getWidth(), calculateQsTopPadding(), p);
             p.setColor(Color.CYAN);
             canvas.drawLine(0, mClockPositionResult.stackScrollerPadding, mView.getWidth(),
-                    mNotificationStackScroller.getTopPadding(), p);
+                    mNotificationStackScrollLayoutController.getTopPadding(), p);
             p.setColor(Color.GRAY);
             canvas.drawLine(0, mClockPositionResult.clockY, mView.getWidth(),
                     mClockPositionResult.clockY, p);
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..07b0a4b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
@@ -47,7 +47,7 @@
 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;
@@ -72,8 +72,8 @@
  * Encapsulates all logic for the notification shade window state management.
  */
 @Singleton
-public class NotificationShadeWindowController implements Callback, Dumpable,
-        ConfigurationListener {
+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/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 8cb54ee..e42c3dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -284,13 +284,22 @@
                 mResources.getString(R.string.accessibility_data_saver_on));
         mIconController.setIconVisibility(mSlotDataSaver, false);
 
+
         // privacy items
+        String microphoneString = mResources.getString(PrivacyType.TYPE_MICROPHONE.getNameId());
+        String microphoneDesc = mResources.getString(
+                R.string.ongoing_privacy_chip_content_multiple_apps, microphoneString);
         mIconController.setIcon(mSlotMicrophone, PrivacyType.TYPE_MICROPHONE.getIconId(),
-                mResources.getString(PrivacyType.TYPE_MICROPHONE.getNameId()));
+                microphoneDesc);
         mIconController.setIconVisibility(mSlotMicrophone, false);
+
+        String cameraString = mResources.getString(PrivacyType.TYPE_CAMERA.getNameId());
+        String cameraDesc = mResources.getString(
+                R.string.ongoing_privacy_chip_content_multiple_apps, cameraString);
         mIconController.setIcon(mSlotCamera, PrivacyType.TYPE_CAMERA.getIconId(),
-                mResources.getString(PrivacyType.TYPE_CAMERA.getNameId()));
+                cameraDesc);
         mIconController.setIconVisibility(mSlotCamera, false);
+
         mIconController.setIcon(mSlotLocation, LOCATION_STATUS_ICON_ID,
                 mResources.getString(R.string.accessibility_location_active));
         mIconController.setIconVisibility(mSlotLocation, false);
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..921bd4f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
@@ -26,6 +26,7 @@
 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;
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 eb62628..5b251be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -138,7 +138,6 @@
 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;
@@ -153,12 +152,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;
@@ -179,17 +183,16 @@
 import com.android.systemui.statusbar.BackDropView;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.CrossFadeHelper;
-import com.android.systemui.statusbar.EmptyShadeView;
 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.NotificationShelf;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.NotificationShelfController;
 import com.android.systemui.statusbar.NotificationViewHierarchyManager;
 import com.android.systemui.statusbar.PulseExpansionHandler;
 import com.android.systemui.statusbar.ScrimView;
@@ -210,6 +213,8 @@
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 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 +236,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;
@@ -395,6 +402,7 @@
     private final ExtensionController mExtensionController;
     private final UserInfoControllerImpl mUserInfoControllerImpl;
     private final DismissCallbackRegistry mDismissCallbackRegistry;
+    private final DemoModeController mDemoModeController;
     private NotificationsController mNotificationsController;
 
     // expanded notifications
@@ -646,6 +654,7 @@
     private final BubbleController.BubbleExpandListener mBubbleExpandListener;
 
     private ActivityIntentHelper mActivityIntentHelper;
+    private NotificationStackScrollLayoutController mStackScrollerController;
 
     /**
      * Public constructor for StatusBar.
@@ -731,6 +740,7 @@
             PhoneStatusBarPolicy phoneStatusBarPolicy,
             KeyguardIndicationController keyguardIndicationController,
             DismissCallbackRegistry dismissCallbackRegistry,
+            DemoModeController demoModeController,
             Lazy<NotificationShadeDepthController> notificationShadeDepthControllerLazy,
             StatusBarTouchableRegionManager statusBarTouchableRegionManager) {
         super(context);
@@ -809,6 +819,7 @@
         mUserInfoControllerImpl = userInfoControllerImpl;
         mIconPolicy = phoneStatusBarPolicy;
         mDismissCallbackRegistry = dismissCallbackRegistry;
+        mDemoModeController = demoModeController;
 
         mBubbleExpandListener =
                 (isExpanding, key) -> {
@@ -863,6 +874,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);
@@ -1016,9 +1030,11 @@
 
         // 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
@@ -1026,10 +1042,10 @@
         mNotificationIconAreaController = SystemUIFactory.getInstance()
                 .createNotificationIconAreaController(context, this,
                         mWakeUpCoordinator, mKeyguardBypassController,
-                        mStatusBarStateController);
+                        mStatusBarStateController, mDemoModeController);
         mWakeUpCoordinator.setIconAreaController(mNotificationIconAreaController);
         inflateShelf();
-        mNotificationIconAreaController.setupShelf(mNotificationShelf);
+        mNotificationIconAreaController.setupShelf(mNotificationShelfController);
         mNotificationPanelViewController.setOnReinflationListener(
                 mNotificationIconAreaController::initAodIcons);
         mNotificationPanelViewController.addExpansionListener(mWakeUpCoordinator);
@@ -1076,7 +1092,7 @@
                     // TODO (b/136993073) Separate notification shade and status bar
                     mHeadsUpAppearanceController = new HeadsUpAppearanceController(
                             mNotificationIconAreaController, mHeadsUpManager,
-                            mNotificationShadeWindowView,
+                            mStackScroller.getController(),
                             mStatusBarStateController, mKeyguardBypassController,
                             mKeyguardStateController, mWakeUpCoordinator, mCommandQueue,
                             mNotificationPanelViewController, mStatusBarView);
@@ -1147,7 +1163,8 @@
         });
         mScrimController.attachViews(scrimBehind, scrimInFront, scrimForBubble);
 
-        mNotificationPanelViewController.initDependencies(this, mGroupManager, mNotificationShelf,
+        mNotificationPanelViewController.initDependencies(this, mGroupManager,
+                mNotificationShelfController,
                 mNotificationIconAreaController, mScrimController);
 
         BackDropView backdrop = mNotificationShadeWindowView.findViewById(R.id.backdrop);
@@ -1248,7 +1265,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);
 
@@ -1300,17 +1316,19 @@
         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);
 
-        mNotificationShelf.setOnActivatedListener(mPresenter);
+        mNotificationShelfController.setOnActivatedListener(mPresenter);
         mRemoteInputManager.getController().addCallback(mNotificationShadeWindowController);
 
         mNotificationActivityStarter =
@@ -1320,16 +1338,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);
     }
@@ -1386,8 +1401,9 @@
     }
 
     private void inflateShelf() {
-        mNotificationShelf = mSuperStatusBarViewFactory.getNotificationShelf(mStackScroller);
-        mNotificationShelf.setOnClickListener(mGoToLockedShadeListener);
+        mNotificationShelfController = mSuperStatusBarViewFactory
+                .getNotificationShelfController(mStackScroller);
+        mNotificationShelfController.setOnClickListener(mGoToLockedShadeListener);
     }
 
     @Override
@@ -1458,6 +1474,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,
@@ -1500,7 +1544,7 @@
         return mStatusBarWindowController.getStatusBarHeight();
     }
 
-    protected boolean toggleSplitScreenMode(int metricsDockAction, int metricsUndockAction) {
+    public boolean toggleSplitScreenMode(int metricsDockAction, int metricsUndockAction) {
         if (!mRecentsOptional.isPresent()) {
             return false;
         }
@@ -2433,8 +2477,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());
         }
@@ -2443,7 +2487,7 @@
     }
 
     // Called by NavigationBarFragment
-    void setQsScrimEnabled(boolean scrimEnabled) {
+    public void setQsScrimEnabled(boolean scrimEnabled) {
         mNotificationPanelViewController.setQsScrimEnabled(scrimEnabled);
     }
 
@@ -2616,7 +2660,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()));
     }
@@ -2807,19 +2851,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);
                 }
@@ -3083,50 +3115,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 :
@@ -3145,16 +3161,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();
         }
     }
 
@@ -4049,7 +4082,7 @@
     protected IStatusBarService mBarService;
 
     // all notifications
-    protected ViewGroup mStackScroller;
+    protected NotificationStackScrollLayout mStackScroller;
 
     private final NotificationGroupManager mGroupManager;
 
@@ -4085,8 +4118,7 @@
 
     private final Optional<Recents> mRecentsOptional;
 
-    protected NotificationShelf mNotificationShelf;
-    protected EmptyShadeView mEmptyShadeView;
+    protected NotificationShelfController mNotificationShelfController;
 
     private final Lazy<AssistManager> mAssistManagerLazy;
 
@@ -4131,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..60ef523 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,8 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
+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;
@@ -54,22 +56,20 @@
  */
 @Singleton
 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..e159fad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -49,12 +49,14 @@
 import com.android.systemui.SystemUIFactory;
 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;
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..b9f94b3 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,6 @@
 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.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.plugins.ActivityStarter;
@@ -93,7 +92,6 @@
 
     private final CommandQueue mCommandQueue;
     private final Handler mMainThreadHandler;
-    private final Handler mBackgroundHandler;
     private final Executor mUiBgExecutor;
 
     private final NotificationEntryManager mEntryManager;
@@ -134,7 +132,6 @@
             Context context,
             CommandQueue commandQueue,
             Handler mainThreadHandler,
-            Handler backgroundHandler,
             Executor uiBgExecutor,
             NotificationEntryManager entryManager,
             NotifPipeline notifPipeline,
@@ -170,7 +167,6 @@
         mContext = context;
         mCommandQueue = commandQueue;
         mMainThreadHandler = mainThreadHandler;
-        mBackgroundHandler = backgroundHandler;
         mUiBgExecutor = uiBgExecutor;
         mEntryManager = entryManager;
         mNotifPipeline = notifPipeline;
@@ -307,7 +303,7 @@
             mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable);
             mShadeController.collapsePanel();
         } else {
-            mBackgroundHandler.postAtFrontOfQueue(runnable);
+            runnable.run();
         }
         return !mNotificationPanel.isFullyCollapsed();
     }
@@ -605,7 +601,7 @@
         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;
@@ -644,7 +640,6 @@
                 Context context,
                 CommandQueue commandQueue,
                 @Main Handler mainThreadHandler,
-                @Background Handler backgroundHandler,
                 @UiBackground Executor uiBgExecutor,
                 NotificationEntryManager entryManager,
                 NotifPipeline notifPipeline,
@@ -676,7 +671,6 @@
             mContext = context;
             mCommandQueue = commandQueue;
             mMainThreadHandler = mainThreadHandler;
-            mBackgroundHandler = backgroundHandler;
             mUiBgExecutor = uiBgExecutor;
             mEntryManager = entryManager;
             mNotifPipeline = notifPipeline;
@@ -734,7 +728,6 @@
                     mContext,
                     mCommandQueue,
                     mMainThreadHandler,
-                    mBackgroundHandler,
                     mUiBgExecutor,
                     mEntryManager,
                     mNotifPipeline,
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..0ec22b4 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;
@@ -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);
@@ -219,7 +219,8 @@
             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 72395e6..ac69d9c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
@@ -244,9 +244,10 @@
 
     @Override
     public boolean handleRemoteViewClick(View view, PendingIntent pendingIntent,
+            boolean appRequestedAuth,
             NotificationRemoteInputManager.ClickHandler defaultHandler) {
         final boolean isActivity = pendingIntent.isActivity();
-        if (isActivity) {
+        if (isActivity || appRequestedAuth) {
             mActionClickLogger.logWaitingToCloseKeyguard(pendingIntent);
             final boolean afterKeyguardGone = mActivityIntentHelper.wouldLaunchResolverActivity(
                     pendingIntent.getIntent(), mLockscreenUserManager.getCurrentUserId());
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..b79bb70 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,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.ScreenDecorations;
+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;
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..d230eca 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
@@ -34,10 +34,12 @@
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 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.navigationbar.NavigationBarController;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.PluginDependencyProvider;
@@ -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;
@@ -79,7 +81,6 @@
 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.PhoneStatusBarPolicy;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.ShadeController;
@@ -196,6 +197,7 @@
             UserInfoControllerImpl userInfoControllerImpl,
             PhoneStatusBarPolicy phoneStatusBarPolicy,
             KeyguardIndicationController keyguardIndicationController,
+            DemoModeController demoModeController,
             Lazy<NotificationShadeDepthController> notificationShadeDepthController,
             DismissCallbackRegistry dismissCallbackRegistry,
             StatusBarTouchableRegionManager statusBarTouchableRegionManager) {
@@ -275,6 +277,7 @@
                 phoneStatusBarPolicy,
                 keyguardIndicationController,
                 dismissCallbackRegistry,
+                demoModeController,
                 notificationShadeDepthController,
                 statusBarTouchableRegionManager);
     }
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..c0caabd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java
@@ -19,6 +19,8 @@
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener;
 
+import androidx.annotation.NonNull;
+
 import javax.inject.Inject;
 import javax.inject.Singleton;
 
@@ -37,12 +39,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..b7dc821 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;
@@ -36,11 +37,14 @@
 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.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;
@@ -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..8110ab4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -29,6 +29,8 @@
 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;
@@ -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..f38c731 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
@@ -31,6 +31,7 @@
 import android.util.ArrayMap;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.annotations.GuardedBy;
@@ -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..071f05a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java
@@ -24,6 +24,8 @@
 import android.provider.Settings.Secure;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.settings.CurrentUserTracker;
@@ -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/FlashlightControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
index 41ff9d10..659212c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
@@ -30,6 +30,8 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
@@ -123,7 +125,8 @@
         return mTorchAvailable;
     }
 
-    public void addCallback(FlashlightListener l) {
+    @Override
+    public void addCallback(@NonNull FlashlightListener l) {
         synchronized (mListeners) {
             if (mCameraId == null) {
                 tryInitCamera();
@@ -135,7 +138,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..1baed09 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
@@ -30,6 +30,8 @@
 import android.os.UserManager;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.util.ConcurrentUtils;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -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/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
index adfc14e..a995ade 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
@@ -33,6 +33,7 @@
 import android.os.UserManager;
 import android.provider.Settings;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
 import com.android.systemui.BootCompleteCache;
@@ -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..7a0f690 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,17 @@
 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.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;
 
@@ -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..604e76e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
@@ -23,6 +23,8 @@
 import android.content.IntentFilter;
 import android.os.UserHandle;
 
+import androidx.annotation.NonNull;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -59,12 +61,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/RotationLockControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
index 1f368e1..58bb64b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
@@ -19,6 +19,8 @@
 import android.content.Context;
 import android.os.UserHandle;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.view.RotationPolicy;
 
 import java.util.concurrent.CopyOnWriteArrayList;
@@ -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..d1a7a7b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -42,6 +42,8 @@
 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;
@@ -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..cbafedc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensorPrivacyControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensorPrivacyControllerImpl.java
@@ -19,6 +19,8 @@
 import android.content.Context;
 import android.hardware.SensorPrivacyManager;
 
+import androidx.annotation.NonNull;
+
 import java.util.ArrayList;
 import java.util.List;
 
@@ -60,7 +62,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 +72,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/UserInfoControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
index 0ca6ff6..6b2820d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
@@ -34,6 +34,8 @@
 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;
@@ -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/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index 4376a01..75c1e33 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -37,6 +37,8 @@
 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;
@@ -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/micdisclosure/AudioActivityObserver.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioActivityObserver.java
index 87b3956..bbab625 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioActivityObserver.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioActivityObserver.java
@@ -40,5 +40,9 @@
         mListener = listener;
     }
 
+    abstract void start();
+
+    abstract void stop();
+
     abstract Set<String> getActivePackages();
 }
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 8e4e123..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,20 +16,18 @@
 
 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;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
-import android.animation.PropertyValuesHolder;
 import android.annotation.IntDef;
 import android.annotation.UiThread;
 import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
 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,35 +63,30 @@
     // CtsSystemUiHostTestCases:TvMicrophoneCaptureIndicatorTest
     private static final String LAYOUT_PARAMS_TITLE = "MicrophoneCaptureIndicator";
 
-    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 = {
+            STATE_STOPPED,
             STATE_NOT_SHOWN,
             STATE_APPEARING,
             STATE_SHOWN,
-            STATE_MINIMIZING,
-            STATE_MINIMIZED,
-            STATE_MAXIMIZING,
             STATE_DISAPPEARING
     })
     public @interface State {}
 
+    private static final int STATE_STOPPED = -1;
     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 static final int PULSE_BIT_DURATION = 1000;
-    private static final float PULSE_SCALE = 1.25f;
 
     private final Context mContext;
+    private boolean mIsEnabled;
 
     private View mIndicatorView;
     private View mIconTextsContainer;
@@ -104,58 +97,82 @@
     private TextView mTextView;
     private boolean mIsLtr;
 
-    @State private int mState = STATE_NOT_SHOWN;
+    @State private int mState = STATE_STOPPED;
 
     /**
      * Array of the observers that monitor different aspects of the system, such as AppOps and
      * microphone foreground services
      */
-    private final 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<>();
+    private AudioActivityObserver[] mAudioActivityObservers;
     /**
      * 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;
 
-        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();
 
-        mAudioActivityObservers = new AudioActivityObserver[]{
-                new RecordAudioAppOpObserver(mContext, this),
-                new MicrophoneForegroundServicesObserver(mContext, this),
-        };
+        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
+    private void start() {
+        if (mState != STATE_STOPPED) {
+            return;
+        }
+        mState = STATE_NOT_SHOWN;
+
+        if (mAudioActivityObservers == null) {
+            mAudioActivityObservers = new AudioActivityObserver[]{
+                    new RecordAudioAppOpObserver(mContext, this),
+                    new MicrophoneForegroundServicesObserver(mContext, this),
+            };
+        }
+
+        for (int i = mAudioActivityObservers.length - 1; i >= 0; i--) {
+            mAudioActivityObservers[i].start();
+        }
+    }
+
+    @UiThread
+    private void stop() {
+        if (mState == STATE_STOPPED) {
+            return;
+        }
+        mState = STATE_STOPPED;
+
+        for (int i = mAudioActivityObservers.length - 1; i >= 0; i--) {
+            mAudioActivityObservers[i].stop();
+        }
+
+        // Remove the view if shown.
+        if (mState != STATE_NOT_SHOWN) {
+            removeIndicatorView();
+        }
     }
 
     @UiThread
@@ -173,70 +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 (DEBUG) Log.d(TAG, "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;
-                if (DEBUG) Log.d(TAG, "   - there are still ongoing activities");
-                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 for " + packageName);
-        }
+    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;
@@ -252,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
@@ -286,6 +246,10 @@
                         new ViewTreeObserver.OnGlobalLayoutListener() {
                             @Override
                             public void onGlobalLayout() {
+                                if (mState == STATE_STOPPED) {
+                                    return;
+                                }
+
                                 // Remove the observer
                                 mIndicatorView.getViewTreeObserver().removeOnGlobalLayoutListener(
                                         this);
@@ -306,18 +270,15 @@
                                             @Override
                                             public void onAnimationStart(Animator animation,
                                                     boolean isReverse) {
+                                                if (mState == STATE_STOPPED) return;
+
                                                 // Indicator is INVISIBLE at the moment, change it.
                                                 mIndicatorView.setVisibility(View.VISIBLE);
                                             }
 
                                             @Override
                                             public void onAnimationEnd(Animator animation) {
-                                                startPulsatingAnimation();
-                                                if (mRevealRecordingPackages) {
-                                                    onExpanded();
-                                                } else {
-                                                    onMinimized();
-                                                }
+                                                onAppeared();
                                             }
                                         });
                                 set.start();
@@ -341,62 +302,9 @@
     }
 
     @UiThread
-    private void expand(String packageName) {
-        assertRevealingRecordingPackages();
-
-        final String label = getApplicationLabel(packageName);
-        if (DEBUG) {
-            Log.d(TAG, "Expanding for " + packageName + " (" + label + ")...");
-        }
-        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();
-
-        if (DEBUG) Log.d(TAG, "Minimizing...");
-        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, "Hiding...");
+        if (DEBUG) Log.d(TAG, "Hide indicator");
+
         final int targetOffset = (mIsLtr ? 1 : -1) * (mIndicatorView.getWidth()
                 - (int) mIconTextsContainer.getTranslationX());
         final AnimatorSet set = new AnimatorSet();
@@ -416,35 +324,40 @@
         mState = STATE_DISAPPEARING;
     }
 
-    @UiThread
-    private void onExpanded() {
-        assertRevealingRecordingPackages();
 
-        if (DEBUG) Log.d(TAG, "Expanded");
+    @UiThread
+    private void onAppeared() {
+        if (mState == STATE_STOPPED) return;
+
         mState = STATE_SHOWN;
 
-        mIndicatorView.postDelayed(this::minimize, MAXIMIZED_DURATION);
-    }
-
-    @UiThread
-    private void onMinimized() {
-        if (DEBUG) Log.d(TAG, "Minimized");
-        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 (DEBUG) Log.d(TAG, "Hidden");
+        if (mState == STATE_STOPPED) return;
 
+        removeIndicatorView();
+        mState = STATE_NOT_SHOWN;
+
+        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);
         windowManager.removeView(mIndicatorView);
@@ -456,52 +369,28 @@
         mTextsContainers = null;
         mTextView = null;
         mBgEnd = null;
-
-        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());
-        }
     }
 
-    @UiThread
-    private void startPulsatingAnimation() {
-        final View pulsatingView = mIconTextsContainer.findViewById(R.id.pulsating_circle);
-        final ObjectAnimator animator =
-                ObjectAnimator.ofPropertyValuesHolder(
-                        pulsatingView,
-                        PropertyValuesHolder.ofFloat(View.SCALE_X, PULSE_SCALE),
-                        PropertyValuesHolder.ofFloat(View.SCALE_Y, PULSE_SCALE));
-        animator.setDuration(PULSE_BIT_DURATION);
-        animator.setRepeatCount(ObjectAnimator.INFINITE);
-        animator.setRepeatMode(ObjectAnimator.REVERSE);
-        animator.start();
+    private static List<String> splitByComma(String string) {
+        return TextUtils.isEmpty(string) ? Collections.emptyList() : Arrays.asList(
+                string.split(","));
     }
 
-    private String[] getGlobalStringArray(String setting) {
-        String result = Settings.Global.getString(mContext.getContentResolver(), setting);
-        return TextUtils.isEmpty(result) ? new String[0] : result.split(",");
-    }
+    private final DeviceConfig.OnPropertiesChangedListener mConfigChangeListener =
+            new DeviceConfig.OnPropertiesChangedListener() {
+                @Override
+                public void onPropertiesChanged(DeviceConfig.Properties properties) {
+                    reloadExemptPackages();
 
-    private String getApplicationLabel(String packageName) {
-        assertRevealingRecordingPackages();
-
-        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);
-        }
-    }
+                    // Check if was enabled/disabled
+                    if (mIsEnabled != properties.getBoolean(ENABLED_FLAG, true)) {
+                        mIsEnabled = !mIsEnabled;
+                        if (mIsEnabled) {
+                            start();
+                        } else {
+                            stop();
+                        }
+                    }
+                }
+            };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/MicrophoneForegroundServicesObserver.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/MicrophoneForegroundServicesObserver.java
index 1ede88a..8caf95f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/MicrophoneForegroundServicesObserver.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/MicrophoneForegroundServicesObserver.java
@@ -30,7 +30,6 @@
 import android.util.Log;
 import android.util.SparseArray;
 
-import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -41,9 +40,8 @@
  */
 class MicrophoneForegroundServicesObserver extends AudioActivityObserver {
     private static final String TAG = "MicrophoneForegroundServicesObserver";
-    private static final boolean ENABLED = true;
 
-    private final IActivityManager mActivityManager;
+    private IActivityManager mActivityManager;
     /**
      * A dictionary that maps PIDs to the package names. We only keep track of the PIDs that are
      * "active" (those that are running FGS with FOREGROUND_SERVICE_TYPE_MICROPHONE flag).
@@ -60,7 +58,10 @@
     MicrophoneForegroundServicesObserver(Context context,
             OnAudioActivityStateChangeListener listener) {
         super(context, listener);
+    }
 
+    @Override
+    void start() {
         mActivityManager = ActivityManager.getService();
         try {
             mActivityManager.registerProcessObserver(mProcessObserver);
@@ -70,8 +71,19 @@
     }
 
     @Override
+    void stop() {
+        try {
+            mActivityManager.unregisterProcessObserver(mProcessObserver);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Couldn't unregister process observer", e);
+        }
+        mActivityManager = null;
+        mPackageToProcessCount.clear();
+    }
+
+    @Override
     Set<String> getActivePackages() {
-        return ENABLED ? mPackageToProcessCount.keySet() : Collections.emptySet();
+        return mPackageToProcessCount.keySet();
     }
 
     @UiThread
@@ -141,13 +153,12 @@
 
     @UiThread
     private void notifyPackageStateChanged(String packageName, boolean active) {
-        if (active) {
-            if (DEBUG) Log.d(TAG, "New microphone fgs detected, package=" + packageName);
-        } else {
-            if (DEBUG) Log.d(TAG, "Microphone fgs is gone, package=" + packageName);
+        if (DEBUG) {
+            Log.d(TAG, (active ? "New microphone fgs detected" : "Microphone fgs is gone")
+                    + ", package=" + packageName);
         }
 
-        if (ENABLED) mListener.onAudioActivityStateChange(active, packageName);
+        mListener.onAudioActivityStateChange(active, packageName);
     }
 
     @UiThread
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/RecordAudioAppOpObserver.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/RecordAudioAppOpObserver.java
index b5b1c2b..9a2b4a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/RecordAudioAppOpObserver.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/RecordAudioAppOpObserver.java
@@ -42,14 +42,33 @@
 
     RecordAudioAppOpObserver(Context context, OnAudioActivityStateChangeListener listener) {
         super(context, listener);
+    }
+
+    @Override
+    void start() {
+        if (DEBUG) {
+            Log.d(TAG, "Start");
+        }
 
         // Register AppOpsManager callback
-        final AppOpsManager appOpsManager = (AppOpsManager) mContext.getSystemService(
-                Context.APP_OPS_SERVICE);
-        appOpsManager.startWatchingActive(
-                new String[]{AppOpsManager.OPSTR_RECORD_AUDIO},
-                mContext.getMainExecutor(),
-                this);
+        mContext.getSystemService(AppOpsManager.class)
+                .startWatchingActive(
+                        new String[]{AppOpsManager.OPSTR_RECORD_AUDIO},
+                        mContext.getMainExecutor(),
+                        this);
+    }
+
+    @Override
+    void stop() {
+        if (DEBUG) {
+            Log.d(TAG, "Stop");
+        }
+
+        // Unregister AppOpsManager callback
+        mContext.getSystemService(AppOpsManager.class).stopWatchingActive(this);
+
+        // Clean up state
+        mActiveAudioRecordingPackages.clear();
     }
 
     @UiThread
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/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..12dced7 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,9 @@
 
 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.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;
@@ -54,6 +53,7 @@
 @Singleton
 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/TvSystemUIRootComponent.java b/packages/SystemUI/src/com/android/systemui/tv/TvGlobalRootComponent.java
similarity index 82%
rename from packages/SystemUI/src/com/android/systemui/tv/TvSystemUIRootComponent.java
rename to packages/SystemUI/src/com/android/systemui/tv/TvGlobalRootComponent.java
index dce38c1..fd6ba1a 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIRootComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvGlobalRootComponent.java
@@ -19,11 +19,10 @@
 import com.android.systemui.dagger.DefaultComponentBinder;
 import com.android.systemui.dagger.DependencyBinder;
 import com.android.systemui.dagger.DependencyProvider;
+import com.android.systemui.dagger.GlobalRootComponent;
 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;
@@ -42,14 +41,17 @@
         SystemServicesModule.class,
         SystemUIBinder.class,
         SystemUIModule.class,
-        SystemUIDefaultModule.class,
+        TvSystemUIModule.class,
         TvSystemUIBinder.class})
-public interface TvSystemUIRootComponent extends SystemUIRootComponent {
+public interface TvGlobalRootComponent extends GlobalRootComponent {
     /**
      * Component Builder interface. This allows to bind Context instance in the component
      */
     @Component.Builder
-    interface Builder extends SystemUIRootComponent.Builder {
-        TvSystemUIRootComponent build();
+    interface Builder extends GlobalRootComponent.Builder {
+        TvGlobalRootComponent build();
     }
+
+    @Override
+    TvSysUIComponent.Builder getSysUIComponent();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
new file mode 100644
index 0000000..bc0cb51
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
@@ -0,0 +1,38 @@
+/*
+ * 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.SysUIComponent;
+import com.android.systemui.dagger.SysUISingleton;
+
+import dagger.Subcomponent;
+
+/**
+ * Dagger Subcomponent for Core SysUI.
+ */
+@SysUISingleton
+@Subcomponent(modules = {})
+public interface TvSysUIComponent extends SysUIComponent {
+
+    /**
+     * Builder for a SysUIComponent.
+     */
+    @Subcomponent.Builder
+    interface Builder extends SysUIComponent.Builder {
+        TvSysUIComponent build();
+    }
+}
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..228b2ea
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
@@ -0,0 +1,172 @@
+/*
+ * 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.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.WindowManagerShellModule;
+
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+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,
+            WindowManagerShellModule.class
+        },
+        subcomponents = {
+            TvSysUIComponent.class
+        })
+public abstract class TvSystemUIModule {
+
+    @Singleton
+    @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
+    @Singleton
+    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
+    @Singleton
+    abstract QSFactory bindQSFactory(QSFactoryImpl qsFactoryImpl);
+
+    @Binds
+    abstract DockManager bindDockManager(DockManagerImpl dockManager);
+
+    @Binds
+    abstract NotificationEntryManager.KeyguardEnvironment bindKeyguardEnvironment(
+            KeyguardEnvironmentImpl keyguardEnvironment);
+
+    @Binds
+    abstract ShadeController provideShadeController(ShadeControllerImpl shadeController);
+
+    @Singleton
+    @Provides
+    @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME)
+    static boolean provideAllowNotificationLongPress() {
+        return true;
+    }
+
+    @Singleton
+    @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
+    @Singleton
+    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/InjectionInflationController.java b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
index 551b7b4..c82b39a 100644
--- a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
+++ b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
@@ -25,13 +25,11 @@
 
 import com.android.keyguard.KeyguardMessageArea;
 import com.android.keyguard.KeyguardSliceView;
-import com.android.systemui.dagger.SystemUIRootComponent;
 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;
@@ -42,8 +40,7 @@
 import javax.inject.Named;
 import javax.inject.Singleton;
 
-import dagger.Module;
-import dagger.Provides;
+import dagger.BindsInstance;
 import dagger.Subcomponent;
 
 /**
@@ -54,24 +51,16 @@
 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.
@@ -92,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.
          */
@@ -126,11 +109,6 @@
         NotificationStackScrollLayout createNotificationStackScrollLayout();
 
         /**
-         * Creates the Shelf.
-         */
-        NotificationShelf creatNotificationShelf();
-
-        /**
          * Creates the KeyguardSliceView.
          */
         KeyguardSliceView createKeyguardSliceView();
@@ -156,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 {
 
@@ -193,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/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/media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl b/packages/SystemUI/src/com/android/systemui/util/kotlin/nullability.kt
similarity index 66%
copy from media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl
copy to packages/SystemUI/src/com/android/systemui/util/kotlin/nullability.kt
index b1f99e6..92c73a4 100644
--- a/media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/nullability.kt
@@ -14,17 +14,9 @@
  * limitations under the License.
  */
 
-package android.media;
-
-import android.media.AudioDeviceAttributes;
+package com.android.systemui.util.kotlin
 
 /**
- * AIDL for AudioService to signal audio strategy-preferred device updates.
- *
- * {@hide}
+ * If [value] is not null, then returns block(value). Otherwise returns null.
  */
-oneway interface IStrategyPreferredDeviceDispatcher {
-
-    void dispatchPrefDeviceChanged(int strategyId, in AudioDeviceAttributes device);
-
-}
+inline fun <T : Any, R> transform(value: T?, block: (T) -> R): R? = value?.let(block)
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..428751b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
@@ -27,6 +27,8 @@
 
 import com.android.settingslib.applications.InterestingConfigChanges;
 import com.android.systemui.Dependency;
+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,6 +40,8 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
@@ -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/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 3455ff4..51ad30e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -150,6 +150,8 @@
     private boolean mShowing;
     private boolean mShowA11yStream;
 
+    private final boolean mShowLowMediaVolumeIcon;
+
     private int mActiveStream;
     private int mPrevActiveStream;
     private boolean mAutomute = VolumePrefs.DEFAULT_ENABLE_AUTOMUTE;
@@ -166,7 +168,7 @@
 
     public VolumeDialogImpl(Context context) {
         mContext =
-                new ContextThemeWrapper(context, R.style.qs_theme);
+                new ContextThemeWrapper(context, R.style.volume_dialog_theme);
         mController = Dependency.get(VolumeDialogController.class);
         mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
         mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
@@ -175,6 +177,8 @@
         mShowActiveStreamOnly = showActiveStreamOnly();
         mHasSeenODICaptionsTooltip =
                 Prefs.getBoolean(context, Prefs.Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP, false);
+        mShowLowMediaVolumeIcon =
+            mContext.getResources().getBoolean(R.bool.config_showLowMediaVolumeIcon);
     }
 
     @Override
@@ -421,6 +425,7 @@
         row.dndIcon = row.view.findViewById(R.id.dnd_icon);
         row.slider = row.view.findViewById(R.id.volume_row_slider);
         row.slider.setOnSeekBarChangeListener(new VolumeSeekBarChangeListener(row));
+        row.number = row.view.findViewById(R.id.volume_number);
 
         row.anim = null;
 
@@ -935,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));
@@ -1024,19 +1030,28 @@
         final boolean iconEnabled = (mAutomute || ss.muteSupported) && !zenMuted;
         row.icon.setEnabled(iconEnabled);
         row.icon.setAlpha(iconEnabled ? 1 : 0.5f);
-        final int iconRes =
-                isRingVibrate ? R.drawable.ic_volume_ringer_vibrate
-                        : isRingSilent || zenMuted ? row.iconMuteRes
-                                : ss.routedToBluetooth
-                                        ? isStreamMuted(ss) ? R.drawable.ic_volume_media_bt_mute
-                                                : R.drawable.ic_volume_media_bt
-                                        : isStreamMuted(ss) ? row.iconMuteRes : row.iconRes;
+        final int iconRes;
+        if (isRingVibrate) {
+            iconRes = R.drawable.ic_volume_ringer_vibrate;
+        } else if (isRingSilent || zenMuted) {
+            iconRes = row.iconMuteRes;
+        } else if (ss.routedToBluetooth) {
+            iconRes = isStreamMuted(ss) ? R.drawable.ic_volume_media_bt_mute
+                                        : R.drawable.ic_volume_media_bt;
+        } else if (isStreamMuted(ss)) {
+            iconRes = row.iconMuteRes;
+        } else {
+            iconRes = mShowLowMediaVolumeIcon && ss.level * 2 < (ss.levelMax + ss.levelMin)
+                      ? R.drawable.ic_volume_media_low : row.iconRes;
+        }
+
         row.icon.setImageResource(iconRes);
         row.iconState =
                 iconRes == R.drawable.ic_volume_ringer_vibrate ? Events.ICON_STATE_VIBRATE
                 : (iconRes == R.drawable.ic_volume_media_bt_mute || iconRes == row.iconMuteRes)
                         ? Events.ICON_STATE_MUTE
-                : (iconRes == R.drawable.ic_volume_media_bt || iconRes == row.iconRes)
+                : (iconRes == R.drawable.ic_volume_media_bt || iconRes == row.iconRes
+                        || iconRes == R.drawable.ic_volume_media_low)
                         ? Events.ICON_STATE_UNMUTE
                 : Events.ICON_STATE_UNKNOWN;
         if (iconEnabled) {
@@ -1090,6 +1105,7 @@
         final int vlevel = row.ss.muted && (!isRingStream && !zenMuted) ? 0
                 : row.ss.level;
         updateVolumeRowSliderH(row, enableSlider, vlevel);
+        if (row.number != null) row.number.setText(Integer.toString(vlevel));
     }
 
     private boolean isStreamMuted(final StreamState streamState) {
@@ -1115,6 +1131,10 @@
         row.icon.setImageTintList(tint);
         row.icon.setImageAlpha(alpha);
         row.cachedTint = tint;
+        if (row.number != null) {
+            row.number.setTextColor(tint);
+            row.number.setAlpha(alpha);
+        }
     }
 
     private void updateVolumeRowSliderH(VolumeRow row, boolean enable, int vlevel) {
@@ -1346,7 +1366,7 @@
 
     private final class CustomDialog extends Dialog implements DialogInterface {
         public CustomDialog(Context context) {
-            super(context, R.style.qs_theme);
+            super(context, R.style.volume_dialog_theme);
         }
 
         @Override
@@ -1458,6 +1478,7 @@
         private TextView header;
         private ImageButton icon;
         private SeekBar slider;
+        private TextView number;
         private int stream;
         private StreamState ss;
         private long userAttempt;  // last user-driven slider change
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index 4c0762e..0d52e4f 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()
+                        .getRootComponent()
+                        .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..aa4db32 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()
+                        .getRootComponent()
+                        .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..560d581 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()
+                        .getRootComponent()
+                        .createViewInstanceCreatorFactory());
         LayoutInflater layoutInflater = inflationController
                 .injectable(LayoutInflater.from(getContext()));
         mKeyguardStatusView =
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/accessibility/IWindowMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
index fbc8e9d..ac567e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
@@ -25,6 +25,7 @@
 import android.os.RemoteException;
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.view.Display;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.IWindowMagnificationConnection;
@@ -47,6 +48,7 @@
  */
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class IWindowMagnificationConnectionTest extends SysuiTestCase {
 
     private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY;
@@ -57,7 +59,7 @@
     @Mock
     private IWindowMagnificationConnectionCallback mConnectionCallback;
     @Mock
-    private WindowMagnificationController mWindowMagnificationController;
+    private WindowMagnificationAnimationController mWindowMagnificationAnimationController;
     @Mock
     private ModeSwitchesController mModeSwitchesController;
     private IWindowMagnificationConnection mIWindowMagnificationConnection;
@@ -74,7 +76,8 @@
                 any(IWindowMagnificationConnection.class));
         mWindowMagnification = new WindowMagnification(getContext(),
                 getContext().getMainThreadHandler(), mCommandQueue, mModeSwitchesController);
-        mWindowMagnification.mWindowMagnificationController = mWindowMagnificationController;
+        mWindowMagnification.mWindowMagnificationAnimationController =
+                mWindowMagnificationAnimationController;
         mWindowMagnification.requestWindowMagnificationConnection(true);
         assertNotNull(mIWindowMagnificationConnection);
         mIWindowMagnificationConnection.setConnectionCallback(mConnectionCallback);
@@ -86,7 +89,7 @@
                 Float.NaN);
         waitForIdleSync();
 
-        verify(mWindowMagnificationController).enableWindowMagnification(3.0f, Float.NaN,
+        verify(mWindowMagnificationAnimationController).enableWindowMagnification(3.0f, Float.NaN,
                 Float.NaN);
     }
 
@@ -99,7 +102,7 @@
         mIWindowMagnificationConnection.disableWindowMagnification(TEST_DISPLAY);
         waitForIdleSync();
 
-        verify(mWindowMagnificationController).deleteWindowMagnification();
+        verify(mWindowMagnificationAnimationController).deleteWindowMagnification();
     }
 
     @Test
@@ -107,7 +110,7 @@
         mIWindowMagnificationConnection.setScale(TEST_DISPLAY, 3.0f);
         waitForIdleSync();
 
-        verify(mWindowMagnificationController).setScale(3.0f);
+        verify(mWindowMagnificationAnimationController).setScale(3.0f);
     }
 
     @Test
@@ -115,7 +118,7 @@
         mIWindowMagnificationConnection.moveWindowMagnifier(TEST_DISPLAY, 100f, 200f);
         waitForIdleSync();
 
-        verify(mWindowMagnificationController).moveWindowMagnifier(100f, 200f);
+        verify(mWindowMagnificationAnimationController).moveWindowMagnifier(100f, 200f);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
new file mode 100644
index 0000000..b7c198e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
@@ -0,0 +1,363 @@
+/*
+ * 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.accessibility;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.animation.ValueAnimator;
+import android.app.Instrumentation;
+import android.content.Context;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.testing.AndroidTestingRunner;
+import android.view.SurfaceControl;
+import android.view.animation.AccelerateInterpolator;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+
+import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+
+@MediumTest
+@RunWith(AndroidTestingRunner.class)
+public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
+
+    private static final float DEFAULT_SCALE = 3.0f;
+    private static final float DEFAULT_CENTER_X = 400.0f;
+    private static final float DEFAULT_CENTER_Y = 500.0f;
+    // The duration couldn't too short, otherwise the ValueAnimator won't work in expectation.
+    private static final long ANIMATION_DURATION_MS = 200;
+
+    private AtomicReference<Float> mCurrentScale = new AtomicReference<>((float) 0);
+    private AtomicReference<Float> mCurrentCenterX = new AtomicReference<>((float) 0);
+    private AtomicReference<Float> mCurrentCenterY = new AtomicReference<>((float) 0);
+    private ArgumentCaptor<Float> mScaleCaptor = ArgumentCaptor.forClass(Float.class);
+    private ArgumentCaptor<Float> mCenterXCaptor = ArgumentCaptor.forClass(Float.class);
+    private ArgumentCaptor<Float> mCenterYCaptor = ArgumentCaptor.forClass(Float.class);
+
+    @Mock
+    Handler mHandler;
+    @Mock
+    SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
+    @Mock
+    WindowMagnifierCallback mWindowMagnifierCallback;
+
+    private SpyWindowMagnificationController mController;
+    private WindowMagnificationController mSpyController;
+    private WindowMagnificationAnimationController mWindowMagnificationAnimationController;
+    private Instrumentation mInstrumentation;
+    private long mWaitingAnimationPeriod;
+    private long mWaitIntermediateAnimationPeriod;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mWaitingAnimationPeriod = ANIMATION_DURATION_MS + 50;
+        mWaitIntermediateAnimationPeriod = ANIMATION_DURATION_MS / 2;
+        mController = new SpyWindowMagnificationController(mContext, mHandler,
+                mSfVsyncFrameProvider, null, new SurfaceControl.Transaction(),
+                mWindowMagnifierCallback);
+        mSpyController = mController.getSpyController();
+        mWindowMagnificationAnimationController = new WindowMagnificationAnimationController(
+                mContext, mController, newValueAnimator());
+    }
+
+    @Test
+    public void enableWindowMagnification_disabled_expectedStartAndEndValues() {
+        enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod);
+
+        verify(mSpyController, atLeast(2)).enableWindowMagnification(
+                mScaleCaptor.capture(),
+                mCenterXCaptor.capture(), mCenterYCaptor.capture());
+        verifyStartValue(mScaleCaptor, 1.0f);
+        verifyStartValue(mCenterXCaptor, DEFAULT_CENTER_X);
+        verifyStartValue(mCenterYCaptor, DEFAULT_CENTER_Y);
+        verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X, DEFAULT_CENTER_Y);
+    }
+
+    @Test
+    public void enableWindowMagnification_enabling_expectedStartAndEndValues() {
+        enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod);
+        final float targetScale = DEFAULT_SCALE + 1.0f;
+        final float targetCenterX = DEFAULT_CENTER_X + 100;
+        final float targetCenterY = DEFAULT_CENTER_Y + 100;
+
+        mInstrumentation.runOnMainSync(() -> {
+            Mockito.reset(mSpyController);
+            mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
+                    targetCenterX, targetCenterY);
+            mCurrentScale.set(mController.getScale());
+            mCurrentCenterX.set(mController.getCenterX());
+            mCurrentCenterY.set(mController.getCenterY());
+        });
+
+        SystemClock.sleep(mWaitingAnimationPeriod);
+
+        verify(mSpyController, atLeast(2)).enableWindowMagnification(mScaleCaptor.capture(),
+                mCenterXCaptor.capture(), mCenterYCaptor.capture());
+        verifyStartValue(mScaleCaptor, mCurrentScale.get());
+        verifyStartValue(mCenterXCaptor, mCurrentCenterX.get());
+        verifyStartValue(mCenterYCaptor, mCurrentCenterY.get());
+        verifyFinalSpec(targetScale, targetCenterX, targetCenterY);
+    }
+
+    @Test
+    public void enableWindowMagnification_disabling_expectedStartAndEndValues() {
+        enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod);
+        deleteWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod);
+        final float targetScale = DEFAULT_SCALE + 1.0f;
+        final float targetCenterX = DEFAULT_CENTER_X + 100;
+        final float targetCenterY = DEFAULT_CENTER_Y + 100;
+
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    Mockito.reset(mSpyController);
+                    mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
+                            targetCenterX, targetCenterY);
+                    mCurrentScale.set(mController.getScale());
+                    mCurrentCenterX.set(mController.getCenterX());
+                    mCurrentCenterY.set(mController.getCenterY());
+                });
+        SystemClock.sleep(mWaitingAnimationPeriod);
+
+        verify(mSpyController, atLeast(2)).enableWindowMagnification(
+                mScaleCaptor.capture(),
+                mCenterXCaptor.capture(), mCenterYCaptor.capture());
+        //Animating in reverse, so we only check if the start values are greater than current.
+        assertTrue(mScaleCaptor.getAllValues().get(0) > mCurrentScale.get());
+        assertEquals(targetScale, mScaleCaptor.getValue(), 0f);
+        assertTrue(mCenterXCaptor.getAllValues().get(0) > mCurrentCenterX.get());
+        assertEquals(targetCenterX, mCenterXCaptor.getValue(), 0f);
+        assertTrue(mCenterYCaptor.getAllValues().get(0) > mCurrentCenterY.get());
+        assertEquals(targetCenterY, mCenterYCaptor.getValue(), 0f);
+        verifyFinalSpec(targetScale, targetCenterX, targetCenterY);
+    }
+
+    @Test
+    public void enableWindowMagnificationWithSameScale_doNothing() {
+        enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod);
+
+        enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod);
+
+        verify(mSpyController, never()).enableWindowMagnification(anyFloat(), anyFloat(),
+                anyFloat());
+    }
+
+    @Test
+    public void setScale_enabled_expectedScale() {
+        enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod);
+
+        mInstrumentation.runOnMainSync(
+                () -> mWindowMagnificationAnimationController.setScale(DEFAULT_SCALE + 1));
+
+        verify(mSpyController).setScale(DEFAULT_SCALE + 1);
+        verifyFinalSpec(DEFAULT_SCALE + 1, DEFAULT_CENTER_X, DEFAULT_CENTER_Y);
+    }
+
+    @Test
+    public void deleteWindowMagnification_enabled_expectedStartAndEndValues() {
+        enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod);
+
+        deleteWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod);
+
+        verify(mSpyController, atLeast(2)).enableWindowMagnification(mScaleCaptor.capture(),
+                mCenterXCaptor.capture(), mCenterYCaptor.capture());
+        verify(mSpyController).deleteWindowMagnification();
+        verifyStartValue(mScaleCaptor, DEFAULT_SCALE);
+        verifyStartValue(mCenterXCaptor, Float.NaN);
+        verifyStartValue(mCenterYCaptor, Float.NaN);
+        verifyFinalSpec(Float.NaN, Float.NaN, Float.NaN);
+    }
+
+    @Test
+    public void deleteWindowMagnification_disabled_doNothing() {
+        deleteWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod);
+
+        Mockito.verifyNoMoreInteractions(mSpyController);
+    }
+
+    @Test
+    public void deleteWindowMagnification_enabling_checkStartAndEndValues() {
+        enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod);
+
+        //It just reverse the animation, so we don't need to wait the whole duration.
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    Mockito.reset(mSpyController);
+                    mWindowMagnificationAnimationController.deleteWindowMagnification();
+                    mCurrentScale.set(mController.getScale());
+                    mCurrentCenterX.set(mController.getCenterX());
+                    mCurrentCenterY.set(mController.getCenterY());
+                });
+        SystemClock.sleep(mWaitingAnimationPeriod);
+
+        verify(mSpyController, atLeast(2)).enableWindowMagnification(mScaleCaptor.capture(),
+                mCenterXCaptor.capture(), mCenterYCaptor.capture());
+        verify(mSpyController).deleteWindowMagnification();
+
+        //The animation is in verse, so we only check the start values should no be greater than
+        // the current one.
+        assertTrue(mScaleCaptor.getAllValues().get(0) <= mCurrentScale.get());
+        assertEquals(1.0f, mScaleCaptor.getValue(), 0f);
+        verifyStartValue(mCenterXCaptor, Float.NaN);
+        verifyStartValue(mCenterYCaptor, Float.NaN);
+        verifyFinalSpec(Float.NaN, Float.NaN, Float.NaN);
+    }
+
+    @Test
+    public void deleteWindowMagnification_disabling_checkStartAndValues() {
+        enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod);
+        deleteWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod);
+
+        deleteWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod);
+
+        verify(mSpyController, atLeast(2)).enableWindowMagnification(mScaleCaptor.capture(),
+                mCenterXCaptor.capture(), mCenterYCaptor.capture());
+        verify(mSpyController).deleteWindowMagnification();
+        assertEquals(1.0f, mScaleCaptor.getValue(), 0f);
+        verifyFinalSpec(Float.NaN, Float.NaN, Float.NaN);
+    }
+
+    @Test
+    public void moveWindowMagnifier_enabled() {
+        enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod);
+
+        mInstrumentation.runOnMainSync(
+                () -> mWindowMagnificationAnimationController.moveWindowMagnifier(100f, 200f));
+
+        verify(mSpyController).moveWindowMagnifier(100f, 200f);
+        verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X + 100f, DEFAULT_CENTER_Y + 100f);
+    }
+
+    @Test
+    public void onConfigurationChanged_passThrough() {
+        mWindowMagnificationAnimationController.onConfigurationChanged(100);
+
+        verify(mSpyController).onConfigurationChanged(100);
+    }
+    private void verifyFinalSpec(float expectedScale, float expectedCenterX,
+            float expectedCenterY) {
+        assertEquals(expectedScale, mController.getScale(), 0f);
+        assertEquals(expectedCenterX, mController.getCenterX(), 0f);
+        assertEquals(expectedCenterY, mController.getCenterY(), 0f);
+    }
+
+    private void enableWindowMagnificationAndWaitAnimating(long duration) {
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    Mockito.reset(mSpyController);
+                    mWindowMagnificationAnimationController.enableWindowMagnification(DEFAULT_SCALE,
+                            DEFAULT_CENTER_X, DEFAULT_CENTER_Y);
+                });
+        SystemClock.sleep(duration);
+    }
+
+    private void deleteWindowMagnificationAndWaitAnimating(long duration) {
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    resetMockObjects();
+                    mWindowMagnificationAnimationController.deleteWindowMagnification();
+                });
+        SystemClock.sleep(duration);
+    }
+
+    private void verifyStartValue(ArgumentCaptor<Float> captor, float startValue) {
+        assertEquals(startValue, captor.getAllValues().get(0), 0f);
+    }
+
+    private void resetMockObjects() {
+        Mockito.reset(mSpyController);
+    }
+
+    /**
+     * It observes the methods in {@link WindowMagnificationController} since we couldn't spy it
+     * directly.
+     */
+    private static class SpyWindowMagnificationController extends WindowMagnificationController {
+        private WindowMagnificationController mSpyController;
+
+        SpyWindowMagnificationController(Context context, Handler handler,
+                SfVsyncFrameCallbackProvider sfVsyncFrameProvider,
+                MirrorWindowControl mirrorWindowControl, SurfaceControl.Transaction transaction,
+                WindowMagnifierCallback callback) {
+            super(context, handler, sfVsyncFrameProvider, mirrorWindowControl, transaction,
+                    callback);
+            mSpyController = Mockito.mock(WindowMagnificationController.class);
+        }
+
+        WindowMagnificationController getSpyController() {
+            return mSpyController;
+        }
+
+        @Override
+        void enableWindowMagnification(float scale, float centerX, float centerY) {
+            super.enableWindowMagnification(scale, centerX, centerY);
+            mSpyController.enableWindowMagnification(scale, centerX, centerY);
+        }
+
+        @Override
+        void deleteWindowMagnification() {
+            super.deleteWindowMagnification();
+            mSpyController.deleteWindowMagnification();
+        }
+
+        @Override
+        void moveWindowMagnifier(float offsetX, float offsetY) {
+            super.moveWindowMagnifier(offsetX, offsetX);
+            mSpyController.moveWindowMagnifier(offsetX, offsetY);
+        }
+
+        @Override
+        void setScale(float scale) {
+            super.setScale(scale);
+            mSpyController.setScale(scale);
+        }
+
+        @Override
+        void onConfigurationChanged(int configDiff) {
+            super.onConfigurationChanged(configDiff);
+            mSpyController.onConfigurationChanged(configDiff);
+        }
+
+    }
+
+    private static ValueAnimator newValueAnimator() {
+        final ValueAnimator valueAnimator = new ValueAnimator();
+        valueAnimator.setDuration(ANIMATION_DURATION_MS);
+        valueAnimator.setInterpolator(new AccelerateInterpolator(2.5f));
+        valueAnimator.setFloatValues(0.0f, 1.0f);
+        return valueAnimator;
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index 2007fbb..f1f394e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -18,6 +18,7 @@
 
 import static android.view.Choreographer.FrameCallback;
 
+import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.atLeastOnce;
@@ -26,8 +27,12 @@
 import static org.mockito.Mockito.when;
 
 import android.app.Instrumentation;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
 import android.os.Handler;
 import android.testing.AndroidTestingRunner;
+import android.view.Display;
+import android.view.Surface;
 import android.view.SurfaceControl;
 
 import androidx.test.InstrumentationRegistry;
@@ -41,6 +46,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
@@ -57,12 +63,14 @@
     WindowMagnifierCallback mWindowMagnifierCallback;
     @Mock
     SurfaceControl.Transaction mTransaction;
+    private Context mContext;
     private WindowMagnificationController mWindowMagnificationController;
     private Instrumentation mInstrumentation;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        mContext  = Mockito.spy(getContext());
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
         doAnswer(invocation -> {
             FrameCallback callback = invocation.getArgument(0);
@@ -73,8 +81,7 @@
         when(mTransaction.remove(any())).thenReturn(mTransaction);
         when(mTransaction.setGeometry(any(), any(), any(),
                 anyInt())).thenReturn(mTransaction);
-
-        mWindowMagnificationController = new WindowMagnificationController(getContext(),
+        mWindowMagnificationController = new WindowMagnificationController(mContext,
                 mHandler, mSfVsyncFrameProvider,
                 mMirrorWindowControl, mTransaction, mWindowMagnifierCallback);
         verify(mMirrorWindowControl).setWindowDelegate(
@@ -83,9 +90,8 @@
 
     @After
     public void tearDown() {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.deleteWindowMagnification();
-        });
+        mInstrumentation.runOnMainSync(
+                () -> mWindowMagnificationController.deleteWindowMagnification());
     }
 
     @Test
@@ -121,4 +127,27 @@
 
         verify(mSfVsyncFrameProvider, atLeastOnce()).postFrameCallback(any());
     }
+
+    @Test
+    public void setScale_enabled_expectedValue() {
+        mInstrumentation.runOnMainSync(
+                () -> mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
+                        Float.NaN));
+
+        mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.setScale(3.0f));
+
+        assertEquals(3.0f, mWindowMagnificationController.getScale(), 0);
+    }
+
+    @Test
+    public void onConfigurationChanged_disabled_withoutException() {
+        Display display = Mockito.spy(mContext.getDisplay());
+        when(display.getRotation()).thenReturn(Surface.ROTATION_90);
+        when(mContext.getDisplay()).thenReturn(display);
+
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_DENSITY);
+            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_ORIENTATION);
+        });
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
index 4136013..936558b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
@@ -26,6 +26,7 @@
 import android.graphics.Rect;
 import android.os.RemoteException;
 import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.view.Display;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.IWindowMagnificationConnection;
@@ -45,6 +46,7 @@
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class WindowMagnificationTest extends SysuiTestCase {
 
     @Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
index 4fdc06e..8f082c1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
@@ -27,6 +27,9 @@
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -34,6 +37,8 @@
 
 import android.app.AppOpsManager;
 import android.content.pm.PackageManager;
+import android.media.AudioManager;
+import android.media.AudioRecordingConfiguration;
 import android.os.Looper;
 import android.os.UserHandle;
 import android.testing.AndroidTestingRunner;
@@ -47,9 +52,11 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.Collections;
 import java.util.List;
 
 @SmallTest
@@ -73,6 +80,12 @@
     private PermissionFlagsCache mFlagsCache;
     @Mock
     private PackageManager mPackageManager;
+    @Mock(stubOnly = true)
+    private AudioManager mAudioManager;
+    @Mock(stubOnly = true)
+    private AudioManager.AudioRecordingCallback mRecordingCallback;
+    @Mock(stubOnly = true)
+    private AudioRecordingConfiguration mPausedMockRecording;
 
     private AppOpsControllerImpl mController;
     private TestableLooper mTestableLooper;
@@ -94,11 +107,20 @@
         when(mFlagsCache.getPermissionFlags(anyString(), anyString(),
                 eq(TEST_UID_NON_USER_SENSITIVE))).thenReturn(0);
 
+        doAnswer((invocation) -> mRecordingCallback = invocation.getArgument(0))
+                .when(mAudioManager).registerAudioRecordingCallback(any(), any());
+        when(mPausedMockRecording.getClientUid()).thenReturn(TEST_UID);
+        when(mPausedMockRecording.isClientSilenced()).thenReturn(true);
+
+        when(mAudioManager.getActiveRecordingConfigurations())
+                .thenReturn(List.of(mPausedMockRecording));
+
         mController = new AppOpsControllerImpl(
                 mContext,
                 mTestableLooper.getLooper(),
                 mDumpManager,
-                mFlagsCache
+                mFlagsCache,
+                mAudioManager
         );
     }
 
@@ -363,6 +385,89 @@
                 AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
     }
 
+    @Test
+    public void testPausedRecordingIsRetrievedOnCreation() {
+        mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback);
+        mTestableLooper.processAllMessages();
+
+        mController.onOpActiveChanged(
+                AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
+        mTestableLooper.processAllMessages();
+
+        verify(mCallback, never())
+                .onActiveStateChanged(anyInt(), anyInt(), anyString(), anyBoolean());
+    }
+
+    @Test
+    public void testPausedRecordingFilteredOut() {
+        mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback);
+        mTestableLooper.processAllMessages();
+
+        mController.onOpActiveChanged(
+                AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
+        mTestableLooper.processAllMessages();
+
+        assertTrue(mController.getActiveAppOps().isEmpty());
+    }
+
+    @Test
+    public void testOnlyRecordAudioPaused() {
+        mController.addCallback(new int[]{
+                AppOpsManager.OP_RECORD_AUDIO,
+                AppOpsManager.OP_CAMERA
+        }, mCallback);
+        mTestableLooper.processAllMessages();
+
+        mController.onOpActiveChanged(
+                AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, true);
+        mTestableLooper.processAllMessages();
+
+        verify(mCallback).onActiveStateChanged(
+                AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, true);
+        List<AppOpItem> list = mController.getActiveAppOps();
+
+        assertEquals(1, list.size());
+        assertEquals(AppOpsManager.OP_CAMERA, list.get(0).getCode());
+    }
+
+    @Test
+    public void testUnpausedRecordingSentActive() {
+        mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback);
+        mTestableLooper.processAllMessages();
+        mController.onOpActiveChanged(
+                AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
+
+        mTestableLooper.processAllMessages();
+        mRecordingCallback.onRecordingConfigChanged(Collections.emptyList());
+
+        mTestableLooper.processAllMessages();
+
+        verify(mCallback).onActiveStateChanged(
+                AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
+    }
+
+    @Test
+    public void testAudioPausedSentInactive() {
+        mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback);
+        mTestableLooper.processAllMessages();
+        mController.onOpActiveChanged(
+                AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
+        mTestableLooper.processAllMessages();
+
+        AudioRecordingConfiguration mockARC = mock(AudioRecordingConfiguration.class);
+        when(mockARC.getClientUid()).thenReturn(TEST_UID_OTHER);
+        when(mockARC.isClientSilenced()).thenReturn(true);
+
+        mRecordingCallback.onRecordingConfigChanged(List.of(mockARC));
+        mTestableLooper.processAllMessages();
+
+        InOrder inOrder = inOrder(mCallback);
+        inOrder.verify(mCallback).onActiveStateChanged(
+                AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
+        inOrder.verify(mCallback).onActiveStateChanged(
+                AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, false);
+    }
+
     private class TestHandler extends AppOpsControllerImpl.H {
         TestHandler(Looper looper) {
             mController.super(looper);
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/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index b758953..a7808ad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -79,10 +79,12 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
+import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent;
 import com.android.systemui.statusbar.phone.DozeParameters;
 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 +155,7 @@
     private ArgumentCaptor<NotificationRemoveInterceptor> mRemoveInterceptorCaptor;
 
     private TestableBubbleController mBubbleController;
-    private NotificationShadeWindowController mNotificationShadeWindowController;
+    private NotificationShadeWindowControllerImpl mNotificationShadeWindowController;
     private NotificationEntryListener mEntryListener;
     private NotificationRemoveInterceptor mRemoveInterceptor;
 
@@ -174,6 +176,8 @@
     @Mock
     private ShadeController mShadeController;
     @Mock
+    private NotificationShelfComponent mNotificationShelfComponent;
+    @Mock
     private NotifPipeline mNotifPipeline;
     @Mock
     private FeatureFlags mFeatureFlagsOldPipeline;
@@ -185,6 +189,7 @@
     private IStatusBarService mStatusBarService;
     @Mock
     private LauncherApps mLauncherApps;
+    @Mock private LockscreenLockIconController mLockIconController;
 
     private BubbleData mBubbleData;
 
@@ -199,7 +204,8 @@
         mContext.addMockSystemService(FaceManager.class, mFaceManager);
         when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
 
-        mNotificationShadeWindowController = new NotificationShadeWindowController(mContext,
+        // Bubbles get added to status bar window view
+        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 43bf191..3df0df6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
@@ -58,6 +58,7 @@
 
 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;
@@ -66,7 +67,9 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+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.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationFilter;
@@ -75,12 +78,12 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
-import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
+import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent;
 import com.android.systemui.statusbar.phone.DozeParameters;
 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;
@@ -88,6 +91,7 @@
 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 org.junit.Before;
 import org.junit.Ignore;
@@ -149,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;
@@ -168,7 +172,7 @@
     @Mock
     private ShadeController mShadeController;
     @Mock
-    private NotificationRowComponent mNotificationRowComponent;
+    private NotificationShelfComponent mNotificationShelfComponent;
     @Mock
     private NotifPipeline mNotifPipeline;
     @Mock
@@ -185,6 +189,7 @@
     private BubbleData mBubbleData;
 
     private TestableLooper mTestableLooper;
+    private SuperStatusBarViewFactory mSuperStatusBarViewFactory;
 
     @Before
     public void setUp() throws Exception {
@@ -195,8 +200,25 @@
         mContext.addMockSystemService(FaceManager.class, mFaceManager);
         when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
 
+        mSuperStatusBarViewFactory = new SuperStatusBarViewFactory(mContext,
+                new InjectionInflationController(SystemUIFactory.getInstance().getRootComponent()
+                        .createViewInstanceCreatorFactory()),
+                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/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..2e794a4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
@@ -83,8 +83,8 @@
         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);
     }
 
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 3789e6e..457d559 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
@@ -13,6 +13,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dump.DumpManager
+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.eq
@@ -58,6 +59,7 @@
     @Mock lateinit var mediaTimeoutListener: MediaTimeoutListener
     @Mock lateinit var mediaResumeListener: MediaResumeListener
     @Mock lateinit var pendingIntent: PendingIntent
+    @Mock lateinit var activityStarter: ActivityStarter
     @JvmField @Rule val mockito = MockitoJUnit.rule()
     lateinit var mediaDataManager: MediaDataManager
     lateinit var mediaNotification: StatusBarNotification
@@ -68,8 +70,8 @@
         backgroundExecutor = FakeExecutor(FakeSystemClock())
         mediaDataManager = MediaDataManager(context, backgroundExecutor, foregroundExecutor,
                 mediaControllerFactory, broadcastDispatcher, dumpManager,
-                mediaTimeoutListener, mediaResumeListener, useMediaResumption = true,
-                useQsMediaPlayer = true)
+                mediaTimeoutListener, mediaResumeListener, activityStarter,
+                useMediaResumption = true, useQsMediaPlayer = true)
         session = MediaSession(context, "MediaDataManagerTestSession")
         mediaNotification = SbnBuilder().run {
             setPkg(PACKAGE_NAME)
@@ -197,6 +199,58 @@
     }
 
     @Test
+    fun testAppBlockedFromResumption() {
+        // 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!!
+        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
+        assertThat(listener.removedKey!!).isEqualTo(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
+        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!!
+        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
+        assertThat(listener.key!!).isEqualTo(PACKAGE_NAME)
+        assertThat(listener.oldKey!!).isEqualTo(KEY)
+    }
+
+    @Test
     fun testAddResumptionControls() {
         val listener = TestListener()
         mediaDataManager.addListener(listener)
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..118cffc
--- /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(LOCAL, !RESUMPTION)
+
+        val playerIsRemote = mock(MediaControlPanel::class.java)
+        whenever(playerIsRemote.isPlaying).thenReturn(false)
+        val dataIsRemote = createMediaData(!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(LOCAL, !RESUMPTION)
+
+        val playerIsPlaying2 = mock(MediaControlPanel::class.java)
+        whenever(playerIsPlaying2.isPlaying).thenReturn(false)
+        val dataIsPlaying2 = createMediaData(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(LOCAL, !RESUMPTION)
+
+        val playerIsPlayingAndRemote = mock(MediaControlPanel::class.java)
+        whenever(playerIsPlayingAndRemote.isPlaying).thenReturn(true)
+        val dataIsPlayingAndRemote = createMediaData(!LOCAL, !RESUMPTION)
+
+        val playerIsStoppedAndLocal = mock(MediaControlPanel::class.java)
+        whenever(playerIsStoppedAndLocal.isPlaying).thenReturn(false)
+        val dataIsStoppedAndLocal = createMediaData(LOCAL, !RESUMPTION)
+
+        val playerIsStoppedAndRemote = mock(MediaControlPanel::class.java)
+        whenever(playerIsStoppedAndLocal.isPlaying).thenReturn(false)
+        val dataIsStoppedAndRemote = createMediaData(!LOCAL, !RESUMPTION)
+
+        val playerCanResume = mock(MediaControlPanel::class.java)
+        whenever(playerCanResume.isPlaying).thenReturn(false)
+        val dataCanResume = createMediaData(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(isLocalSession: Boolean, resumption: Boolean) =
+        MediaData(0, false, 0, null, 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/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/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/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..9922d36 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()
+                                .getRootComponent()
+                                .createViewInstanceCreatorFactory()),
                 mock(QSTileHost.class),
                 mock(StatusBarStateController.class),
                 commandQueue,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index bdb7166..c8e1a74 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -78,7 +78,7 @@
 
 @RunWith(AndroidTestingRunner.class)
 @SmallTest
-@RunWithLooper
+@RunWithLooper(setAsMainLooper = true)
 public class QSTileHostTest extends SysuiTestCase {
 
     private static String MOCK_STATE_STRING = "MockState";
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
index c2579dd..3aa40de 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
@@ -53,7 +53,7 @@
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
 class CustomTileTest : SysuiTestCase() {
 
     companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt
index f70106a..2006a75 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt
@@ -38,7 +38,7 @@
 import org.mockito.MockitoAnnotations
 
 @RunWith(AndroidTestingRunner::class)
-@RunWithLooper
+@RunWithLooper(setAsMainLooper = true)
 @SmallTest
 class BatterySaverTileTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
index 8ece622..5d14898 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
@@ -58,7 +58,7 @@
 
 
 @RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
 @SmallTest
 public class CastTileTest extends SysuiTestCase {
 
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..c35ada7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
@@ -212,19 +212,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/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/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/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..54ccc4d 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
@@ -79,7 +79,7 @@
 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;
 
@@ -117,7 +117,7 @@
     @Mock private VisualStabilityManager mVisualStabilityManager;
     @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;
@@ -156,7 +156,7 @@
                 mChannelEditorDialogController, mContextTracker, mProvider,
                 mAssistantFeedbackController, mBubbleController,
                 new UiEventLoggerFake());
-        mGutsManager.setUpWithPresenter(mPresenter, mStackScroller,
+        mGutsManager.setUpWithPresenter(mPresenter, mNotificationListContainer,
                 mCheckSaveListener, mOnSettingsClickListener);
         mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
     }
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..fa2fa04 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,7 +422,6 @@
                 mock(OnExpandClickListener.class),
                 mock(NotificationMediaManager.class),
                 mock(ExpandableNotificationRow.CoordinateOnClickListener.class),
-                mock(ExpandableNotificationRow.CoordinateOnClickListener.class),
                 mock(FalsingManager.class),
                 mStatusBarStateController,
                 mPeopleNotificationIdentifier);
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 ddac2ec..fec4677 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
@@ -64,6 +64,7 @@
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.NotificationShelfController;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -197,7 +198,10 @@
         mEntryManager.setUpWithPresenter(mock(NotificationPresenter.class));
         when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(false);
 
+        NotificationShelfController notificationShelfController =
+                mock(NotificationShelfController.class);
         NotificationShelf notificationShelf = mock(NotificationShelf.class);
+        when(notificationShelfController.getView()).thenReturn(notificationShelf);
         when(mNotificationSectionsManager.createSectionsForBuckets()).thenReturn(
                 new NotificationSection[]{
                         mNotificationSection
@@ -208,7 +212,7 @@
         // holds a copy of the CUT's instances of these KeyguardBypassController, so they still
         // refer to the CUT's member variables, not the spy's member variables.
         mStackScrollerInternal = new NotificationStackScrollLayout(getContext(), null,
-                true /* allowLongPress */, mNotificationRoundnessManager,
+                mNotificationRoundnessManager,
                 mock(DynamicPrivacyController.class),
                 mock(SysuiStatusBarStateController.class),
                 mHeadsUpManager,
@@ -230,7 +234,7 @@
         verify(mLockscreenUserManager).addUserChangedListener(userChangedCaptor.capture());
         mUserChangedListener = userChangedCaptor.getValue();
         mStackScroller = spy(mStackScrollerInternal);
-        mStackScroller.setShelf(notificationShelf);
+        mStackScroller.setShelfController(notificationShelfController);
         mStackScroller.setStatusBar(mBar);
         mStackScroller.setScrimController(mock(ScrimController.class));
         mStackScroller.setGroupManager(mGroupManager);
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..a5f4e51 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
@@ -40,6 +40,7 @@
 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;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index e546dff..8dea84c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
@@ -38,7 +38,7 @@
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
-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.Assert;
@@ -51,8 +51,8 @@
 @RunWithLooper
 public class HeadsUpAppearanceControllerTest extends SysuiTestCase {
 
-    private final NotificationStackScrollLayout mStackScroller =
-            mock(NotificationStackScrollLayout.class);
+    private final NotificationStackScrollLayoutController mStackScrollerController =
+            mock(NotificationStackScrollLayoutController.class);
     private final NotificationPanelViewController mPanelView =
             mock(NotificationPanelViewController.class);
     private final DarkIconDispatcher mDarkIconDispatcher = mock(DarkIconDispatcher.class);
@@ -93,9 +93,9 @@
                 mWakeUpCoordinator,
                 mKeyguardStateController,
                 mCommandQueue,
-                mHeadsUpStatusBarView,
-                mStackScroller,
+                mStackScrollerController,
                 mPanelView,
+                mHeadsUpStatusBarView,
                 new View(mContext),
                 mOperatorNameView,
                 new View(mContext));
@@ -172,9 +172,9 @@
                 mWakeUpCoordinator,
                 mKeyguardStateController,
                 mCommandQueue,
-                mHeadsUpStatusBarView,
-                mStackScroller,
+                mStackScrollerController,
                 mPanelView,
+                mHeadsUpStatusBarView,
                 new View(mContext),
                 new View(mContext),
                 new View(mContext));
@@ -193,14 +193,14 @@
         reset(mHeadsUpManager);
         reset(mDarkIconDispatcher);
         reset(mPanelView);
-        reset(mStackScroller);
+        reset(mStackScrollerController);
         mHeadsUpAppearanceController.destroy();
         verify(mHeadsUpManager).removeListener(any());
         verify(mDarkIconDispatcher).removeDarkReceiver((DarkIconDispatcher.DarkReceiver) any());
         verify(mPanelView).removeVerticalTranslationListener(any());
         verify(mPanelView).removeTrackingHeadsUpListener(any());
         verify(mPanelView).setHeadsUpAppearanceController(any());
-        verify(mStackScroller).removeOnExpandedHeightChangedListener(any());
-        verify(mStackScroller).removeOnLayoutChangeListener(any());
+        verify(mStackScrollerController).removeOnExpandedHeightChangedListener(any());
+        verify(mStackScrollerController).removeOnLayoutChangeListener(any());
     }
 }
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..48bf2db 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,6 +33,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.AlertingNotificationManager;
 import com.android.systemui.statusbar.AlertingNotificationManagerTest;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
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..18cb667 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
@@ -18,7 +18,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,6 +28,7 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationMediaManager;
@@ -66,6 +66,7 @@
     private NotificationIconAreaController mController;
     @Mock
     private BubbleController mBubbleController;
+    @Mock private DemoModeController mDemoModeController;
 
     @Before
     public void setup() {
@@ -77,7 +78,8 @@
 
         mController = new NotificationIconAreaController(mContext, mStatusBar,
                 mStatusBarStateController, mWakeUpCoordinator, mKeyguardBypassController,
-                mNotificationMediaManager, mListener, mDozeParameters, mBubbleController);
+                mNotificationMediaManager, mListener, mDozeParameters, mBubbleController,
+                mDemoModeController);
     }
 
     @Test
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 c7434f6..a0c0e79 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
@@ -63,7 +63,7 @@
 import com.android.systemui.statusbar.FlingAnimationUtils;
 import com.android.systemui.statusbar.KeyguardAffordanceView;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.NotificationShelfController;
 import com.android.systemui.statusbar.PulseExpansionHandler;
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -74,6 +74,7 @@
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.ZenModeController;
@@ -115,7 +116,7 @@
     @Mock
     private HeadsUpManagerPhone mHeadsUpManager;
     @Mock
-    private NotificationShelf mNotificationShelf;
+    private NotificationShelfController mNotificationShelfController;
     @Mock
     private NotificationGroupManager mGroupManager;
     @Mock
@@ -186,7 +187,8 @@
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     @Mock
     private KeyguardClockSwitchController mKeyguardClockSwitchController;
-    private FlingAnimationUtils.Builder mFlingAnimationUtilsBuilder;
+    @Mock
+    private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
 
     private NotificationPanelViewController mNotificationPanelViewController;
     private View.AccessibilityDelegate mAccessibiltyDelegate;
@@ -205,14 +207,18 @@
         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));
         when(mView.findViewById(R.id.big_clock_container)).thenReturn(mBigClockContainer);
         when(mView.findViewById(R.id.qs_frame)).thenReturn(mQsFrame);
-        mFlingAnimationUtilsBuilder = new FlingAnimationUtils.Builder(mDisplayMetrics);
+        FlingAnimationUtils.Builder flingAnimationUtilsBuilder = new FlingAnimationUtils.Builder(
+                mDisplayMetrics);
 
         doAnswer((Answer<Void>) invocation -> {
             mTouchHandler = invocation.getArgument(0);
@@ -241,19 +247,19 @@
                 mDozeParameters, mCommandQueue, mVibratorHelper,
                 mLatencyTracker, mPowerManager, mAccessibilityManager, 0, mUpdateMonitor,
                 mMetricsLogger, mActivityManager, mZenModeController, mConfigurationController,
-                mFlingAnimationUtilsBuilder, mStatusBarTouchableRegionManager,
+                flingAnimationUtilsBuilder, mStatusBarTouchableRegionManager,
                 mConversationNotificationManager, mMediaHiearchyManager,
                 mBiometricUnlockController, mStatusBarKeyguardViewManager,
-                () -> mKeyguardClockSwitchController);
+                () -> mKeyguardClockSwitchController,
+                mNotificationStackScrollLayoutController);
         mNotificationPanelViewController.initDependencies(mStatusBar, mGroupManager,
-                mNotificationShelf, mNotificationAreaController, mScrimController);
+                mNotificationShelfController, mNotificationAreaController, mScrimController);
         mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);
         mNotificationPanelViewController.setBar(mPanelBar);
 
         ArgumentCaptor<View.AccessibilityDelegate> accessibilityDelegateArgumentCaptor =
                 ArgumentCaptor.forClass(View.AccessibilityDelegate.class);
-        verify(mView)
-                .setAccessibilityDelegate(accessibilityDelegateArgumentCaptor.capture());
+        verify(mView).setAccessibilityDelegate(accessibilityDelegateArgumentCaptor.capture());
         mAccessibiltyDelegate = accessibilityDelegateArgumentCaptor.getValue();
     }
 
@@ -261,8 +267,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..ea57cc9 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()
+                                .getRootComponent()
+                                .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..a71b10c 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
@@ -184,7 +184,6 @@
                         getContext(),
                         mock(CommandQueue.class),
                         mHandler,
-                        mHandler,
                         mUiBgExecutor,
                         mEntryManager,
                         mNotifPipeline,
@@ -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
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..e46aa04 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,6 +43,7 @@
 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;
@@ -59,6 +58,8 @@
 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..8a1d95e 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;
@@ -124,7 +126,9 @@
 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));
@@ -403,6 +414,7 @@
                 mPhoneStatusBarPolicy,
                 mKeyguardIndicationController,
                 mDismissCallbackRegistry,
+                mDemoModeController,
                 mNotificationShadeDepthControllerLazy,
                 mStatusBarTouchableRegionManager);
 
@@ -429,7 +441,7 @@
         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/TetheringNotificationUpdaterTest.kt b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt
index 4b6bbac..75c819b 100644
--- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt
+++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt
@@ -18,33 +18,40 @@
 
 import android.app.Notification
 import android.app.NotificationManager
+import android.app.PendingIntent
+import android.app.PendingIntent.FLAG_IMMUTABLE
 import android.content.Context
+import android.content.Intent
 import android.content.pm.ActivityInfo
 import android.content.pm.ApplicationInfo
 import android.content.pm.PackageManager
 import android.content.pm.ResolveInfo
 import android.content.res.Resources
 import android.net.ConnectivityManager.TETHERING_WIFI
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING
 import android.os.Handler
 import android.os.HandlerThread
 import android.os.Looper
-import android.net.NetworkCapabilities
-import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING
 import android.os.UserHandle
+import android.provider.Settings
 import android.telephony.TelephonyManager
 import androidx.test.filters.SmallTest
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.runner.AndroidJUnit4
 import com.android.internal.util.test.BroadcastInterceptingContext
+import com.android.networkstack.tethering.TetheringNotificationUpdater.ACTION_DISABLE_TETHERING
 import com.android.networkstack.tethering.TetheringNotificationUpdater.DOWNSTREAM_NONE
 import com.android.networkstack.tethering.TetheringNotificationUpdater.EVENT_SHOW_NO_UPSTREAM
 import com.android.networkstack.tethering.TetheringNotificationUpdater.NO_UPSTREAM_NOTIFICATION_ID
 import com.android.networkstack.tethering.TetheringNotificationUpdater.RESTRICTED_NOTIFICATION_ID
 import com.android.networkstack.tethering.TetheringNotificationUpdater.ROAMING_NOTIFICATION_ID
 import com.android.networkstack.tethering.TetheringNotificationUpdater.VERIZON_CARRIER_ID
+import com.android.networkstack.tethering.TetheringNotificationUpdater.getSettingsPackageName
 import com.android.testutils.waitForIdle
 import org.junit.After
 import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotNull
 import org.junit.Assert.fail
 import org.junit.Before
 import org.junit.Test
@@ -87,12 +94,17 @@
     // every test but should always be initialized before use (or the test should crash).
     private lateinit var context: TestContext
     private lateinit var notificationUpdater: TetheringNotificationUpdater
+
+    // Initializing the following members depends on initializing some of the mocks and
+    // is more logically done in setup().
     private lateinit var fakeTetheringThread: HandlerThread
 
     private val ROAMING_CAPABILITIES = NetworkCapabilities()
     private val HOME_CAPABILITIES = NetworkCapabilities().addCapability(NET_CAPABILITY_NOT_ROAMING)
     private val NOTIFICATION_ICON_ID = R.drawable.stat_sys_tether_general
     private val TIMEOUT_MS = 500L
+    private val ACTIVITY_PENDING_INTENT = 0
+    private val BROADCAST_PENDING_INTENT = 1
 
     private inner class TestContext(c: Context) : BroadcastInterceptingContext(c) {
         override fun createContextAsUser(user: UserHandle, flags: Int) =
@@ -146,10 +158,43 @@
         fakeTetheringThread.quitSafely()
     }
 
+    private fun verifyActivityPendingIntent(intent: Intent, flags: Int) {
+        // Use FLAG_NO_CREATE to verify whether PendingIntent has FLAG_IMMUTABLE flag(forcefully add
+        // the flag in creating arguments). If the described PendingIntent does not already exist,
+        // getActivity() will return null instead of PendingIntent object.
+        val pi = PendingIntent.getActivity(
+                context.createContextAsUser(UserHandle.CURRENT, 0 /* flags */),
+                0 /* requestCode */,
+                intent,
+                flags or FLAG_IMMUTABLE or PendingIntent.FLAG_NO_CREATE,
+                null /* options */)
+        assertNotNull("Activity PendingIntent with FLAG_IMMUTABLE does not exist.", pi)
+    }
+
+    private fun verifyBroadcastPendingIntent(intent: Intent, flags: Int) {
+        // Use FLAG_NO_CREATE to verify whether PendingIntent has FLAG_IMMUTABLE flag(forcefully add
+        // the flag in creating arguments). If the described PendingIntent does not already exist,
+        // getBroadcast() will return null instead of PendingIntent object.
+        val pi = PendingIntent.getBroadcast(
+                context.createContextAsUser(UserHandle.CURRENT, 0 /* flags */),
+                0 /* requestCode */,
+                intent,
+                flags or FLAG_IMMUTABLE or PendingIntent.FLAG_NO_CREATE)
+        assertNotNull("Broadcast PendingIntent with FLAG_IMMUTABLE does not exist.", pi)
+    }
+
     private fun Notification.title() = this.extras.getString(Notification.EXTRA_TITLE)
     private fun Notification.text() = this.extras.getString(Notification.EXTRA_TEXT)
 
-    private fun verifyNotification(iconId: Int, title: String, text: String, id: Int) {
+    private fun verifyNotification(
+        iconId: Int,
+        title: String,
+        text: String,
+        id: Int,
+        intentSenderType: Int,
+        intent: Intent,
+        flags: Int
+    ) {
         verify(notificationManager, never()).cancel(any(), eq(id))
 
         val notificationCaptor = ArgumentCaptor.forClass(Notification::class.java)
@@ -161,6 +206,11 @@
         assertEquals(title, notification.title())
         assertEquals(text, notification.text())
 
+        when (intentSenderType) {
+            ACTIVITY_PENDING_INTENT -> verifyActivityPendingIntent(intent, flags)
+            BROADCAST_PENDING_INTENT -> verifyBroadcastPendingIntent(intent, flags)
+        }
+
         reset(notificationManager)
     }
 
@@ -176,6 +226,10 @@
 
     @Test
     fun testRestrictedNotification() {
+        val settingsIntent = Intent(Settings.ACTION_TETHER_SETTINGS)
+                .setPackage(getSettingsPackageName(context.packageManager))
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+
         // Set test sub id.
         notificationUpdater.onActiveDataSubscriptionIdChanged(TEST_SUBID)
         verifyNotificationCancelled(listOf(NO_UPSTREAM_NOTIFICATION_ID, ROAMING_NOTIFICATION_ID))
@@ -183,7 +237,7 @@
         // User restrictions on. Show restricted notification.
         notificationUpdater.notifyTetheringDisabledByRestriction()
         verifyNotification(NOTIFICATION_ICON_ID, TEST_DISALLOW_TITLE, TEST_DISALLOW_MESSAGE,
-                RESTRICTED_NOTIFICATION_ID)
+                RESTRICTED_NOTIFICATION_ID, ACTIVITY_PENDING_INTENT, settingsIntent, FLAG_IMMUTABLE)
 
         // User restrictions off. Clear notification.
         notificationUpdater.tetheringRestrictionLifted()
@@ -196,7 +250,7 @@
         // User restrictions on again. Show restricted notification.
         notificationUpdater.notifyTetheringDisabledByRestriction()
         verifyNotification(NOTIFICATION_ICON_ID, TEST_DISALLOW_TITLE, TEST_DISALLOW_MESSAGE,
-                RESTRICTED_NOTIFICATION_ID)
+                RESTRICTED_NOTIFICATION_ID, ACTIVITY_PENDING_INTENT, settingsIntent, FLAG_IMMUTABLE)
     }
 
     val MAX_BACKOFF_MS = 200L
@@ -234,6 +288,8 @@
 
     @Test
     fun testNoUpstreamNotification() {
+        val disableIntent = Intent(ACTION_DISABLE_TETHERING).setPackage(context.packageName)
+
         // Set test sub id.
         notificationUpdater.onActiveDataSubscriptionIdChanged(TEST_SUBID)
         verifyNotificationCancelled(listOf(NO_UPSTREAM_NOTIFICATION_ID, ROAMING_NOTIFICATION_ID))
@@ -246,7 +302,8 @@
         notificationUpdater.onUpstreamCapabilitiesChanged(null)
         notificationUpdater.handler.waitForDelayedMessage(EVENT_SHOW_NO_UPSTREAM, TIMEOUT_MS)
         verifyNotification(NOTIFICATION_ICON_ID, TEST_NO_UPSTREAM_TITLE, TEST_NO_UPSTREAM_MESSAGE,
-                NO_UPSTREAM_NOTIFICATION_ID)
+                NO_UPSTREAM_NOTIFICATION_ID, BROADCAST_PENDING_INTENT, disableIntent,
+                FLAG_IMMUTABLE)
 
         // Same capabilities changed. Nothing happened.
         notificationUpdater.onUpstreamCapabilitiesChanged(null)
@@ -260,7 +317,8 @@
         notificationUpdater.onUpstreamCapabilitiesChanged(null)
         notificationUpdater.handler.waitForDelayedMessage(EVENT_SHOW_NO_UPSTREAM, TIMEOUT_MS)
         verifyNotification(NOTIFICATION_ICON_ID, TEST_NO_UPSTREAM_TITLE, TEST_NO_UPSTREAM_MESSAGE,
-                NO_UPSTREAM_NOTIFICATION_ID)
+                NO_UPSTREAM_NOTIFICATION_ID, BROADCAST_PENDING_INTENT, disableIntent,
+                FLAG_IMMUTABLE)
 
         // No downstream.
         notificationUpdater.onDownstreamChanged(DOWNSTREAM_NONE)
@@ -305,6 +363,11 @@
 
     @Test
     fun testRoamingNotification() {
+        val disableIntent = Intent(ACTION_DISABLE_TETHERING).setPackage(context.packageName)
+        val settingsIntent = Intent(Settings.ACTION_TETHER_SETTINGS)
+                .setPackage(getSettingsPackageName(context.packageManager))
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+
         // Set test sub id.
         notificationUpdater.onActiveDataSubscriptionIdChanged(TEST_SUBID)
         verifyNotificationCancelled(listOf(NO_UPSTREAM_NOTIFICATION_ID, ROAMING_NOTIFICATION_ID))
@@ -316,7 +379,7 @@
         // Upstream capabilities changed to roaming state. Show roaming notification.
         notificationUpdater.onUpstreamCapabilitiesChanged(ROAMING_CAPABILITIES)
         verifyNotification(NOTIFICATION_ICON_ID, TEST_ROAMING_TITLE, TEST_ROAMING_MESSAGE,
-                ROAMING_NOTIFICATION_ID)
+                ROAMING_NOTIFICATION_ID, ACTIVITY_PENDING_INTENT, settingsIntent, FLAG_IMMUTABLE)
 
         // Same capabilities change. Nothing happened.
         notificationUpdater.onUpstreamCapabilitiesChanged(ROAMING_CAPABILITIES)
@@ -329,14 +392,15 @@
         // Upstream capabilities changed to roaming state again. Show roaming notification.
         notificationUpdater.onUpstreamCapabilitiesChanged(ROAMING_CAPABILITIES)
         verifyNotification(NOTIFICATION_ICON_ID, TEST_ROAMING_TITLE, TEST_ROAMING_MESSAGE,
-                ROAMING_NOTIFICATION_ID)
+                ROAMING_NOTIFICATION_ID, ACTIVITY_PENDING_INTENT, settingsIntent, FLAG_IMMUTABLE)
 
         // No upstream. Clear roaming notification and show no upstream notification.
         notificationUpdater.onUpstreamCapabilitiesChanged(null)
         notificationUpdater.handler.waitForDelayedMessage(EVENT_SHOW_NO_UPSTREAM, TIMEOUT_MS)
         verifyNotificationCancelled(listOf(ROAMING_NOTIFICATION_ID), false)
         verifyNotification(NOTIFICATION_ICON_ID, TEST_NO_UPSTREAM_TITLE, TEST_NO_UPSTREAM_MESSAGE,
-                NO_UPSTREAM_NOTIFICATION_ID)
+                NO_UPSTREAM_NOTIFICATION_ID, BROADCAST_PENDING_INTENT, disableIntent,
+                FLAG_IMMUTABLE)
 
         // No downstream.
         notificationUpdater.onDownstreamChanged(DOWNSTREAM_NONE)
@@ -347,7 +411,8 @@
         notificationUpdater.handler.waitForDelayedMessage(EVENT_SHOW_NO_UPSTREAM, TIMEOUT_MS)
         verifyNotificationCancelled(listOf(ROAMING_NOTIFICATION_ID), false)
         verifyNotification(NOTIFICATION_ICON_ID, TEST_NO_UPSTREAM_TITLE, TEST_NO_UPSTREAM_MESSAGE,
-                NO_UPSTREAM_NOTIFICATION_ID)
+                NO_UPSTREAM_NOTIFICATION_ID, BROADCAST_PENDING_INTENT, disableIntent,
+                FLAG_IMMUTABLE)
 
         // Set R.bool.config_upstream_roaming_notification to false and change upstream
         // network to roaming state again. No roaming notification.
@@ -363,8 +428,7 @@
         val testSettingsPackageName = "com.android.test.settings"
         val pm = mock(PackageManager::class.java)
         doReturn(null).`when`(pm).resolveActivity(any(), anyInt())
-        assertEquals(defaultSettingsPackageName,
-                TetheringNotificationUpdater.getSettingsPackageName(pm))
+        assertEquals(defaultSettingsPackageName, getSettingsPackageName(pm))
 
         val resolveInfo = ResolveInfo().apply {
             activityInfo = ActivityInfo().apply {
@@ -375,7 +439,6 @@
             }
         }
         doReturn(resolveInfo).`when`(pm).resolveActivity(any(), anyInt())
-        assertEquals(testSettingsPackageName,
-                TetheringNotificationUpdater.getSettingsPackageName(pm))
+        assertEquals(testSettingsPackageName, getSettingsPackageName(pm))
     }
 }
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/Android.bp b/services/Android.bp
index ef52c2a..b348b91 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -156,14 +156,10 @@
 
 java_library {
     name: "android_system_server_stubs_current",
-    defaults: ["android_stubs_dists_default"],
     srcs: [":services-stubs.sources"],
     installable: false,
     static_libs: ["android_module_lib_stubs_current"],
     sdk_version: "none",
     system_modules: "none",
     java_version: "1.8",
-    dist: {
-        dir: "apistubs/android/system-server",
-    },
 }
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 e1baefe..a2d58c8 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -609,6 +609,9 @@
                 mSendHoverExitDelayed.cancel();
                 if (mGestureDetector.isMultiFingerGesturesEnabled()
                         && mGestureDetector.isTwoFingerPassthroughEnabled()) {
+                    if (pointerIndex < 0) {
+                        return;
+                    }
                     final float deltaX =
                             mReceivedPointerTracker.getReceivedPointerDownX(pointerId)
                                     - rawEvent.getX(pointerIndex);
@@ -628,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(
@@ -791,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 {
@@ -956,37 +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;
-        float draggingY;
-        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)) {
-                draggingX = firstPtrX;
-                draggingY = firstPtrY;
-                mDraggingPointerId = firstPtrId;
-            } else {
-                draggingX = secondPtrX;
-                draggingY = secondPtrY;
-                mDraggingPointerId = secondPtrId;
-            }
-        } else {
-            // Just use the coordinates of the dragging pointer.
-            int pointerIndex = event.findPointerIndex(mDraggingPointerId);
-            draggingX = event.getX(pointerIndex);
-            draggingY = event.getY(pointerIndex);
-        }
-        event.setLocation(draggingX, draggingY);
+        mDraggingPointerId = (getDistanceToClosestEdge(firstPtrX, firstPtrY)
+                 < getDistanceToClosestEdge(secondPtrX, secondPtrY))
+                 ? firstPtrId : secondPtrId;
     }
 
     private float getDistanceToClosestEdge(float x, float y) {
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
index bd25f2b..3ee5b28 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
@@ -25,10 +25,12 @@
 
 import android.annotation.Nullable;
 import android.content.Context;
+import android.graphics.Point;
 import android.provider.Settings;
 import android.util.Log;
 import android.util.MathUtils;
 import android.util.Slog;
+import android.view.Display;
 import android.view.MotionEvent;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -90,6 +92,8 @@
 
     private MotionEventDispatcherDelegate mMotionEventDispatcherDelegate;
     private final int mDisplayId;
+    private final Context mContext;
+    private final Point mTempPoint = new Point();
 
     private final Queue<MotionEvent> mDebugOutputEventHistory;
 
@@ -107,7 +111,7 @@
             Slog.i(LOG_TAG,
                     "WindowMagnificationGestureHandler() , displayId = " + displayId + ")");
         }
-
+        mContext = context;
         mWindowMagnificationMgr = windowMagnificationMgr;
         mDetectShortcutTrigger = detectShortcutTrigger;
         mDisplayId = displayId;
@@ -184,7 +188,14 @@
         if (!mDetectShortcutTrigger) {
             return;
         }
-        toggleMagnification(Float.NaN, Float.NaN);
+        final Point screenSize = mTempPoint;
+        getScreenSize(mTempPoint);
+        toggleMagnification(screenSize.x / 2.0f, screenSize.y / 2.0f);
+    }
+
+    private  void getScreenSize(Point outSize) {
+        final Display display = mContext.getDisplay();
+        display.getRealSize(outSize);
     }
 
     @Override
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/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
index 1c31166..a92d334 100644
--- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
@@ -27,6 +27,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentSender;
+import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
@@ -214,7 +215,13 @@
                     return componentName;
                 }
                 intent.addFlags(Intent.FLAG_ACTIVITY_MATCH_EXTERNAL);
-                return intent.resolveActivity(packageManager);
+                final ActivityInfo ai =
+                        intent.resolveActivityInfo(packageManager, PackageManager.MATCH_INSTANT);
+                if (ai != null) {
+                    return new ComponentName(ai.applicationInfo.packageName, ai.name);
+                }
+
+                return null;
             }
         };
         final LayoutInflater inflater = LayoutInflater.from(context);
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/Android.bp b/services/core/Android.bp
index e76ec74..addaa65 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -2,38 +2,25 @@
     name: "services.core-sources",
     srcs: ["java/**/*.java"],
     path: "java",
-    visibility: ["//frameworks/base/services"],
-}
-
-java_library {
-    name: "protolog-common",
-    srcs: [
-        "java/com/android/server/protolog/common/**/*.java",
+    visibility: [
+        "//frameworks/base/services",
+        "//frameworks/base/core/java/com/android/internal/protolog",
     ],
-    host_supported: true,
-}
-
-java_library {
-    name: "services.core.wm.protologgroups",
-    srcs: [
-        "java/com/android/server/wm/ProtoLogGroup.java",
-    ],
-    static_libs: ["protolog-common"],
 }
 
 genrule {
     name: "services.core.protologsrc",
     srcs: [
-        ":services.core.wm.protologgroups",
+        ":protolog-groups",
         ":services.core-sources",
     ],
     tools: ["protologtool"],
     cmd: "$(location protologtool) transform-protolog-calls " +
-      "--protolog-class com.android.server.protolog.common.ProtoLog " +
-      "--protolog-impl-class com.android.server.protolog.ProtoLogImpl " +
-      "--protolog-cache-class 'com.android.server.protolog.ProtoLog$$Cache' " +
-      "--loggroups-class com.android.server.wm.ProtoLogGroup " +
-      "--loggroups-jar $(location :services.core.wm.protologgroups) " +
+      "--protolog-class com.android.internal.protolog.common.ProtoLog " +
+      "--protolog-impl-class com.android.internal.protolog.ProtoLogImpl " +
+      "--protolog-cache-class 'com.android.server.wm.ProtoLogCache' " +
+      "--loggroups-class com.android.internal.protolog.ProtoLogGroup " +
+      "--loggroups-jar $(location :protolog-groups) " +
       "--output-srcjar $(out) " +
       "$(locations :services.core-sources)",
     out: ["services.core.protolog.srcjar"],
@@ -42,14 +29,14 @@
 genrule {
     name: "generate-protolog.json",
     srcs: [
-        ":services.core.wm.protologgroups",
+        ":protolog-groups",
         ":services.core-sources",
     ],
     tools: ["protologtool"],
     cmd: "$(location protologtool) generate-viewer-config " +
-      "--protolog-class com.android.server.protolog.common.ProtoLog " +
-      "--loggroups-class com.android.server.wm.ProtoLogGroup " +
-      "--loggroups-jar $(location :services.core.wm.protologgroups) " +
+      "--protolog-class com.android.internal.protolog.common.ProtoLog " +
+      "--loggroups-class com.android.internal.protolog.ProtoLogGroup " +
+      "--loggroups-jar $(location :protolog-groups) " +
       "--viewer-conf $(out) " +
       "$(locations :services.core-sources)",
     out: ["services.core.protolog.json"],
@@ -109,6 +96,7 @@
     ],
 
     static_libs: [
+        "protolog-lib",
         "time_zone_distro",
         "time_zone_distro_installer",
         "android.hardware.authsecret-V1.0-java",
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/BinderCallsStatsService.java b/services/core/java/com/android/server/BinderCallsStatsService.java
index a3c04be..c951359 100644
--- a/services/core/java/com/android/server/BinderCallsStatsService.java
+++ b/services/core/java/com/android/server/BinderCallsStatsService.java
@@ -28,7 +28,9 @@
 import android.net.Uri;
 import android.os.BatteryStatsInternal;
 import android.os.Binder;
+import android.os.ParcelFileDescriptor;
 import android.os.Process;
+import android.os.ShellCommand;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -308,50 +310,134 @@
         }
 
         boolean verbose = false;
+        int worksourceUid = Process.INVALID_UID;
         if (args != null) {
-            for (final String arg : args) {
+            for (int i = 0; i < args.length; i++) {
+                String arg = args[i];
                 if ("-a".equals(arg)) {
                     verbose = true;
-                } else if ("--reset".equals(arg)) {
-                    reset();
-                    pw.println("binder_calls_stats reset.");
-                    return;
-                } else if ("--enable".equals(arg)) {
-                    Binder.setObserver(mBinderCallsStats);
-                    return;
-                } else if ("--disable".equals(arg)) {
-                    Binder.setObserver(null);
-                    return;
-                } else if ("--no-sampling".equals(arg)) {
-                    mBinderCallsStats.setSamplingInterval(1);
-                    return;
-                } else if ("--enable-detailed-tracking".equals(arg)) {
-                    SystemProperties.set(PERSIST_SYS_BINDER_CALLS_DETAILED_TRACKING, "1");
-                    mBinderCallsStats.setDetailedTracking(true);
-                    pw.println("Detailed tracking enabled");
-                    return;
-                } else if ("--disable-detailed-tracking".equals(arg)) {
-                    SystemProperties.set(PERSIST_SYS_BINDER_CALLS_DETAILED_TRACKING, "");
-                    mBinderCallsStats.setDetailedTracking(false);
-                    pw.println("Detailed tracking disabled");
-                    return;
-                } else if ("--dump-worksource-provider".equals(arg)) {
-                    mWorkSourceProvider.dump(pw, AppIdToPackageMap.getSnapshot());
-                    return;
                 } else if ("-h".equals(arg)) {
-                    pw.println("binder_calls_stats commands:");
-                    pw.println("  --reset: Reset stats");
-                    pw.println("  --enable: Enable tracking binder calls");
-                    pw.println("  --disable: Disables tracking binder calls");
-                    pw.println("  --no-sampling: Tracks all calls");
-                    pw.println("  --enable-detailed-tracking: Enables detailed tracking");
-                    pw.println("  --disable-detailed-tracking: Disables detailed tracking");
+                    pw.println("dumpsys binder_calls_stats options:");
+                    pw.println("  -a: Verbose");
+                    pw.println("  --work-source-uid <UID>: Dump binder calls from the UID");
                     return;
-                } else {
-                    pw.println("Unknown option: " + arg);
+                } else if ("--work-source-uid".equals(arg)) {
+                    i++;
+                    if (i >= args.length) {
+                        throw new IllegalArgumentException(
+                                "Argument expected after \"" + arg + "\"");
+                    }
+                    String uidArg = args[i];
+                    try {
+                        worksourceUid = Integer.parseInt(uidArg);
+                    } catch (NumberFormatException e) {
+                        pw.println("Invalid UID: " + uidArg);
+                        return;
+                    }
+                }
+            }
+
+            if (args.length > 0 && worksourceUid == Process.INVALID_UID) {
+                // For compatibility, support "cmd"-style commands when passed to "dumpsys".
+                BinderCallsStatsShellCommand command = new BinderCallsStatsShellCommand(pw);
+                int status = command.exec(this, null, FileDescriptor.out, FileDescriptor.err, args);
+                if (status == 0) {
+                    return;
                 }
             }
         }
-        mBinderCallsStats.dump(pw, AppIdToPackageMap.getSnapshot(), verbose);
+        mBinderCallsStats.dump(pw, AppIdToPackageMap.getSnapshot(), worksourceUid, verbose);
+    }
+
+    @Override
+    public int handleShellCommand(ParcelFileDescriptor in, ParcelFileDescriptor out,
+            ParcelFileDescriptor err, String[] args) {
+        ShellCommand command = new BinderCallsStatsShellCommand(null);
+        int status = command.exec(this, in.getFileDescriptor(), out.getFileDescriptor(),
+                err.getFileDescriptor(), args);
+        if (status != 0) {
+            command.onHelp();
+        }
+        return status;
+    }
+
+    private class BinderCallsStatsShellCommand extends ShellCommand {
+        private final PrintWriter mPrintWriter;
+
+        BinderCallsStatsShellCommand(PrintWriter printWriter) {
+            mPrintWriter = printWriter;
+        }
+
+        @Override
+        public PrintWriter getOutPrintWriter() {
+            if (mPrintWriter != null) {
+                return mPrintWriter;
+            }
+            return super.getOutPrintWriter();
+        }
+
+        @Override
+        public int onCommand(String cmd) {
+            PrintWriter pw = getOutPrintWriter();
+            if (cmd == null) {
+                return -1;
+            }
+
+            switch (cmd) {
+                case "--reset":
+                    reset();
+                    pw.println("binder_calls_stats reset.");
+                    break;
+                case "--enable":
+                    Binder.setObserver(mBinderCallsStats);
+                    break;
+                case "--disable":
+                    Binder.setObserver(null);
+                    break;
+                case "--no-sampling":
+                    mBinderCallsStats.setSamplingInterval(1);
+                    break;
+                case "--enable-detailed-tracking":
+                    SystemProperties.set(PERSIST_SYS_BINDER_CALLS_DETAILED_TRACKING, "1");
+                    mBinderCallsStats.setDetailedTracking(true);
+                    pw.println("Detailed tracking enabled");
+                    break;
+                case "--disable-detailed-tracking":
+                    SystemProperties.set(PERSIST_SYS_BINDER_CALLS_DETAILED_TRACKING, "");
+                    mBinderCallsStats.setDetailedTracking(false);
+                    pw.println("Detailed tracking disabled");
+                    break;
+                case "--dump-worksource-provider":
+                    mBinderCallsStats.setDetailedTracking(true);
+                    mWorkSourceProvider.dump(pw, AppIdToPackageMap.getSnapshot());
+                    break;
+                case "--work-source-uid":
+                    String uidArg = getNextArgRequired();
+                    try {
+                        int uid = Integer.parseInt(uidArg);
+                        mBinderCallsStats.recordAllCallsForWorkSourceUid(uid);
+                    } catch (NumberFormatException e) {
+                        pw.println("Invalid UID: " + uidArg);
+                        return -1;
+                    }
+                    break;
+                default:
+                    return handleDefaultCommands(cmd);
+            }
+            return 0;
+        }
+
+        @Override
+        public void onHelp() {
+            PrintWriter pw = getOutPrintWriter();
+            pw.println("binder_calls_stats commands:");
+            pw.println("  --reset: Reset stats");
+            pw.println("  --enable: Enable tracking binder calls");
+            pw.println("  --disable: Disables tracking binder calls");
+            pw.println("  --no-sampling: Tracks all calls");
+            pw.println("  --enable-detailed-tracking: Enables detailed tracking");
+            pw.println("  --disable-detailed-tracking: Disables detailed tracking");
+            pw.println("  --work-source-uid <UID>: Track all binder calls from the UID");
+        }
     }
 }
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 0ddfa1c..97f3b37 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -185,10 +185,10 @@
     /** Set of interfaces with active alerts. */
     @GuardedBy("mQuotaLock")
     private HashMap<String, Long> mActiveAlerts = Maps.newHashMap();
-    /** Set of UIDs blacklisted on metered networks. */
+    /** Set of UIDs denylisted on metered networks. */
     @GuardedBy("mRulesLock")
     private SparseBooleanArray mUidRejectOnMetered = new SparseBooleanArray();
-    /** Set of UIDs whitelisted on metered networks. */
+    /** Set of UIDs allowlisted on metered networks. */
     @GuardedBy("mRulesLock")
     private SparseBooleanArray mUidAllowOnMetered = new SparseBooleanArray();
     /** Set of UIDs with cleartext penalties. */
@@ -561,27 +561,27 @@
             synchronized (mRulesLock) {
                 size = mUidRejectOnMetered.size();
                 if (size > 0) {
-                    if (DBG) Slog.d(TAG, "Pushing " + size + " UIDs to metered blacklist rules");
+                    if (DBG) Slog.d(TAG, "Pushing " + size + " UIDs to metered denylist rules");
                     uidRejectOnQuota = mUidRejectOnMetered;
                     mUidRejectOnMetered = new SparseBooleanArray();
                 }
 
                 size = mUidAllowOnMetered.size();
                 if (size > 0) {
-                    if (DBG) Slog.d(TAG, "Pushing " + size + " UIDs to metered whitelist rules");
+                    if (DBG) Slog.d(TAG, "Pushing " + size + " UIDs to metered allowlist rules");
                     uidAcceptOnQuota = mUidAllowOnMetered;
                     mUidAllowOnMetered = new SparseBooleanArray();
                 }
             }
             if (uidRejectOnQuota != null) {
                 for (int i = 0; i < uidRejectOnQuota.size(); i++) {
-                    setUidMeteredNetworkBlacklist(uidRejectOnQuota.keyAt(i),
+                    setUidMeteredNetworkDenylist(uidRejectOnQuota.keyAt(i),
                             uidRejectOnQuota.valueAt(i));
                 }
             }
             if (uidAcceptOnQuota != null) {
                 for (int i = 0; i < uidAcceptOnQuota.size(); i++) {
-                    setUidMeteredNetworkWhitelist(uidAcceptOnQuota.keyAt(i),
+                    setUidMeteredNetworkAllowlist(uidAcceptOnQuota.keyAt(i),
                             uidAcceptOnQuota.valueAt(i));
                 }
             }
@@ -1307,14 +1307,14 @@
         }
     }
 
-    private void setUidOnMeteredNetworkList(int uid, boolean blacklist, boolean enable) {
+    private void setUidOnMeteredNetworkList(int uid, boolean denylist, boolean enable) {
         NetworkStack.checkNetworkStackPermission(mContext);
 
         synchronized (mQuotaLock) {
             boolean oldEnable;
             SparseBooleanArray quotaList;
             synchronized (mRulesLock) {
-                quotaList = blacklist ? mUidRejectOnMetered : mUidAllowOnMetered;
+                quotaList = denylist ? mUidRejectOnMetered : mUidAllowOnMetered;
                 oldEnable = quotaList.get(uid, false);
             }
             if (oldEnable == enable) {
@@ -1324,7 +1324,7 @@
 
             Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "inetd bandwidth");
             try {
-                if (blacklist) {
+                if (denylist) {
                     if (enable) {
                         mNetdService.bandwidthAddNaughtyApp(uid);
                     } else {
@@ -1353,12 +1353,12 @@
     }
 
     @Override
-    public void setUidMeteredNetworkBlacklist(int uid, boolean enable) {
+    public void setUidMeteredNetworkDenylist(int uid, boolean enable) {
         setUidOnMeteredNetworkList(uid, true, enable);
     }
 
     @Override
-    public void setUidMeteredNetworkWhitelist(int uid, boolean enable) {
+    public void setUidMeteredNetworkAllowlist(int uid, boolean enable) {
         setUidOnMeteredNetworkList(uid, false, enable);
     }
 
@@ -1626,7 +1626,7 @@
                     }
                 }
             }
-            // Normally, whitelist chains only contain deny rules, so numUids == exemptUids.length.
+            // Normally, allowlist chains only contain deny rules, so numUids == exemptUids.length.
             // But the code does not guarantee this in any way, and at least in one case - if we add
             // a UID rule to the firewall, and then disable the firewall - the chains can contain
             // the wrong type of rule. In this case, don't close connections that we shouldn't.
@@ -1691,7 +1691,7 @@
             // Close any sockets that were opened by the affected UIDs. This has to be done after
             // disabling network connectivity, in case they react to the socket close by reopening
             // the connection and race with the iptables commands that enable the firewall. All
-            // whitelist and blacklist chains allow RSTs through.
+            // allowlist and denylist chains allow RSTs through.
             if (enable) {
                 closeSocketsForFirewallChainLocked(chain, chainName);
             }
@@ -1828,7 +1828,7 @@
             } else {
                 ruleName = "deny";
             }
-        } else { // Blacklist mode
+        } else { // Denylist mode
             if (rule == FIREWALL_RULE_DENY) {
                 ruleName = "deny";
             } else {
@@ -1913,8 +1913,8 @@
             pw.print("Active alert ifaces: "); pw.println(mActiveAlerts.toString());
             pw.print("Data saver mode: "); pw.println(mDataSaverMode);
             synchronized (mRulesLock) {
-                dumpUidRuleOnQuotaLocked(pw, "blacklist", mUidRejectOnMetered);
-                dumpUidRuleOnQuotaLocked(pw, "whitelist", mUidAllowOnMetered);
+                dumpUidRuleOnQuotaLocked(pw, "denylist", mUidRejectOnMetered);
+                dumpUidRuleOnQuotaLocked(pw, "allowlist", mUidAllowOnMetered);
             }
         }
 
@@ -2179,9 +2179,9 @@
             }
         }
 
-        void setUidOnMeteredNetworkList(boolean blacklist, int uid, boolean enable) {
+        void setUidOnMeteredNetworkList(boolean denylist, int uid, boolean enable) {
             synchronized (mRulesLock) {
-                if (blacklist) {
+                if (denylist) {
                     mUidRejectOnMetered.put(uid, enable);
                 } else {
                     mUidAllowOnMetered.put(uid, enable);
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index 1689656..e675d8d 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -1453,8 +1453,11 @@
             } else {
                 mHealthCheckState = HealthCheckState.ACTIVE;
             }
-            Slog.i(TAG, "Updated health check state for package " + getName() + ": "
-                    + toString(oldState) + " -> " + toString(mHealthCheckState));
+
+            if (oldState != mHealthCheckState) {
+                Slog.i(TAG, "Updated health check state for package " + getName() + ": "
+                        + toString(oldState) + " -> " + toString(mHealthCheckState));
+            }
             return mHealthCheckState;
         }
 
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..d1d9c0e 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -3282,7 +3282,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..df966c1 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;
 
@@ -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,21 +250,26 @@
         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 userHandle) {
+        final TargetUser targetUser;
+        synchronized (mTargetUsers) {
+            targetUser = mTargetUsers.get(userHandle);
         }
-        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 " + userHandle);
+        return targetUser;
     }
 
     /**
      * Starts the given user.
      */
     public void startUser(final @NonNull TimingsTraceAndSlog t, final @UserIdInt int userHandle) {
+        // Create cached TargetUser
+        final UserInfo userInfo = mUserManagerInternal.getUserInfo(userHandle);
+        Preconditions.checkState(userInfo != null, "No UserInfo for " + userHandle);
+        synchronized (mTargetUsers) {
+            mTargetUsers.put(userHandle, new TargetUser(userInfo));
+        }
+
         onUser(t, START, userHandle);
     }
 
@@ -291,6 +306,11 @@
      */
     public void cleanupUser(final @UserIdInt int userHandle) {
         onUser(CLEANUP, userHandle);
+
+        // Remove cached TargetUser
+        synchronized (mTargetUsers) {
+            mTargetUsers.remove(userHandle);
+        }
     }
 
     private void onUser(@NonNull String onWhat, @UserIdInt int userHandle) {
@@ -306,9 +326,9 @@
             @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 59ac09c..96d973e 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -114,9 +114,6 @@
     private static final VibrationAttributes DEFAULT_ATTRIBUTES =
             new VibrationAttributes.Builder().build();
 
-    // If HAL supports callbacks set the timeout to ASYNC_TIMEOUT_MULTIPLIER * duration.
-    private static final long ASYNC_TIMEOUT_MULTIPLIER = 2;
-
     // A mapping from the intensity adjustment to the scaling to apply, where the intensity
     // adjustment is defined as the delta between the default intensity level and the user selected
     // intensity level. It's important that we apply the scaling on the delta between the two so
@@ -130,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;
@@ -187,8 +185,10 @@
 
     static native int[] vibratorGetSupportedEffects(long controllerPtr);
 
-    static native long vibratorPerformEffect(long effect, long strength, Vibration vibration,
-            boolean withCallback);
+    static native int[] vibratorGetSupportedPrimitives(long controllerPtr);
+
+    static native long vibratorPerformEffect(
+            long controllerPtr, long effect, long strength, Vibration vibration);
 
     static native void vibratorPerformComposedEffect(long controllerPtr,
             VibrationEffect.Composition.PrimitiveEffect[] effect, Vibration vibration);
@@ -400,6 +400,7 @@
         mNativeWrapper.vibratorOff();
 
         mSupportedEffects = asList(mNativeWrapper.vibratorGetSupportedEffects());
+        mSupportedPrimitives = asList(mNativeWrapper.vibratorGetSupportedPrimitives());
         mCapabilities = mNativeWrapper.vibratorGetCapabilities();
 
         mContext = context;
@@ -650,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;
     }
@@ -898,19 +902,11 @@
         }
     }
 
-    private final Runnable mVibrationEndRunnable = new Runnable() {
-        @Override
-        public void run() {
-            onVibrationFinished();
-        }
-    };
-
     @GuardedBy("mLock")
     private void doCancelVibrateLocked() {
         Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doCancelVibrateLocked");
         try {
-            mH.removeCallbacks(mVibrationEndRunnable);
             if (mThread != null) {
                 mThread.cancel();
                 mThread = null;
@@ -958,13 +954,11 @@
     private void startVibrationInnerLocked(Vibration vib) {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationInnerLocked");
         try {
-            long timeout = 0;
             mCurrentVibration = vib;
             if (vib.effect instanceof VibrationEffect.OneShot) {
                 Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
                 VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) vib.effect;
                 doVibratorOn(oneShot.getDuration(), oneShot.getAmplitude(), vib);
-                timeout = oneShot.getDuration() * ASYNC_TIMEOUT_MULTIPLIER;
             } else if (vib.effect instanceof VibrationEffect.Waveform) {
                 // mThread better be null here. doCancelVibrate should always be
                 // called before startNextVibrationLocked or startVibrationLocked.
@@ -973,24 +967,13 @@
                 mThread.start();
             } else if (vib.effect instanceof VibrationEffect.Prebaked) {
                 Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
-                timeout = doVibratorPrebakedEffectLocked(vib);
+                doVibratorPrebakedEffectLocked(vib);
             } else if (vib.effect instanceof  VibrationEffect.Composed) {
                 Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
                 doVibratorComposedEffectLocked(vib);
-                // FIXME: We rely on the completion callback here, but I don't think we require that
-                // devices which support composition also support the completion callback. If we
-                // ever get a device that supports the former but not the latter, then we have no
-                // real way of knowing how long a given effect should last.
-                timeout = 10_000;
             } else {
                 Slog.e(TAG, "Unknown vibration type, ignoring");
             }
-            // Post extra runnable to ensure vibration will end even if the HAL or native controller
-            // never triggers the callback.
-            // TODO: Move ASYNC_TIMEOUT_MULTIPLIER here once native controller is fully integrated.
-            if (timeout > 0) {
-                mH.postDelayed(mVibrationEndRunnable, timeout);
-            }
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
         }
@@ -1354,7 +1337,7 @@
     }
 
     @GuardedBy("mLock")
-    private long doVibratorPrebakedEffectLocked(Vibration vib) {
+    private void doVibratorPrebakedEffectLocked(Vibration vib) {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doVibratorPrebakedEffectLocked");
         try {
             final VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) vib.effect;
@@ -1364,25 +1347,20 @@
             }
             // Input devices don't support prebaked effect, so skip trying it with them.
             if (!usingInputDeviceVibrators) {
-                long duration = mNativeWrapper.vibratorPerformEffect(prebaked.getId(),
-                        prebaked.getEffectStrength(), vib,
-                        hasCapability(IVibrator.CAP_PERFORM_CALLBACK));
-                long timeout = duration;
-                if (hasCapability(IVibrator.CAP_PERFORM_CALLBACK)) {
-                    timeout *= ASYNC_TIMEOUT_MULTIPLIER;
-                }
-                if (timeout > 0) {
+                long duration = mNativeWrapper.vibratorPerformEffect(
+                        prebaked.getId(), prebaked.getEffectStrength(), vib);
+                if (duration > 0) {
                     noteVibratorOnLocked(vib.uid, duration);
-                    return timeout;
+                    return;
                 }
             }
             if (!prebaked.shouldFallback()) {
-                return 0;
+                return;
             }
             VibrationEffect effect = getFallbackEffect(prebaked.getId());
             if (effect == null) {
                 Slog.w(TAG, "Failed to play prebaked effect, no fallback");
-                return 0;
+                return;
             }
             Vibration fallbackVib = new Vibration(vib.token, effect, vib.attrs, vib.uid,
                     vib.opPkg, vib.reason + " (fallback)");
@@ -1390,7 +1368,7 @@
             linkVibration(fallbackVib);
             applyVibrationIntensityScalingLocked(fallbackVib, intensity);
             startVibrationInnerLocked(fallbackVib);
-            return 0;
+            return;
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
         }
@@ -1530,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) {
@@ -1788,10 +1767,15 @@
             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,
-                boolean withCallback) {
-            return VibratorService.vibratorPerformEffect(effect, strength, vibration, withCallback);
+        public long vibratorPerformEffect(long effect, long strength, Vibration vibration) {
+            return VibratorService.vibratorPerformEffect(
+                    mNativeControllerPtr, effect, strength, vibration);
         }
 
         /** Turns vibrator on to perform one of the supported composed effects. */
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 3eb26de..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,18 +705,10 @@
                         "Not potential delay (user " + r.userId + " not started): " + r);
             }
         }
-
         if (allowBackgroundActivityStarts) {
             r.allowBgActivityStartsOnServiceStart();
         }
         ComponentName cmp = startServiceInnerLocked(smap, service, r, callerFg, addToStarting);
-
-        if (!r.mAllowWhileInUsePermissionInFgs) {
-            r.mAllowWhileInUsePermissionInFgs =
-                    shouldAllowWhileInUsePermissionInFgsLocked(callingPackage, callingPid,
-                            callingUid, service, r, allowBackgroundActivityStarts);
-        }
-
         return cmp;
     }
 
@@ -1369,6 +1378,7 @@
                                     + r.shortInstanceName);
                 }
             }
+
             boolean alreadyStartedOp = false;
             boolean stopProcStatsOp = false;
             if (r.fgRequired) {
@@ -1415,6 +1425,24 @@
                     ignoreForeground = true;
                 }
 
+                if (!ignoreForeground) {
+                    if (!r.mAllowStartForeground) {
+                        if (!r.mLoggedInfoAllowStartForeground) {
+                            Slog.wtf(TAG, "Background started FGS "
+                                    + r.mInfoAllowStartForeground);
+                            r.mLoggedInfoAllowStartForeground = true;
+                        }
+                        if (mAm.mConstants.mFlagFgsStartRestrictionEnabled) {
+                            Slog.w(TAG,
+                                    "Service.startForeground() not allowed due to "
+                                            + "mAllowStartForeground false: service "
+                                            + r.shortInstanceName);
+                            updateServiceForegroundLocked(r.app, true);
+                            ignoreForeground = true;
+                        }
+                    }
+                }
+
                 // Apps under strict background restrictions simply don't get to have foreground
                 // services, so now that we've enforced the startForegroundService() contract
                 // we only do the machinery of making the service foreground when the app
@@ -2067,12 +2095,7 @@
                 }
             }
 
-            if (!s.mAllowWhileInUsePermissionInFgs) {
-                s.mAllowWhileInUsePermissionInFgs =
-                        shouldAllowWhileInUsePermissionInFgsLocked(callingPackage,
-                                callingPid, callingUid,
-                                service, s, false);
-            }
+            setFgsRestrictionLocked(callingPackage, callingPid, callingUid, service, s, false);
 
             if (s.app != null) {
                 if ((flags&Context.BIND_TREAT_LIKE_ACTIVITY) != 0) {
@@ -4840,21 +4863,48 @@
     }
 
     /**
-     * Should allow while-in-use permissions in foreground service or not.
-     * while-in-use permissions in FGS started from background might be restricted.
+     * There are two FGS restrictions:
+     * In R, mAllowWhileInUsePermissionInFgs is to allow while-in-use permissions in foreground
+     *  service or not. while-in-use permissions in FGS started from background might be restricted.
+     * In S, mAllowStartForeground is to allow FGS to startForeground or not. Service started
+     * from background may not become a FGS.
      * @param callingPackage caller app's package name.
      * @param callingUid caller app's uid.
      * @param intent intent to start/bind service.
      * @param r the service to start.
      * @return true if allow, false otherwise.
      */
-    private boolean shouldAllowWhileInUsePermissionInFgsLocked(String callingPackage,
+    private void setFgsRestrictionLocked(String callingPackage,
             int callingPid, int callingUid, Intent intent, ServiceRecord r,
             boolean allowBackgroundActivityStarts) {
-        // Is the background FGS start restriction turned on?
+        // Check DeviceConfig flag.
         if (!mAm.mConstants.mFlagBackgroundFgsStartRestrictionEnabled) {
-            return true;
+            r.mAllowWhileInUsePermissionInFgs = true;
         }
+
+        if (!r.mAllowWhileInUsePermissionInFgs || !r.mAllowStartForeground) {
+            final boolean temp = shouldAllowFgsFeatureLocked(callingPackage, callingPid,
+                    callingUid, intent, r, allowBackgroundActivityStarts);
+            if (!r.mAllowWhileInUsePermissionInFgs) {
+                r.mAllowWhileInUsePermissionInFgs = temp;
+            }
+            if (!r.mAllowStartForeground) {
+                r.mAllowStartForeground = temp;
+            }
+        }
+    }
+
+    /**
+     * Should allow FGS feature or not.
+     * @param callingPackage caller app's package name.
+     * @param callingUid caller app's uid.
+     * @param intent intent to start/bind service.
+     * @param r the service to start.
+     * @return true if allow, false otherwise.
+     */
+    private boolean shouldAllowFgsFeatureLocked(String callingPackage,
+            int callingPid, int callingUid, Intent intent, ServiceRecord r,
+            boolean allowBackgroundActivityStarts) {
         // Is the allow activity background start flag on?
         if (allowBackgroundActivityStarts) {
             return true;
@@ -4894,10 +4944,11 @@
         }
 
         // Is the calling UID at PROCESS_STATE_TOP or above?
-        final boolean isCallingUidTopApp = appIsTopLocked(callingUid);
-        if (isCallingUidTopApp) {
+        final int uidState = mAm.getUidState(callingUid);
+        if (uidState <= ActivityManager.PROCESS_STATE_TOP) {
             return true;
         }
+
         // Does the calling UID have any visible activity?
         final boolean isCallingUidVisible = mAm.mAtmInternal.isUidForeground(callingUid);
         if (isCallingUidVisible) {
@@ -4915,6 +4966,19 @@
         if (isDeviceOwner) {
             return true;
         }
+
+        final String info =
+                "[callingPackage: " + callingPackage
+                        + "; callingUid: " + callingUid
+                        + "; uidState: " + ProcessList.makeProcStateString(uidState)
+                        + "; intent: " + intent
+                        + "; targetSdkVersion:" + r.appInfo.targetSdkVersion
+                        + "]";
+        if (!info.equals(r.mInfoAllowStartForeground)) {
+            r.mLoggedInfoAllowStartForeground = false;
+            r.mInfoAllowStartForeground = info;
+        }
+
         return false;
     }
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 775119c..e9539be 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -144,6 +144,13 @@
     private static final String KEY_DEFAULT_BACKGROUND_FGS_STARTS_RESTRICTION_ENABLED =
             "default_background_fgs_starts_restriction_enabled";
 
+    /**
+     * Default value for mFlagFgsStartRestrictionEnabled if not explicitly set in
+     * Settings.Global.
+     */
+    private static final String KEY_DEFAULT_FGS_STARTS_RESTRICTION_ENABLED =
+            "default_fgs_starts_restriction_enabled";
+
     // Maximum number of cached processes we will allow.
     public int MAX_CACHED_PROCESSES = DEFAULT_MAX_CACHED_PROCESSES;
 
@@ -293,6 +300,11 @@
     // started, the restriction is on while-in-use permissions.)
     volatile boolean mFlagBackgroundFgsStartRestrictionEnabled = true;
 
+    // Indicates whether the foreground service background start restriction is enabled.
+    // When the restriction is enabled, service is not allowed to startForeground from background
+    // at all.
+    volatile boolean mFlagFgsStartRestrictionEnabled = false;
+
     private final ActivityManagerService mService;
     private ContentResolver mResolver;
     private final KeyValueListParser mParser = new KeyValueListParser(',');
@@ -436,6 +448,9 @@
                             case KEY_DEFAULT_BACKGROUND_FGS_STARTS_RESTRICTION_ENABLED:
                                 updateBackgroundFgsStartsRestriction();
                                 break;
+                            case KEY_DEFAULT_FGS_STARTS_RESTRICTION_ENABLED:
+                                updateFgsStartsRestriction();
+                                break;
                             case KEY_OOMADJ_UPDATE_POLICY:
                                 updateOomAdjUpdatePolicy();
                                 break;
@@ -659,6 +674,7 @@
         mFlagForegroundServiceStartsLoggingEnabled = Settings.Global.getInt(mResolver,
                 Settings.Global.FOREGROUND_SERVICE_STARTS_LOGGING_ENABLED, 1) == 1;
     }
+
     private void updateBackgroundFgsStartsRestriction() {
         mFlagBackgroundFgsStartRestrictionEnabled = DeviceConfig.getBoolean(
                 DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -666,6 +682,13 @@
                 /*defaultValue*/ true);
     }
 
+    private void updateFgsStartsRestriction() {
+        mFlagFgsStartRestrictionEnabled = DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                KEY_DEFAULT_FGS_STARTS_RESTRICTION_ENABLED,
+                /*defaultValue*/ false);
+    }
+
     private void updateOomAdjUpdatePolicy() {
         OOMADJ_UPDATE_QUICK = DeviceConfig.getInt(
                 DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b62ff1c2..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;
@@ -1581,6 +1497,7 @@
     static final int DISPATCH_OOM_ADJ_OBSERVER_MSG = 70;
     static final int KILL_APP_ZYGOTE_MSG = 71;
     static final int BINDER_HEAVYHITTER_AUTOSAMPLER_TIMEOUT_MSG = 72;
+    static final int WAIT_FOR_CONTENT_PROVIDER_TIMEOUT_MSG = 73;
 
     static final int FIRST_BROADCAST_QUEUE_MSG = 200;
 
@@ -1698,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;
@@ -1794,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;
@@ -1917,6 +1829,11 @@
                 case BINDER_HEAVYHITTER_AUTOSAMPLER_TIMEOUT_MSG: {
                     handleBinderHeavyHitterAutoSamplerTimeOut();
                 } break;
+                case WAIT_FOR_CONTENT_PROVIDER_TIMEOUT_MSG: {
+                    synchronized (ActivityManagerService.this) {
+                        ((ContentProviderRecord) msg.obj).onProviderPublishStatusLocked(false);
+                    }
+                } break;
             }
         }
     }
@@ -2505,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);
@@ -2600,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();
 
@@ -3416,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) {
@@ -4603,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,
@@ -4663,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,
@@ -7494,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);
         }
     }
 
@@ -10121,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;
                 }
             }
@@ -10255,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));
@@ -10421,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:");
@@ -10507,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;
             }
@@ -10593,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);
@@ -12101,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,
@@ -12707,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,
@@ -14470,7 +14188,7 @@
                 resolvedType, resultTo, resultCode, resultData, resultExtras, requiredPermissions,
                 appOp, bOptions, ordered, sticky, callingPid, callingUid, realCallingUid,
                 realCallingPid, userId, false /* allowBackgroundActivityStarts */,
-                null /* tokenNeededForBackgroundActivityStarts */, null /* broadcastWhitelist */);
+                null /* tokenNeededForBackgroundActivityStarts */, null /* broadcastAllowList */);
     }
 
     @GuardedBy("this")
@@ -15314,7 +15032,7 @@
                         OP_NONE, bOptions, serialized, sticky, -1, uid, realCallingUid,
                         realCallingPid, userId, allowBackgroundActivityStarts,
                         backgroundActivityStartsToken,
-                        null /*broadcastWhitelist*/);
+                        null /* broadcastAllowList */);
             } finally {
                 Binder.restoreCallingIdentity(origId);
             }
@@ -15921,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,
@@ -16224,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);
+                }
             }
         }
     }
@@ -16812,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);
     }
 
     /**
@@ -18473,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/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index 5cc7aba..bfba4af 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -50,6 +50,7 @@
 import android.os.Bundle;
 import android.os.Debug;
 import android.os.IBinder;
+import android.os.Message;
 import android.os.Process;
 import android.os.RemoteCallback;
 import android.os.RemoteException;
@@ -218,7 +219,7 @@
                     // of being published...  but it is also allowed to run
                     // in the caller's process, so don't make a connection
                     // and just let the caller instantiate its own instance.
-                    ContentProviderHolder holder = cpr.newHolder(null);
+                    ContentProviderHolder holder = cpr.newHolder(null, true);
                     // don't give caller the provider object, it needs to make its own.
                     holder.provider = null;
                     return holder;
@@ -415,7 +416,7 @@
                     // info and allow the caller to instantiate it.  Only do
                     // this if the provider is the same user as the caller's
                     // process, or can run as root (so can be in any process).
-                    return cpr.newHolder(null);
+                    return cpr.newHolder(null, true);
                 }
 
                 if (ActivityManagerDebugConfig.DEBUG_PROVIDER) {
@@ -513,6 +514,38 @@
                     UserHandle.getAppId(cpi.applicationInfo.uid));
         }
 
+        if (caller != null) {
+            // The client will be waiting, and we'll notify it when the provider is ready.
+            synchronized (cpr) {
+                if (cpr.provider == null) {
+                    if (cpr.launchingApp == null) {
+                        Slog.w(TAG, "Unable to launch app "
+                                + cpi.applicationInfo.packageName + "/"
+                                + cpi.applicationInfo.uid + " for provider "
+                                + name + ": launching app became null");
+                        EventLogTags.writeAmProviderLostProcess(
+                                UserHandle.getUserId(cpi.applicationInfo.uid),
+                                cpi.applicationInfo.packageName,
+                                cpi.applicationInfo.uid, name);
+                        return null;
+                    }
+
+                    if (conn != null) {
+                        conn.waiting = true;
+                    }
+                    Message msg = mService.mHandler.obtainMessage(
+                            ActivityManagerService.WAIT_FOR_CONTENT_PROVIDER_TIMEOUT_MSG);
+                    msg.obj = cpr;
+                    mService.mHandler.sendMessageDelayed(msg,
+                            ContentResolver.CONTENT_PROVIDER_READY_TIMEOUT_MILLIS);
+                }
+            }
+            // Return a holder instance even if we are waiting for the publishing of the provider,
+            // client will check for the holder.provider to see if it needs to wait for it.
+            return cpr.newHolder(conn, false);
+        }
+
+        // Because of the provider's external client (i.e., SHELL), we'll have to wait right here.
         // Wait for the provider to be published...
         final long timeout =
                 SystemClock.uptimeMillis() + ContentResolver.CONTENT_PROVIDER_READY_TIMEOUT_MILLIS;
@@ -569,7 +602,7 @@
                     + " caller=" + callerName + "/" + Binder.getCallingUid());
             return null;
         }
-        return cpr.newHolder(conn);
+        return cpr.newHolder(conn, false);
     }
 
     void publishContentProviders(IApplicationThread caller, List<ContentProviderHolder> providers) {
@@ -622,6 +655,8 @@
                 }
                 if (wasInLaunchingProviders) {
                     mService.mHandler.removeMessages(
+                            ActivityManagerService.WAIT_FOR_CONTENT_PROVIDER_TIMEOUT_MSG, dst);
+                    mService.mHandler.removeMessages(
                             ActivityManagerService.CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG, r);
                 }
                 // Make sure the package is associated with the process.
@@ -635,6 +670,7 @@
                     dst.provider = src.provider;
                     dst.setProcess(r);
                     dst.notifyAll();
+                    dst.onProviderPublishStatusLocked(true);
                 }
                 dst.mRestartCount = 0;
                 mService.updateOomAdjLocked(r, true, OomAdjuster.OOM_ADJ_REASON_GET_PROVIDER);
@@ -1504,6 +1540,9 @@
             synchronized (cpr) {
                 cpr.launchingApp = null;
                 cpr.notifyAll();
+                cpr.onProviderPublishStatusLocked(false);
+                mService.mHandler.removeMessages(
+                        ActivityManagerService.WAIT_FOR_CONTENT_PROVIDER_TIMEOUT_MSG, cpr);
             }
             final int userId = UserHandle.getUserId(cpr.uid);
             // Don't remove from provider map if it doesn't match
diff --git a/services/core/java/com/android/server/am/ContentProviderRecord.java b/services/core/java/com/android/server/am/ContentProviderRecord.java
index d8d8ccc..fb8b5d4 100644
--- a/services/core/java/com/android/server/am/ContentProviderRecord.java
+++ b/services/core/java/com/android/server/am/ContentProviderRecord.java
@@ -85,11 +85,12 @@
         noReleaseNeeded = cpr.noReleaseNeeded;
     }
 
-    public ContentProviderHolder newHolder(ContentProviderConnection conn) {
+    public ContentProviderHolder newHolder(ContentProviderConnection conn, boolean local) {
         ContentProviderHolder holder = new ContentProviderHolder(info);
         holder.provider = provider;
         holder.noReleaseNeeded = noReleaseNeeded;
         holder.connection = conn;
+        holder.mLocal = local;
         return holder;
     }
 
@@ -179,6 +180,50 @@
         return !connections.isEmpty() || hasExternalProcessHandles();
     }
 
+    /**
+     * Notify all clients that the provider has been published and ready to use,
+     * or timed out.
+     *
+     * @param status true: successfully published; false: timed out
+     */
+    void onProviderPublishStatusLocked(boolean status) {
+        final int numOfConns = connections.size();
+        final int userId = UserHandle.getUserId(appInfo.uid);
+        for (int i = 0; i < numOfConns; i++) {
+            final ContentProviderConnection conn = connections.get(i);
+            if (conn.waiting && conn.client != null) {
+                final ProcessRecord client = conn.client;
+                if (!status) {
+                    if (launchingApp == null) {
+                        Slog.w(TAG_AM, "Unable to launch app "
+                                + appInfo.packageName + "/"
+                                + appInfo.uid + " for provider "
+                                + info.authority + ": launching app became null");
+                        EventLogTags.writeAmProviderLostProcess(
+                                userId,
+                                appInfo.packageName,
+                                appInfo.uid, info.authority);
+                    } else {
+                        Slog.wtf(TAG_AM, "Timeout waiting for provider "
+                                + appInfo.packageName + "/"
+                                + appInfo.uid + " for provider "
+                                + info.authority
+                                + " caller=" + client);
+                    }
+                }
+                if (client.thread != null) {
+                    try {
+                        client.thread.notifyContentProviderPublishStatus(
+                                newHolder(status ? conn : null, false),
+                                info.authority, userId, status);
+                    } catch (RemoteException e) {
+                    }
+                }
+            }
+            conn.waiting = false;
+        }
+    }
+
     void dump(PrintWriter pw, String prefix, boolean full) {
         if (full) {
             pw.print(prefix); pw.print("package=");
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..2e8660e 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -2960,7 +2960,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/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 4a27030..66677b67 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -146,6 +146,12 @@
     // the most recent package that start/bind this service.
     String mRecentCallingPackage;
 
+    // allow the service becomes foreground service? Service started from background may not be
+    // allowed to become a foreground service.
+    boolean mAllowStartForeground;
+    String mInfoAllowStartForeground;
+    boolean mLoggedInfoAllowStartForeground;
+
     String stringName;      // caching of toString
 
     private int lastStartId;    // identifier of most recent start request.
@@ -408,6 +414,10 @@
                 pw.println(mAllowWhileInUsePermissionInFgs);
         pw.print(prefix); pw.print("recentCallingPackage=");
                 pw.println(mRecentCallingPackage);
+        pw.print(prefix); pw.print("allowStartForeground=");
+        pw.println(mAllowStartForeground);
+        pw.print(prefix); pw.print("infoAllowStartForeground=");
+        pw.println(mInfoAllowStartForeground);
         if (delayed) {
             pw.print(prefix); pw.print("delayed="); pw.println(delayed);
         }
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 0658e81..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);
             }
@@ -1630,7 +1632,7 @@
             UserInfo currentUserInfo = getUserInfo(currentUserId);
             Pair<UserInfo, UserInfo> userNames = new Pair<>(currentUserInfo, targetUserInfo);
             mUiHandler.removeMessages(START_USER_SWITCH_UI_MSG);
-            mUiHandler.sendMessage(mHandler.obtainMessage(
+            mUiHandler.sendMessage(mUiHandler.obtainMessage(
                     START_USER_SWITCH_UI_MSG, userNames));
         } else {
             mHandler.removeMessages(START_USER_SWITCH_FG_MSG);
@@ -2887,13 +2889,18 @@
 
         void showUserSwitchingDialog(UserInfo fromUser, UserInfo toUser,
                 String switchingFromSystemUserMessage, String switchingToSystemUserMessage) {
-            if (!mService.mContext.getPackageManager()
+            if (mService.mContext.getPackageManager()
                     .hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
-                final Dialog d = new UserSwitchingDialog(mService, mService.mContext, fromUser,
-                        toUser, true /* above system */, switchingFromSystemUserMessage,
-                        switchingToSystemUserMessage);
-                d.show();
+                // config_customUserSwitchUi is set to true on Automotive as CarSystemUI is
+                // responsible to show the UI; OEMs should not change that, but if they do, we
+                // should at least warn the user...
+                Slog.w(TAG, "Showing user switch dialog on UserController, it could cause a race "
+                        + "condition if it's shown by CarSystemUI as well");
             }
+            final Dialog d = new UserSwitchingDialog(mService, mService.mContext, fromUser,
+                    toUser, true /* above system */, switchingFromSystemUserMessage,
+                    switchingToSystemUserMessage);
+            d.show();
         }
 
         void reportGlobalUsageEventLocked(int event) {
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..bf7c68a 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...
@@ -1282,7 +1283,6 @@
             }
 
             if (isPlatformTelevision()) {
-                checkAddAllFixedVolumeDevices(AudioSystem.DEVICE_OUT_HDMI, caller);
                 synchronized (mHdmiClientLock) {
                     if (mHdmiManager != null && mHdmiPlaybackClient != null) {
                         updateHdmiCecSinkLocked(mHdmiCecSink | false);
@@ -1302,22 +1302,54 @@
         }
     }
 
-    private void checkAddAllFixedVolumeDevices(int device, String caller) {
+    /**
+     * Update volume states for the given device.
+     *
+     * This will initialize the volume index if no volume index is available.
+     * If the device is the currently routed device, fixed/full volume policies will be applied.
+     *
+     * @param device a single audio device, ensure that this is not a devices bitmask
+     * @param caller caller of this method
+     */
+    private void updateVolumeStatesForAudioDevice(int device, String caller) {
         final int numStreamTypes = AudioSystem.getNumStreamTypes();
         for (int streamType = 0; streamType < numStreamTypes; streamType++) {
-            if (!mStreamStates[streamType].hasIndexForDevice(device)) {
-                // set the default value, if device is affected by a full/fix/abs volume rule, it
-                // will taken into account in checkFixedVolumeDevices()
-                mStreamStates[streamType].setIndex(
-                        mStreamStates[mStreamVolumeAlias[streamType]]
-                                .getIndex(AudioSystem.DEVICE_OUT_DEFAULT),
-                        device, caller, true /*hasModifyAudioSettings*/);
-            }
-            mStreamStates[streamType].checkFixedVolumeDevices();
+            updateVolumeStates(device, streamType, caller);
+        }
+    }
 
-            // Unmute streams if device is full volume
-            if (mFullVolumeDevices.contains(device)) {
-                mStreamStates[streamType].mute(false);
+    /**
+     * Update volume states for the given device and given stream.
+     *
+     * This will initialize the volume index if no volume index is available.
+     * If the device is the currently routed device, fixed/full volume policies will be applied.
+     *
+     * @param device a single audio device, ensure that this is not a devices bitmask
+     * @param streamType streamType to be updated
+     * @param caller caller of this method
+     */
+    private void updateVolumeStates(int device, int streamType, String caller) {
+        if (!mStreamStates[streamType].hasIndexForDevice(device)) {
+            // set the default value, if device is affected by a full/fix/abs volume rule, it
+            // will taken into account in checkFixedVolumeDevices()
+            mStreamStates[streamType].setIndex(
+                    mStreamStates[mStreamVolumeAlias[streamType]]
+                            .getIndex(AudioSystem.DEVICE_OUT_DEFAULT),
+                    device, caller, true /*hasModifyAudioSettings*/);
+        }
+
+        // Check if device to be updated is routed for the given audio stream
+        List<AudioDeviceAttributes> devicesForAttributes = getDevicesForAttributesInt(
+                new AudioAttributes.Builder().setInternalLegacyStreamType(streamType).build());
+        for (AudioDeviceAttributes deviceAttributes : devicesForAttributes) {
+            if (deviceAttributes.getType() == AudioDeviceInfo.convertInternalDeviceToDeviceType(
+                    device)) {
+                mStreamStates[streamType].checkFixedVolumeDevices();
+
+                // Unmute streams if required if device is full volume
+                if (isStreamMute(streamType) && mFullVolumeDevices.contains(device)) {
+                    mStreamStates[streamType].mute(false);
+                }
             }
         }
     }
@@ -1777,22 +1809,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,60 +1839,73 @@
     }
 
     /** @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) */
     public @NonNull ArrayList<AudioDeviceAttributes> getDevicesForAttributes(
             @NonNull AudioAttributes attributes) {
-        Objects.requireNonNull(attributes);
         enforceModifyAudioRoutingPermission();
+        return getDevicesForAttributesInt(attributes);
+    }
+
+    protected @NonNull ArrayList<AudioDeviceAttributes> getDevicesForAttributesInt(
+            @NonNull AudioAttributes attributes) {
+        Objects.requireNonNull(attributes);
         return AudioSystem.getDevicesForAttributes(attributes);
     }
 
@@ -4901,7 +4952,15 @@
         synchronized (VolumeStreamState.class) {
             for (int stream = 0; stream < mStreamStates.length; stream++) {
                 if (stream != skipStream) {
-                    mStreamStates[stream].observeDevicesForStream_syncVSS(false /*checkOthers*/);
+                    int devices = mStreamStates[stream].observeDevicesForStream_syncVSS(
+                            false /*checkOthers*/);
+
+                    Set<Integer> devicesSet = AudioSystem.generateAudioDeviceTypesSet(devices);
+                    for (Integer device : devicesSet) {
+                        // Update volume states for devices routed for the stream
+                        updateVolumeStates(device, stream,
+                                "AudioService#observeDevicesForStreams");
+                    }
                 }
             }
         }
@@ -4970,7 +5029,7 @@
                       + Integer.toHexString(audioSystemDeviceOut) + " from:" + caller));
         // make sure we have a volume entry for this device, and that volume is updated according
         // to volume behavior
-        checkAddAllFixedVolumeDevices(audioSystemDeviceOut, "setDeviceVolumeBehavior:" + caller);
+        updateVolumeStatesForAudioDevice(audioSystemDeviceOut, "setDeviceVolumeBehavior:" + caller);
     }
 
     /**
@@ -7192,10 +7251,9 @@
                 // HDMI output
                 removeAudioSystemDeviceOutFromFullVolumeDevices(AudioSystem.DEVICE_OUT_HDMI);
             }
+            updateVolumeStatesForAudioDevice(AudioSystem.DEVICE_OUT_HDMI,
+                    "HdmiPlaybackClient.DisplayStatusCallback");
         }
-
-        checkAddAllFixedVolumeDevices(AudioSystem.DEVICE_OUT_HDMI,
-                "HdmiPlaybackClient.DisplayStatusCallback");
     }
 
     private class MyHdmiControlStatusChangeListenerCallback
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/sensors/AcquisitionClient.java b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
index ab48fdb..e87c8fe 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
@@ -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..47c839c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -191,7 +191,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 +211,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..fffc073 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,15 +175,15 @@
     @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 protected 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) {
             mHandler.post(() -> {
@@ -203,8 +203,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 +227,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(
@@ -262,7 +262,7 @@
             }
 
             final Interruptable interruptable = (Interruptable) currentClient;
-            interruptable.cancelWithoutStarting(mInternalFinishCallback);
+            interruptable.cancelWithoutStarting(mInternalCallback);
             // Now we wait for the client to send its FinishCallback, which kicks off the next
             // operation.
             return;
@@ -280,7 +280,10 @@
         final boolean shouldStartNow = currentClient.getCookie() == 0;
         if (shouldStartNow) {
             Slog.d(getTag(), "[Starting] " + mCurrentOperation);
-            currentClient.start(mInternalFinishCallback);
+            if (mCurrentOperation.mClientCallback != null) {
+                mCurrentOperation.mClientCallback.onClientStarted(currentClient);
+            }
+            currentClient.start(mInternalCallback);
             mCurrentOperation.state = Operation.STATE_STARTED;
         } else {
             try {
@@ -323,8 +326,11 @@
         }
 
         Slog.d(getTag(), "[Starting] Prepared client: " + mCurrentOperation);
+        if (mCurrentOperation.mClientCallback != null) {
+            mCurrentOperation.mClientCallback.onClientStarted(mCurrentOperation.clientMonitor);
+        }
         mCurrentOperation.state = Operation.STATE_STARTED;
-        mCurrentOperation.clientMonitor.start(mInternalFinishCallback);
+        mCurrentOperation.clientMonitor.start(mInternalCallback);
     }
 
     /**
@@ -340,11 +346,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 +362,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..81867b0 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,17 @@
     /**
      * 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;
     }
 
     /**
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..2c60d09 100644
--- a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
@@ -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..2c5b802 100644
--- a/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java
@@ -47,16 +47,16 @@
     }
 
     @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 */);
+            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/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..a4956cb 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
@@ -98,6 +98,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
@@ -394,9 +399,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;
+                }
             }
         });
     }
@@ -459,18 +467,68 @@
     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, "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 +546,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/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/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..20a1807 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
@@ -409,9 +409,12 @@
                 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;
+                }
             }
         });
     }
@@ -453,12 +456,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());
+                    }
                 }
-            }));
+            });
         });
     }
 
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..751df8e 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
@@ -79,7 +79,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 +119,7 @@
             onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
                     0 /* vendorCode */);
             UdfpsHelper.hideUdfpsOverlay(mUdfpsOverlayController);
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 
@@ -132,7 +132,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/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/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/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/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 397358b..dab8c7f 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -30,8 +30,6 @@
 import static android.hardware.display.DisplayViewport.VIEWPORT_EXTERNAL;
 import static android.hardware.display.DisplayViewport.VIEWPORT_INTERNAL;
 import static android.hardware.display.DisplayViewport.VIEWPORT_VIRTUAL;
-import static android.view.Surface.ROTATION_270;
-import static android.view.Surface.ROTATION_90;
 
 import android.Manifest;
 import android.annotation.NonNull;
@@ -46,7 +44,6 @@
 import android.database.ContentObserver;
 import android.graphics.ColorSpace;
 import android.graphics.Point;
-import android.graphics.Rect;
 import android.hardware.SensorManager;
 import android.hardware.display.AmbientBrightnessDayStats;
 import android.hardware.display.BrightnessChangeEvent;
@@ -104,7 +101,6 @@
 import com.android.server.DisplayThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
-import com.android.server.SystemService.TargetUser;
 import com.android.server.UiThread;
 import com.android.server.wm.SurfaceAnimationThread;
 import com.android.server.wm.WindowManagerInternal;
@@ -1394,9 +1390,13 @@
             }
 
             final DisplayInfo displayInfo = logicalDisplay.getDisplayInfoLocked();
-            return SurfaceControl.screenshotToBufferWithSecureLayersUnsafe(token, new Rect(),
-                    displayInfo.getNaturalWidth(), displayInfo.getNaturalHeight(),
-                    false /* useIdentityTransform */, 0 /* rotation */);
+            final SurfaceControl.DisplayCaptureArgs captureArgs =
+                    new SurfaceControl.DisplayCaptureArgs.Builder(token)
+                            .setSize(displayInfo.getNaturalWidth(), displayInfo.getNaturalHeight())
+                            .setUseIdentityTransform(true)
+                            .setCaptureSecureLayers(true)
+                            .build();
+            return SurfaceControl.captureDisplay(captureArgs);
         }
     }
 
@@ -1406,30 +1406,11 @@
             if (token == null) {
                 return null;
             }
-            final LogicalDisplay logicalDisplay = mLogicalDisplays.get(displayId);
-            if (logicalDisplay == null) {
-                return null;
-            }
 
-            final DisplayInfo displayInfo = logicalDisplay.getDisplayInfoLocked();
-            // Takes screenshot based on current device orientation.
-            final Display display = DisplayManagerGlobal.getInstance()
-                    .getRealDisplay(displayId);
-            if (display == null) {
-                return null;
-            }
-            final Point displaySize = new Point();
-            display.getRealSize(displaySize);
-
-            int rotation = displayInfo.rotation;
-            // TODO (b/153382624) : This workaround solution would be removed after
-            // SurfaceFlinger fixes the inconsistency with rotation direction issue.
-            if (rotation == ROTATION_90 || rotation == ROTATION_270) {
-                rotation = (rotation == ROTATION_90) ? ROTATION_270 : ROTATION_90;
-            }
-
-            return SurfaceControl.screenshotToBuffer(token, new Rect(), displaySize.x,
-                    displaySize.y, false /* useIdentityTransform */, rotation /* rotation */);
+            final SurfaceControl.DisplayCaptureArgs captureArgs =
+                    new SurfaceControl.DisplayCaptureArgs.Builder(token)
+                            .build();
+            return SurfaceControl.captureDisplay(captureArgs);
         }
     }
 
diff --git a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
index 7bbcdaa..6672daa 100644
--- a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
+++ b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
@@ -949,7 +949,7 @@
                         if (debug) {
                             Slog.d(mTag, "Eagerly recreating service for user " + userId);
                         }
-                        getServiceForUserLocked(userId);
+                        updateCachedServiceLocked(userId);
                     }
                 }
                 onServicePackageRestartedLocked(userId);
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 d4f8c7e..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() {
@@ -1615,7 +1617,7 @@
                 case LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF:
                     // fall through
                 case LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF:
-                    updateService();
+                    updateRegistrations(registration -> true);
                     break;
                 default:
                     break;
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index 8004ec7..850cf7f 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -49,8 +49,6 @@
 import android.os.Message;
 import android.os.PersistableBundle;
 import android.os.PowerManager;
-import android.os.PowerManager.ServiceType;
-import android.os.PowerSaveState;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
@@ -486,10 +484,6 @@
                         deviceIdleService.unregisterStationaryListener(
                                 mDeviceIdleStationaryListener);
                     }
-                    // Intentional fall-through.
-                case PowerManager.ACTION_POWER_SAVE_MODE_CHANGED:
-                case Intent.ACTION_SCREEN_OFF:
-                case Intent.ACTION_SCREEN_ON:
                     // Call updateLowPowerMode on handler thread so it's always called from the
                     // same thread.
                     mHandler.sendEmptyMessage(UPDATE_LOW_POWER_MODE);
@@ -554,15 +548,6 @@
     private void updateLowPowerMode() {
         // Disable GPS if we are in device idle mode and the device is stationary.
         boolean disableGpsForPowerManager = mPowerManager.isDeviceIdleMode() && mIsDeviceStationary;
-        final PowerSaveState result = mPowerManager.getPowerSaveState(ServiceType.LOCATION);
-        switch (result.locationMode) {
-            case PowerManager.LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF:
-            case PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF:
-                // If we are in battery saver mode and the screen is off, disable GPS.
-                disableGpsForPowerManager |=
-                        result.batterySaverEnabled && !mPowerManager.isInteractive();
-                break;
-        }
         if (disableGpsForPowerManager != mDisableGpsForPowerManager) {
             mDisableGpsForPowerManager = disableGpsForPowerManager;
             updateEnabled();
@@ -1959,10 +1944,7 @@
             IntentFilter intentFilter = new IntentFilter();
             intentFilter.addAction(ALARM_WAKEUP);
             intentFilter.addAction(ALARM_TIMEOUT);
-            intentFilter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
             intentFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
-            intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
-            intentFilter.addAction(Intent.ACTION_SCREEN_ON);
             intentFilter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
             intentFilter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
             mContext.registerReceiver(mBroadcastReceiver, intentFilter, null, this);
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index f1b89c7..d6e37bac 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -33,6 +33,7 @@
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE;
 import static com.android.internal.widget.LockPatternUtils.USER_FRP;
+import static com.android.internal.widget.LockPatternUtils.VERIFY_FLAG_RETURN_GK_PW;
 import static com.android.internal.widget.LockPatternUtils.frpCredentialEnabled;
 import static com.android.internal.widget.LockPatternUtils.userOwnsFrpCredential;
 
@@ -186,6 +187,9 @@
     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)
@@ -1308,7 +1312,7 @@
         try {
             doVerifyCredential(getDecryptedPasswordForTiedProfile(profileHandle),
                     challengeType, challenge, profileHandle, null /* progressCallback */,
-                    resetLockouts);
+                    resetLockouts, 0 /* flags */);
         } catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException
                 | NoSuchAlgorithmException | NoSuchPaddingException
                 | InvalidAlgorithmParameterException | IllegalBlockSizeException
@@ -1608,8 +1612,8 @@
             // Verify the parent credential again, to make sure we have a fresh enough
             // auth token such that getDecryptedPasswordForTiedProfile() inside
             // setLockCredentialInternal() can function correctly.
-            verifyCredential(savedCredential, /* challenge */ 0,
-                    mUserManager.getProfileParent(userId).id);
+            verifyCredential(savedCredential, mUserManager.getProfileParent(userId).id,
+                    0 /* flags */);
             savedCredential.zeroize();
             savedCredential = LockscreenCredential.createNone();
         }
@@ -1724,7 +1728,7 @@
         fixateNewestUserKeyAuth(userId);
         // Refresh the auth token
         doVerifyCredential(credential, CHALLENGE_FROM_CALLER, 0, userId,
-                null /* progressCallback */);
+                null /* progressCallback */, 0 /* flags */);
         synchronizeUnifiedWorkChallengeForProfiles(userId, null);
         sendCredentialsOnChangeIfRequired(credential, userId, isLockTiedToParent);
         return true;
@@ -1835,7 +1839,7 @@
             throw new IllegalArgumentException("Non-OK response verifying a credential we just set "
                     + vcr.getResponseCode());
         }
-        byte[] token = vcr.getPayload();
+        byte[] token = vcr.getGatekeeperHAT();
         if (token == null) {
             throw new IllegalArgumentException("Empty payload verifying a credential we just set");
         }
@@ -1968,35 +1972,56 @@
             ICheckCredentialProgressCallback progressCallback) {
         checkPasswordReadPermission(userId);
         try {
-            return doVerifyCredential(credential, CHALLENGE_NONE, 0, userId, progressCallback);
+            return doVerifyCredential(credential, CHALLENGE_NONE, 0L, userId, progressCallback,
+                    0 /* flags */);
         } finally {
             scheduleGc();
         }
     }
 
     @Override
+    @Nullable
     public VerifyCredentialResponse verifyCredential(LockscreenCredential credential,
-            long challenge, int userId) {
+            int userId, int flags) {
         checkPasswordReadPermission(userId);
-        @ChallengeType int challengeType = CHALLENGE_FROM_CALLER;
-        if (challenge == 0) {
-            Slog.w(TAG, "VerifyCredential called with challenge=0");
-            challengeType = CHALLENGE_NONE;
 
-        }
         try {
-            return doVerifyCredential(credential, challengeType, challenge, userId,
-                    null /* progressCallback */);
+            return doVerifyCredential(credential, CHALLENGE_NONE, 0L, userId,
+                    null /* progressCallback */, flags);
         } finally {
             scheduleGc();
         }
     }
 
+    @Override
+    public VerifyCredentialResponse verifyGatekeeperPassword(byte[] gatekeeperPassword,
+            long challenge, int userId) {
+        checkPasswordReadPermission(userId);
+
+        VerifyCredentialResponse response;
+        synchronized (mSpManager) {
+            response = mSpManager.verifyChallengeInternal(getGateKeeperService(),
+                    gatekeeperPassword, challenge, userId);
+        }
+        return response;
+    }
+
+    /**
+     * @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) {
+            ICheckCredentialProgressCallback progressCallback,
+            @LockPatternUtils.VerifyFlag int flags) {
         return doVerifyCredential(credential, challengeType, challenge, userId,
-                progressCallback, null /* resetLockouts */);
+                progressCallback, null /* resetLockouts */, flags);
     }
 
     /**
@@ -2006,7 +2031,8 @@
     private VerifyCredentialResponse doVerifyCredential(LockscreenCredential credential,
             @ChallengeType int challengeType, long challenge, int userId,
             ICheckCredentialProgressCallback progressCallback,
-            @Nullable ArrayList<PendingResetLockout> resetLockouts) {
+            @Nullable ArrayList<PendingResetLockout> resetLockouts,
+            @LockPatternUtils.VerifyFlag int flags) {
         if (credential == null || credential.isNone()) {
             throw new IllegalArgumentException("Credential can't be null or empty");
         }
@@ -2017,7 +2043,7 @@
         }
         VerifyCredentialResponse response = null;
         response = spBasedDoVerifyCredential(credential, challengeType, challenge,
-                userId, progressCallback, resetLockouts);
+                userId, progressCallback, resetLockouts, flags);
         // The user employs synthetic password based credential.
         if (response != null) {
             if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
@@ -2050,7 +2076,7 @@
 
     @Override
     public VerifyCredentialResponse verifyTiedProfileChallenge(LockscreenCredential credential,
-            long challenge, int userId) {
+            int userId, @LockPatternUtils.VerifyFlag int flags) {
         checkPasswordReadPermission(userId);
         if (!isManagedProfileWithUnifiedLock(userId)) {
             throw new IllegalArgumentException("User id must be managed profile with unified lock");
@@ -2059,10 +2085,11 @@
         // Unlock parent by using parent's challenge
         final VerifyCredentialResponse parentResponse = doVerifyCredential(
                 credential,
-                CHALLENGE_FROM_CALLER,
-                challenge,
+                CHALLENGE_NONE,
+                0L,
                 parentProfileId,
-                null /* progressCallback */);
+                null /* progressCallback */,
+                flags);
         if (parentResponse.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) {
             // Failed, just return parent's response
             return parentResponse;
@@ -2071,9 +2098,10 @@
         try {
             // Unlock work profile, and work profile with unified lock must use password only
             return doVerifyCredential(getDecryptedPasswordForTiedProfile(userId),
-                    CHALLENGE_FROM_CALLER,
-                    challenge,
-                    userId, null /* progressCallback */);
+                    CHALLENGE_NONE,
+                    0L,
+                    userId, null /* progressCallback */,
+                    flags);
         } catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException
                 | NoSuchAlgorithmException | NoSuchPaddingException
                 | InvalidAlgorithmParameterException | IllegalBlockSizeException
@@ -2132,8 +2160,8 @@
             unlockKeystore(credential.getCredential(), userId);
 
             Slog.i(TAG, "Unlocking user " + userId + " with token length "
-                    + response.getPayload().length);
-            unlockUser(userId, response.getPayload(), secretFromCredential(credential));
+                    + response.getGatekeeperHAT().length);
+            unlockUser(userId, response.getGatekeeperHAT(), secretFromCredential(credential));
 
             if (isManagedProfileWithSeparatedLock(userId)) {
                 setDeviceUnlockedForUser(userId);
@@ -2684,7 +2712,8 @@
     private VerifyCredentialResponse spBasedDoVerifyCredential(LockscreenCredential userCredential,
             @ChallengeType int challengeType, long challenge,
             int userId, ICheckCredentialProgressCallback progressCallback,
-            @Nullable ArrayList<PendingResetLockout> resetLockouts) {
+            @Nullable ArrayList<PendingResetLockout> resetLockouts,
+            @LockPatternUtils.VerifyFlag int flags) {
 
         final boolean hasEnrolledBiometrics = mInjector.hasEnrolledBiometrics(userId);
 
@@ -2705,6 +2734,8 @@
 
         final AuthenticationResult authResult;
         VerifyCredentialResponse response;
+        final boolean returnGkPw = (flags & VERIFY_FLAG_RETURN_GK_PW) != 0;
+
         synchronized (mSpManager) {
             if (!isSyntheticPasswordBasedCredentialLocked(userId)) {
                 return null;
@@ -2717,8 +2748,8 @@
             long handle = getSyntheticPasswordHandleLocked(userId);
             authResult = mSpManager.unwrapPasswordBasedSyntheticPassword(
                     getGateKeeperService(), handle, userCredential, userId, progressCallback);
-
             response = authResult.gkResponse;
+
             // credential has matched
             if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
                 // perform verifyChallenge with synthetic password which generates the real GK auth
@@ -2739,7 +2770,7 @@
                 if (resetLockouts == null) {
                     resetLockouts = new ArrayList<>();
                 }
-                resetLockouts.add(new PendingResetLockout(userId, response.getPayload()));
+                resetLockouts.add(new PendingResetLockout(userId, response.getGatekeeperHAT()));
             }
 
             onCredentialVerified(authResult.authToken, challengeType, challenge, resetLockouts,
@@ -2750,7 +2781,12 @@
             }
         }
 
-        return response;
+        if (response.isMatched() && returnGkPw) {
+            return new VerifyCredentialResponse.Builder()
+                    .setGatekeeperPassword(authResult.authToken.deriveGkPassword()).build();
+        } else {
+            return response;
+        }
     }
 
     private void onCredentialVerified(AuthenticationToken authToken,
@@ -3172,7 +3208,8 @@
             if (cred == null) {
                 return false;
             }
-            return doVerifyCredential(cred, CHALLENGE_NONE, 0, userId, null /* progressCallback */)
+            return doVerifyCredential(cred, CHALLENGE_NONE, 0, userId,
+                    null /* progressCallback */, 0 /* flags */)
                     .getResponseCode() == VerifyCredentialResponse.RESPONSE_OK;
         }
     }
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/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index d644b1d..e31bf3d 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -482,11 +482,12 @@
                     (int status, WeaverReadResponse readResponse) -> {
                     switch (status) {
                         case WeaverReadStatus.OK:
-                            response[0] = new VerifyCredentialResponse(
-                                    fromByteArrayList(readResponse.value));
+                            response[0] = new VerifyCredentialResponse.Builder().setGatekeeperHAT(
+                                    fromByteArrayList(readResponse.value)).build();
                             break;
                         case WeaverReadStatus.THROTTLE:
-                            response[0] = new VerifyCredentialResponse(readResponse.timeout);
+                            response[0] = VerifyCredentialResponse
+                                    .fromTimeout(readResponse.timeout);
                             Slog.e(TAG, "weaver read failed (THROTTLE), slot: " + slot);
                             break;
                         case WeaverReadStatus.INCORRECT_KEY:
@@ -494,7 +495,8 @@
                                 response[0] = VerifyCredentialResponse.ERROR;
                                 Slog.e(TAG, "weaver read failed (INCORRECT_KEY), slot: " + slot);
                             } else {
-                                response[0] = new VerifyCredentialResponse(readResponse.timeout);
+                                response[0] = VerifyCredentialResponse
+                                        .fromTimeout(readResponse.timeout);
                                 Slog.e(TAG, "weaver read failed (INCORRECT_KEY/THROTTLE), slot: "
                                         + slot);
                             }
@@ -1007,7 +1009,8 @@
                 return result;
             }
             sid = GateKeeper.INVALID_SECURE_USER_ID;
-            applicationId = transformUnderWeaverSecret(pwdToken, result.gkResponse.getPayload());
+            applicationId = transformUnderWeaverSecret(pwdToken,
+                    result.gkResponse.getGatekeeperHAT());
         } else {
             byte[] gkPwdToken = passwordTokenToGkInput(pwdToken);
             GateKeeperResponse response;
@@ -1045,7 +1048,7 @@
                     }
                 }
             } else if (responseCode == GateKeeperResponse.RESPONSE_RETRY) {
-                result.gkResponse = new VerifyCredentialResponse(response.getTimeout());
+                result.gkResponse = VerifyCredentialResponse.fromTimeout(response.getTimeout());
                 return result;
             } else  {
                 result.gkResponse = VerifyCredentialResponse.ERROR;
@@ -1096,12 +1099,12 @@
             }
             VerifyCredentialResponse response = weaverVerify(slotId, null);
             if (response.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK ||
-                    response.getPayload() == null) {
+                    response.getGatekeeperHAT() == null) {
                 Slog.e(TAG, "Failed to retrieve weaver secret when unwrapping token");
                 result.gkResponse = VerifyCredentialResponse.ERROR;
                 return result;
             }
-            secdiscardable = SyntheticPasswordCrypto.decrypt(response.getPayload(),
+            secdiscardable = SyntheticPasswordCrypto.decrypt(response.getGatekeeperHAT(),
                     PERSONALISATION_WEAVER_TOKEN, secdiscardable);
         }
         byte[] applicationId = transformUnderSecdiscardable(token, secdiscardable);
@@ -1174,6 +1177,12 @@
      */
     public @Nullable VerifyCredentialResponse verifyChallenge(IGateKeeperService gatekeeper,
             @NonNull AuthenticationToken auth, long challenge, int userId) {
+        return verifyChallengeInternal(gatekeeper, auth.deriveGkPassword(), challenge, userId);
+    }
+
+    protected @Nullable VerifyCredentialResponse verifyChallengeInternal(
+            IGateKeeperService gatekeeper, @NonNull byte[] gatekeeperPassword, long challenge,
+            int userId) {
         byte[] spHandle = loadSyntheticPasswordHandle(userId);
         if (spHandle == null) {
             // There is no password handle associated with the given user, i.e. the user is not
@@ -1183,18 +1192,19 @@
         GateKeeperResponse response;
         try {
             response = gatekeeper.verifyChallenge(userId, challenge,
-                    spHandle, auth.deriveGkPassword());
+                    spHandle, gatekeeperPassword);
         } catch (RemoteException e) {
             Slog.e(TAG, "Fail to verify with gatekeeper " + userId, e);
             return VerifyCredentialResponse.ERROR;
         }
         int responseCode = response.getResponseCode();
         if (responseCode == GateKeeperResponse.RESPONSE_OK) {
-            VerifyCredentialResponse result = new VerifyCredentialResponse(response.getPayload());
+            VerifyCredentialResponse result = new VerifyCredentialResponse.Builder()
+                    .setGatekeeperHAT(response.getPayload()).build();
             if (response.getShouldReEnroll()) {
                 try {
                     response = gatekeeper.enroll(userId, spHandle, spHandle,
-                            auth.deriveGkPassword());
+                            gatekeeperPassword);
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Failed to invoke gatekeeper.enroll", e);
                     response = GateKeeperResponse.ERROR;
@@ -1203,7 +1213,8 @@
                     spHandle = response.getPayload();
                     saveSyntheticPasswordHandle(spHandle, userId);
                     // Call self again to re-verify with updated handle
-                    return verifyChallenge(gatekeeper, auth, challenge, userId);
+                    return verifyChallengeInternal(gatekeeper, gatekeeperPassword, challenge,
+                            userId);
                 } else {
                     // Fall through, return result from the previous verification attempt.
                     Slog.w(TAG, "Fail to re-enroll SP handle for user " + userId);
@@ -1211,7 +1222,7 @@
             }
             return result;
         } else if (responseCode == GateKeeperResponse.RESPONSE_RETRY) {
-            return new VerifyCredentialResponse(response.getTimeout());
+            return VerifyCredentialResponse.fromTimeout(response.getTimeout());
         } else {
             return VerifyCredentialResponse.ERROR;
         }
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 e2f70e3..a8a0e2e 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";
@@ -1937,7 +1927,8 @@
                 // Context#getPackageName() for getting package name that matches with the PID/UID,
                 // but it doesn't tell which package has created the MediaController, so useless.
                 return hasMediaControlPermission(controllerPid, controllerUid)
-                        || hasEnabledNotificationListener(userId, controllerPackageName, uid);
+                        || hasEnabledNotificationListener(
+                                userId, controllerPackageName, controllerUid);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -2001,21 +1992,21 @@
             return resolvedUserId;
         }
 
-        private boolean hasEnabledNotificationListener(int resolvedUserId, String packageName,
-                int uid) {
-            // TODO: revisit this checking code
-            // You may not access another user's content as an enabled listener.
-            final int userId = UserHandle.getUserHandleForUid(resolvedUserId).getIdentifier();
-            if (resolvedUserId != userId) {
+        private boolean hasEnabledNotificationListener(int callingUserId,
+                String controllerPackageName, int controllerUid) {
+            int controllerUserId = UserHandle.getUserHandleForUid(controllerUid).getIdentifier();
+            if (callingUserId != controllerUserId) {
+                // Enabled notification listener only works within the same user.
                 return false;
             }
-            if (mNotificationManager.hasEnabledNotificationListener(packageName,
-                    UserHandle.getUserHandleForUid(uid))) {
+
+            if (mNotificationManager.hasEnabledNotificationListener(controllerPackageName,
+                    UserHandle.getUserHandleForUid(controllerUid))) {
                 return true;
             }
             if (DEBUG) {
-                Log.d(TAG, packageName + " (uid=" + uid + ") doesn't have an enabled "
-                        + "notification listener");
+                Log.d(TAG, controllerPackageName + " (uid=" + controllerUid
+                        + ") doesn't have an enabled notification listener");
             }
             return false;
         }
@@ -2709,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/net/NetworkPolicyLogger.java b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
index 3bd18f9..006d78e 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyLogger.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
@@ -70,9 +70,9 @@
 
     static final int NTWK_BLOCKED_POWER = 0;
     static final int NTWK_ALLOWED_NON_METERED = 1;
-    static final int NTWK_BLOCKED_BLACKLIST = 2;
-    static final int NTWK_ALLOWED_WHITELIST = 3;
-    static final int NTWK_ALLOWED_TMP_WHITELIST = 4;
+    static final int NTWK_BLOCKED_DENYLIST = 2;
+    static final int NTWK_ALLOWED_ALLOWLIST = 3;
+    static final int NTWK_ALLOWED_TMP_ALLOWLIST = 4;
     static final int NTWK_BLOCKED_BG_RESTRICT = 5;
     static final int NTWK_ALLOWED_DEFAULT = 6;
     static final int NTWK_ALLOWED_SYSTEM = 7;
@@ -269,12 +269,12 @@
                 return "blocked by power restrictions";
             case NTWK_ALLOWED_NON_METERED:
                 return "allowed on unmetered network";
-            case NTWK_BLOCKED_BLACKLIST:
-                return "blacklisted on metered network";
-            case NTWK_ALLOWED_WHITELIST:
-                return "whitelisted on metered network";
-            case NTWK_ALLOWED_TMP_WHITELIST:
-                return "temporary whitelisted on metered network";
+            case NTWK_BLOCKED_DENYLIST:
+                return "denylisted on metered network";
+            case NTWK_ALLOWED_ALLOWLIST:
+                return "allowlisted on metered network";
+            case NTWK_ALLOWED_TMP_ALLOWLIST:
+                return "temporary allowlisted on metered network";
             case NTWK_BLOCKED_BG_RESTRICT:
                 return "blocked when background is restricted";
             case NTWK_ALLOWED_DEFAULT:
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index d6557f6..295143e 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -101,13 +101,13 @@
 import static com.android.internal.util.XmlUtils.writeLongAttribute;
 import static com.android.internal.util.XmlUtils.writeStringAttribute;
 import static com.android.server.NetworkManagementService.LIMIT_GLOBAL_ALERT;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_ALLOWLIST;
 import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_DEFAULT;
 import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_NON_METERED;
 import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_SYSTEM;
-import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_TMP_WHITELIST;
-import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_WHITELIST;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_TMP_ALLOWLIST;
 import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_BG_RESTRICT;
-import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_BLACKLIST;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_DENYLIST;
 import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_POWER;
 import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_UPDATED;
 
@@ -507,7 +507,7 @@
      * UIDs that have been initially white-listed by system to avoid restricted background.
      */
     @GuardedBy("mUidRulesFirstLock")
-    private final SparseBooleanArray mDefaultRestrictBackgroundWhitelistUids =
+    private final SparseBooleanArray mDefaultRestrictBackgroundAllowlistUids =
             new SparseBooleanArray();
 
     /**
@@ -515,7 +515,7 @@
      * but later revoked by user.
      */
     @GuardedBy("mUidRulesFirstLock")
-    private final SparseBooleanArray mRestrictBackgroundWhitelistRevokedUids =
+    private final SparseBooleanArray mRestrictBackgroundAllowlistRevokedUids =
             new SparseBooleanArray();
 
     /** Set of ifaces that are metered. */
@@ -582,7 +582,7 @@
     @GuardedBy("mUidRulesFirstLock")
     private final SparseBooleanArray mInternetPermissionMap = new SparseBooleanArray();
 
-    // TODO: keep whitelist of system-critical services that should never have
+    // TODO: keep allowlist of system-critical services that should never have
     // rules enforced, such as system, phone, and radio UIDs.
 
     // TODO: migrate notifications to SystemUI
@@ -668,26 +668,26 @@
     }
 
     /**
-     * Whitelists pre-defined apps for restrict background, but only if the user didn't already
-     * revoke the whitelist.
+     * Allows pre-defined apps for restrict background, but only if the user didn't already
+     * revoked them.
      *
-     * @return whether any uid has been whitelisted.
+     * @return whether any uid has been allowlisted.
      */
     @GuardedBy("mUidRulesFirstLock")
-    boolean addDefaultRestrictBackgroundWhitelistUidsUL() {
+    boolean addDefaultRestrictBackgroundAllowlistUidsUL() {
         final List<UserInfo> users = mUserManager.getUsers();
         final int numberUsers = users.size();
 
         boolean changed = false;
         for (int i = 0; i < numberUsers; i++) {
             final UserInfo user = users.get(i);
-            changed = addDefaultRestrictBackgroundWhitelistUidsUL(user.id) || changed;
+            changed = addDefaultRestrictBackgroundAllowlistUidsUL(user.id) || changed;
         }
         return changed;
     }
 
     @GuardedBy("mUidRulesFirstLock")
-    private boolean addDefaultRestrictBackgroundWhitelistUidsUL(int userId) {
+    private boolean addDefaultRestrictBackgroundAllowlistUidsUL(int userId) {
         final SystemConfig sysConfig = SystemConfig.getInstance();
         final PackageManager pm = mContext.getPackageManager();
         final ArraySet<String> allowDataUsage = sysConfig.getAllowInDataUsageSave();
@@ -695,7 +695,7 @@
         for (int i = 0; i < allowDataUsage.size(); i++) {
             final String pkg = allowDataUsage.valueAt(i);
             if (LOGD)
-                Slog.d(TAG, "checking restricted background whitelisting for package " + pkg
+                Slog.d(TAG, "checking restricted background allowlisting for package " + pkg
                         + " and user " + userId);
             final ApplicationInfo app;
             try {
@@ -706,20 +706,20 @@
                 continue;
             }
             if (!app.isPrivilegedApp()) {
-                Slog.e(TAG, "addDefaultRestrictBackgroundWhitelistUidsUL(): "
+                Slog.e(TAG, "addDefaultRestrictBackgroundAllowlistUidsUL(): "
                         + "skipping non-privileged app  " + pkg);
                 continue;
             }
             final int uid = UserHandle.getUid(userId, app.uid);
-            mDefaultRestrictBackgroundWhitelistUids.append(uid, true);
+            mDefaultRestrictBackgroundAllowlistUids.append(uid, true);
             if (LOGD)
                 Slog.d(TAG, "Adding uid " + uid + " (user " + userId + ") to default restricted "
-                        + "background whitelist. Revoked status: "
-                        + mRestrictBackgroundWhitelistRevokedUids.get(uid));
-            if (!mRestrictBackgroundWhitelistRevokedUids.get(uid)) {
+                        + "background allowlist. Revoked status: "
+                        + mRestrictBackgroundAllowlistRevokedUids.get(uid));
+            if (!mRestrictBackgroundAllowlistRevokedUids.get(uid)) {
                 if (LOGD)
                     Slog.d(TAG, "adding default package " + pkg + " (uid " + uid + " for user "
-                            + userId + ") to restrict background whitelist");
+                            + userId + ") to restrict background allowlist");
                 setUidPolicyUncheckedUL(uid, POLICY_ALLOW_METERED_BACKGROUND, false);
                 changed = true;
             }
@@ -799,7 +799,7 @@
                                 }
                             });
 
-                    if (addDefaultRestrictBackgroundWhitelistUidsUL()) {
+                    if (addDefaultRestrictBackgroundAllowlistUidsUL()) {
                         writePolicyAL();
                     }
 
@@ -1005,14 +1005,14 @@
                 case ACTION_USER_ADDED:
                     synchronized (mUidRulesFirstLock) {
                         // Remove any persistable state for the given user; both cleaning up after a
-                        // USER_REMOVED, and one last sanity check during USER_ADDED
+                        // USER_REMOVED, and one last check during USER_ADDED
                         removeUserStateUL(userId, true, false);
                         // Removing outside removeUserStateUL since that can also be called when
                         // user resets app preferences.
                         mMeteredRestrictedUids.remove(userId);
                         if (action == ACTION_USER_ADDED) {
-                            // Add apps that are whitelisted by default.
-                            addDefaultRestrictBackgroundWhitelistUidsUL(userId);
+                            // Add apps that are allowlisted by default.
+                            addDefaultRestrictBackgroundAllowlistUidsUL(userId);
                         }
                         // Update global restrict for that user
                         synchronized (mNetworkPoliciesSecondLock) {
@@ -2196,12 +2196,12 @@
             in.setInput(fis, StandardCharsets.UTF_8.name());
 
              // Must save the <restrict-background> tags and convert them to <uid-policy> later,
-             // to skip UIDs that were explicitly blacklisted.
-            final SparseBooleanArray whitelistedRestrictBackground = new SparseBooleanArray();
+             // to skip UIDs that were explicitly denylisted.
+            final SparseBooleanArray allowlistedRestrictBackground = new SparseBooleanArray();
 
             int type;
             int version = VERSION_INIT;
-            boolean insideWhitelist = false;
+            boolean insideAllowlist = false;
             while ((type = in.next()) != END_DOCUMENT) {
                 final String tag = in.getName();
                 if (type == START_TAG) {
@@ -2340,28 +2340,28 @@
                             Slog.w(TAG, "unable to apply policy to UID " + uid + "; ignoring");
                         }
                     } else if (TAG_WHITELIST.equals(tag)) {
-                        insideWhitelist = true;
-                    } else if (TAG_RESTRICT_BACKGROUND.equals(tag) && insideWhitelist) {
+                        insideAllowlist = true;
+                    } else if (TAG_RESTRICT_BACKGROUND.equals(tag) && insideAllowlist) {
                         final int uid = readIntAttribute(in, ATTR_UID);
-                        whitelistedRestrictBackground.append(uid, true);
-                    } else if (TAG_REVOKED_RESTRICT_BACKGROUND.equals(tag) && insideWhitelist) {
+                        allowlistedRestrictBackground.append(uid, true);
+                    } else if (TAG_REVOKED_RESTRICT_BACKGROUND.equals(tag) && insideAllowlist) {
                         final int uid = readIntAttribute(in, ATTR_UID);
-                        mRestrictBackgroundWhitelistRevokedUids.put(uid, true);
+                        mRestrictBackgroundAllowlistRevokedUids.put(uid, true);
                     }
                 } else if (type == END_TAG) {
                     if (TAG_WHITELIST.equals(tag)) {
-                        insideWhitelist = false;
+                        insideAllowlist = false;
                     }
 
                 }
             }
 
-            final int size = whitelistedRestrictBackground.size();
+            final int size = allowlistedRestrictBackground.size();
             for (int i = 0; i < size; i++) {
-                final int uid = whitelistedRestrictBackground.keyAt(i);
+                final int uid = allowlistedRestrictBackground.keyAt(i);
                 final int policy = mUidPolicy.get(uid, POLICY_NONE);
                 if ((policy & POLICY_REJECT_METERED_BACKGROUND) != 0) {
-                    Slog.w(TAG, "ignoring restrict-background-whitelist for " + uid
+                    Slog.w(TAG, "ignoring restrict-background-allowlist for " + uid
                             + " because its policy is " + uidPoliciesToString(policy));
                     continue;
                 }
@@ -2533,13 +2533,13 @@
 
             out.endTag(null, TAG_POLICY_LIST);
 
-            // write all whitelists
+            // write all allowlists
             out.startTag(null, TAG_WHITELIST);
 
-            // revoked restrict background whitelist
-            int size = mRestrictBackgroundWhitelistRevokedUids.size();
+            // revoked restrict background allowlist
+            int size = mRestrictBackgroundAllowlistRevokedUids.size();
             for (int i = 0; i < size; i++) {
-                final int uid = mRestrictBackgroundWhitelistRevokedUids.keyAt(i);
+                final int uid = mRestrictBackgroundAllowlistRevokedUids.keyAt(i);
                 out.startTag(null, TAG_REVOKED_RESTRICT_BACKGROUND);
                 writeIntAttribute(out, ATTR_UID, uid);
                 out.endTag(null, TAG_REVOKED_RESTRICT_BACKGROUND);
@@ -2619,21 +2619,21 @@
         setUidPolicyUncheckedUL(uid, policy, false);
 
         final boolean notifyApp;
-        if (!isUidValidForWhitelistRulesUL(uid)) {
+        if (!isUidValidForAllowlistRulesUL(uid)) {
             notifyApp = false;
         } else {
-            final boolean wasBlacklisted = oldPolicy == POLICY_REJECT_METERED_BACKGROUND;
-            final boolean isBlacklisted = policy == POLICY_REJECT_METERED_BACKGROUND;
-            final boolean wasWhitelisted = oldPolicy == POLICY_ALLOW_METERED_BACKGROUND;
-            final boolean isWhitelisted = policy == POLICY_ALLOW_METERED_BACKGROUND;
-            final boolean wasBlocked = wasBlacklisted || (mRestrictBackground && !wasWhitelisted);
-            final boolean isBlocked = isBlacklisted || (mRestrictBackground && !isWhitelisted);
-            if ((wasWhitelisted && (!isWhitelisted || isBlacklisted))
-                    && mDefaultRestrictBackgroundWhitelistUids.get(uid)
-                    && !mRestrictBackgroundWhitelistRevokedUids.get(uid)) {
+            final boolean wasDenylisted = oldPolicy == POLICY_REJECT_METERED_BACKGROUND;
+            final boolean isDenylisted = policy == POLICY_REJECT_METERED_BACKGROUND;
+            final boolean wasAllowlisted = oldPolicy == POLICY_ALLOW_METERED_BACKGROUND;
+            final boolean isAllowlisted = policy == POLICY_ALLOW_METERED_BACKGROUND;
+            final boolean wasBlocked = wasDenylisted || (mRestrictBackground && !wasAllowlisted);
+            final boolean isBlocked = isDenylisted || (mRestrictBackground && !isAllowlisted);
+            if ((wasAllowlisted && (!isAllowlisted || isDenylisted))
+                    && mDefaultRestrictBackgroundAllowlistUids.get(uid)
+                    && !mRestrictBackgroundAllowlistRevokedUids.get(uid)) {
                 if (LOGD)
-                    Slog.d(TAG, "Adding uid " + uid + " to revoked restrict background whitelist");
-                mRestrictBackgroundWhitelistRevokedUids.append(uid, true);
+                    Slog.d(TAG, "Adding uid " + uid + " to revoked restrict background allowlist");
+                mRestrictBackgroundAllowlistRevokedUids.append(uid, true);
             }
             notifyApp = wasBlocked != isBlocked;
         }
@@ -2700,11 +2700,11 @@
         mLogger.removingUserState(userId);
         boolean changed = false;
 
-        // Remove entries from revoked default restricted background UID whitelist
-        for (int i = mRestrictBackgroundWhitelistRevokedUids.size() - 1; i >= 0; i--) {
-            final int uid = mRestrictBackgroundWhitelistRevokedUids.keyAt(i);
+        // Remove entries from revoked default restricted background UID allowlist
+        for (int i = mRestrictBackgroundAllowlistRevokedUids.size() - 1; i >= 0; i--) {
+            final int uid = mRestrictBackgroundAllowlistRevokedUids.keyAt(i);
             if (UserHandle.getUserId(uid) == userId) {
-                mRestrictBackgroundWhitelistRevokedUids.removeAt(i);
+                mRestrictBackgroundAllowlistRevokedUids.removeAt(i);
                 changed = true;
             }
         }
@@ -2913,7 +2913,7 @@
             Slog.d(TAG, "setRestrictBackgroundUL(): " + restrictBackground + "; reason: " + reason);
             final boolean oldRestrictBackground = mRestrictBackground;
             mRestrictBackground = restrictBackground;
-            // Must whitelist foreground apps before turning data saver mode on.
+            // Must allowlist foreground apps before turning data saver mode on.
             // TODO: there is no need to iterate through all apps here, just those in the foreground,
             // so it could call AM to get the UIDs of such apps, and iterate through them instead.
             updateRulesForRestrictBackgroundUL();
@@ -2966,7 +2966,7 @@
                 Binder.restoreCallingIdentity(token);
             }
             if (policy == POLICY_REJECT_METERED_BACKGROUND) {
-                // App is blacklisted.
+                // App is denylisted.
                 return RESTRICT_BACKGROUND_STATUS_ENABLED;
             }
             if (!mRestrictBackground) {
@@ -3543,25 +3543,25 @@
                     fout.decreaseIndent();
                 }
 
-                size = mDefaultRestrictBackgroundWhitelistUids.size();
+                size = mDefaultRestrictBackgroundAllowlistUids.size();
                 if (size > 0) {
-                    fout.println("Default restrict background whitelist uids:");
+                    fout.println("Default restrict background allowlist uids:");
                     fout.increaseIndent();
                     for (int i = 0; i < size; i++) {
                         fout.print("UID=");
-                        fout.print(mDefaultRestrictBackgroundWhitelistUids.keyAt(i));
+                        fout.print(mDefaultRestrictBackgroundAllowlistUids.keyAt(i));
                         fout.println();
                     }
                     fout.decreaseIndent();
                 }
 
-                size = mRestrictBackgroundWhitelistRevokedUids.size();
+                size = mRestrictBackgroundAllowlistRevokedUids.size();
                 if (size > 0) {
-                    fout.println("Default restrict background whitelist uids revoked by users:");
+                    fout.println("Default restrict background allowlist uids revoked by users:");
                     fout.increaseIndent();
                     for (int i = 0; i < size; i++) {
                         fout.print("UID=");
-                        fout.print(mRestrictBackgroundWhitelistRevokedUids.keyAt(i));
+                        fout.print(mRestrictBackgroundAllowlistRevokedUids.keyAt(i));
                         fout.println();
                     }
                     fout.decreaseIndent();
@@ -3882,7 +3882,7 @@
 
     @GuardedBy("mUidRulesFirstLock")
     void updateRuleForAppIdleUL(int uid) {
-        if (!isUidValidForBlacklistRulesUL(uid)) return;
+        if (!isUidValidForDenylistRulesUL(uid)) return;
 
         if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) {
             Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRuleForAppIdleUL: " + uid );
@@ -3915,13 +3915,13 @@
         final SparseIntArray blockedUids = new SparseIntArray();
         for (int i = 0; i < ruleCount; i++) {
             final int uid = mUidFirewallStandbyRules.keyAt(i);
-            if (!isUidValidForBlacklistRulesUL(uid)) {
+            if (!isUidValidForDenylistRulesUL(uid)) {
                 continue;
             }
             int oldRules = mUidRules.get(uid);
             if (enableChain) {
                 // Chain wasn't enabled before and the other power-related
-                // chains are whitelists, so we can clear the
+                // chains are allowlists, so we can clear the
                 // MASK_ALL_NETWORKS part of the rules and re-inform listeners if
                 // the effective rules result in blocking network access.
                 oldRules &= MASK_METERED_NETWORKS;
@@ -4079,10 +4079,10 @@
     // TODO: the MEDIA / DRM restriction might not be needed anymore, in which case both
     // methods below could be merged into a isUidValidForRules() method.
     @GuardedBy("mUidRulesFirstLock")
-    private boolean isUidValidForBlacklistRulesUL(int uid) {
+    private boolean isUidValidForDenylistRulesUL(int uid) {
         // allow rules on specific system services, and any apps
         if (uid == android.os.Process.MEDIA_UID || uid == android.os.Process.DRM_UID
-                || isUidValidForWhitelistRulesUL(uid)) {
+                || isUidValidForAllowlistRulesUL(uid)) {
             return true;
         }
 
@@ -4090,7 +4090,7 @@
     }
 
     @GuardedBy("mUidRulesFirstLock")
-    private boolean isUidValidForWhitelistRulesUL(int uid) {
+    private boolean isUidValidForAllowlistRulesUL(int uid) {
         return UserHandle.isApp(uid) && hasInternetPermissionUL(uid);
     }
 
@@ -4235,23 +4235,23 @@
 
     /**
      * Applies network rules to bandwidth controllers based on process state and user-defined
-     * restrictions (blacklist / whitelist).
+     * restrictions (allowlist / denylist).
      *
      * <p>
      * {@code netd} defines 3 firewall chains that govern whether an app has access to metered
      * networks:
      * <ul>
-     * <li>@{code bw_penalty_box}: UIDs added to this chain do not have access (blacklist).
-     * <li>@{code bw_happy_box}: UIDs added to this chain have access (whitelist), unless they're
-     *     also blacklisted.
+     * <li>@{code bw_penalty_box}: UIDs added to this chain do not have access (denylist).
+     * <li>@{code bw_happy_box}: UIDs added to this chain have access (allowlist), unless they're
+     *     also denylisted.
      * <li>@{code bw_data_saver}: when enabled (through {@link #setRestrictBackground(boolean)}),
-     *     no UIDs other than those whitelisted will have access.
+     *     no UIDs other than those allowlisted will have access.
      * <ul>
      *
      * <p>The @{code bw_penalty_box} and @{code bw_happy_box} are primarily managed through the
-     * {@link #setUidPolicy(int, int)} and {@link #addRestrictBackgroundWhitelistedUid(int)} /
-     * {@link #removeRestrictBackgroundWhitelistedUid(int)} methods (for blacklist and whitelist
-     * respectively): these methods set the proper internal state (blacklist / whitelist), then call
+     * {@link #setUidPolicy(int, int)} and {@link #addRestrictBackgroundAllowlistedUid(int)} /
+     * {@link #removeRestrictBackgroundDenylistedUid(int)} methods (for denylist and allowlist
+     * respectively): these methods set the proper internal state (denylist / allowlist), then call
      * this ({@link #updateRulesForDataUsageRestrictionsUL(int)}) to propagate the rules to
      * {@link INetworkManagementService}, but this method should also be called in events (like
      * Data Saver Mode flips or UID state changes) that might affect the foreground app, since the
@@ -4260,7 +4260,7 @@
      * <ul>
      * <li>When Data Saver mode is on, the foreground app should be temporarily added to
      *     {@code bw_happy_box} before the @{code bw_data_saver} chain is enabled.
-     * <li>If the foreground app is blacklisted by the user, it should be temporarily removed from
+     * <li>If the foreground app is denylisted by the user, it should be temporarily removed from
      *     {@code bw_penalty_box}.
      * <li>When the app leaves foreground state, the temporary changes above should be reverted.
      * </ul>
@@ -4285,7 +4285,7 @@
     }
 
     private void updateRulesForDataUsageRestrictionsULInner(int uid) {
-        if (!isUidValidForWhitelistRulesUL(uid)) {
+        if (!isUidValidForAllowlistRulesUL(uid)) {
             if (LOGD) Slog.d(TAG, "no need to update restrict data rules for uid " + uid);
             return;
         }
@@ -4295,8 +4295,8 @@
         final boolean isForeground = isUidForegroundOnRestrictBackgroundUL(uid);
         final boolean isRestrictedByAdmin = isRestrictedByAdminUL(uid);
 
-        final boolean isBlacklisted = (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0;
-        final boolean isWhitelisted = (uidPolicy & POLICY_ALLOW_METERED_BACKGROUND) != 0;
+        final boolean isDenylisted = (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0;
+        final boolean isAllowlisted = (uidPolicy & POLICY_ALLOW_METERED_BACKGROUND) != 0;
         final int oldRule = oldUidRules & MASK_METERED_NETWORKS;
         int newRule = RULE_NONE;
 
@@ -4304,15 +4304,15 @@
         if (isRestrictedByAdmin) {
             newRule = RULE_REJECT_METERED;
         } else if (isForeground) {
-            if (isBlacklisted || (mRestrictBackground && !isWhitelisted)) {
+            if (isDenylisted || (mRestrictBackground && !isAllowlisted)) {
                 newRule = RULE_TEMPORARY_ALLOW_METERED;
-            } else if (isWhitelisted) {
+            } else if (isAllowlisted) {
                 newRule = RULE_ALLOW_METERED;
             }
         } else {
-            if (isBlacklisted) {
+            if (isDenylisted) {
                 newRule = RULE_REJECT_METERED;
-            } else if (mRestrictBackground && isWhitelisted) {
+            } else if (mRestrictBackground && isAllowlisted) {
                 newRule = RULE_ALLOW_METERED;
             }
         }
@@ -4321,8 +4321,8 @@
         if (LOGV) {
             Log.v(TAG, "updateRuleForRestrictBackgroundUL(" + uid + ")"
                     + ": isForeground=" +isForeground
-                    + ", isBlacklisted=" + isBlacklisted
-                    + ", isWhitelisted=" + isWhitelisted
+                    + ", isDenylisted=" + isDenylisted
+                    + ", isAllowlisted=" + isAllowlisted
                     + ", isRestrictedByAdmin=" + isRestrictedByAdmin
                     + ", oldRule=" + uidRulesToString(oldRule)
                     + ", newRule=" + uidRulesToString(newRule)
@@ -4339,49 +4339,49 @@
         // Second step: apply bw changes based on change of state.
         if (newRule != oldRule) {
             if (hasRule(newRule, RULE_TEMPORARY_ALLOW_METERED)) {
-                // Temporarily whitelist foreground app, removing from blacklist if necessary
+                // Temporarily allowlist foreground app, removing from denylist if necessary
                 // (since bw_penalty_box prevails over bw_happy_box).
 
-                setMeteredNetworkWhitelist(uid, true);
+                setMeteredNetworkAllowlist(uid, true);
                 // TODO: if statement below is used to avoid an unnecessary call to netd / iptables,
                 // but ideally it should be just:
-                //    setMeteredNetworkBlacklist(uid, isBlacklisted);
-                if (isBlacklisted) {
-                    setMeteredNetworkBlacklist(uid, false);
+                //    setMeteredNetworkDenylist(uid, isDenylisted);
+                if (isDenylisted) {
+                    setMeteredNetworkDenylist(uid, false);
                 }
             } else if (hasRule(oldRule, RULE_TEMPORARY_ALLOW_METERED)) {
-                // Remove temporary whitelist from app that is not on foreground anymore.
+                // Remove temporary allowlist from app that is not on foreground anymore.
 
                 // TODO: if statements below are used to avoid unnecessary calls to netd / iptables,
                 // but ideally they should be just:
-                //    setMeteredNetworkWhitelist(uid, isWhitelisted);
-                //    setMeteredNetworkBlacklist(uid, isBlacklisted);
-                if (!isWhitelisted) {
-                    setMeteredNetworkWhitelist(uid, false);
+                //    setMeteredNetworkAllowlist(uid, isAllowlisted);
+                //    setMeteredNetworkDenylist(uid, isDenylisted);
+                if (!isAllowlisted) {
+                    setMeteredNetworkAllowlist(uid, false);
                 }
-                if (isBlacklisted || isRestrictedByAdmin) {
-                    setMeteredNetworkBlacklist(uid, true);
+                if (isDenylisted || isRestrictedByAdmin) {
+                    setMeteredNetworkDenylist(uid, true);
                 }
             } else if (hasRule(newRule, RULE_REJECT_METERED)
                     || hasRule(oldRule, RULE_REJECT_METERED)) {
-                // Flip state because app was explicitly added or removed to blacklist.
-                setMeteredNetworkBlacklist(uid, (isBlacklisted || isRestrictedByAdmin));
-                if (hasRule(oldRule, RULE_REJECT_METERED) && isWhitelisted) {
-                    // Since blacklist prevails over whitelist, we need to handle the special case
-                    // where app is whitelisted and blacklisted at the same time (although such
-                    // scenario should be blocked by the UI), then blacklist is removed.
-                    setMeteredNetworkWhitelist(uid, isWhitelisted);
+                // 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
+                    // 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);
                 }
             } else if (hasRule(newRule, RULE_ALLOW_METERED)
                     || hasRule(oldRule, RULE_ALLOW_METERED)) {
-                // Flip state because app was explicitly added or removed to whitelist.
-                setMeteredNetworkWhitelist(uid, isWhitelisted);
+                // Flip state because app was explicitly added or removed to allowlist.
+                setMeteredNetworkAllowlist(uid, isAllowlisted);
             } else {
                 // All scenarios should have been covered above.
                 Log.wtf(TAG, "Unexpected change of metered UID state for " + uid
                         + ": foreground=" + isForeground
-                        + ", whitelisted=" + isWhitelisted
-                        + ", blacklisted=" + isBlacklisted
+                        + ", allowlisted=" + isAllowlisted
+                        + ", denylisted=" + isDenylisted
                         + ", isRestrictedByAdmin=" + isRestrictedByAdmin
                         + ", newRule=" + uidRulesToString(newUidRules)
                         + ", oldRule=" + uidRulesToString(oldUidRules));
@@ -4397,7 +4397,7 @@
      * listeners in case of change.
      * <p>
      * There are 3 power-related rules that affects whether an app has background access on
-     * non-metered networks, and when the condition applies and the UID is not whitelisted for power
+     * non-metered networks, and when the condition applies and the UID is not allowlisted for power
      * restriction, it's added to the equivalent firewall chain:
      * <ul>
      * <li>App is idle: {@code fw_standby} firewall chain.
@@ -4406,7 +4406,7 @@
      * </ul>
      * <p>
      * This method updates the power-related part of the {@link #mUidRules} for a given uid based on
-     * these modes, the UID process state (foreground or not), and the UIDwhitelist state.
+     * these modes, the UID process state (foreground or not), and the UID allowlist state.
      * <p>
      * <strong>NOTE: </strong>This method does not update the firewall rules on {@code netd}.
      */
@@ -4450,7 +4450,7 @@
     @GuardedBy("mUidRulesFirstLock")
     private int updateRulesForPowerRestrictionsULInner(int uid, int oldUidRules,
             boolean isUidIdle) {
-        if (!isUidValidForBlacklistRulesUL(uid)) {
+        if (!isUidValidForDenylistRulesUL(uid)) {
             if (LOGD) Slog.d(TAG, "no need to update restrict power rules for uid " + uid);
             return RULE_NONE;
         }
@@ -4859,23 +4859,23 @@
         }
     }
 
-    private void setMeteredNetworkBlacklist(int uid, boolean enable) {
-        if (LOGV) Slog.v(TAG, "setMeteredNetworkBlacklist " + uid + ": " + enable);
+    private void setMeteredNetworkDenylist(int uid, boolean enable) {
+        if (LOGV) Slog.v(TAG, "setMeteredNetworkDenylist " + uid + ": " + enable);
         try {
-            mNetworkManager.setUidMeteredNetworkBlacklist(uid, enable);
+            mNetworkManager.setUidMeteredNetworkDenylist(uid, enable);
         } catch (IllegalStateException e) {
-            Log.wtf(TAG, "problem setting blacklist (" + enable + ") rules for " + uid, e);
+            Log.wtf(TAG, "problem setting denylist (" + enable + ") rules for " + uid, e);
         } catch (RemoteException e) {
             // ignored; service lives in system_server
         }
     }
 
-    private void setMeteredNetworkWhitelist(int uid, boolean enable) {
-        if (LOGV) Slog.v(TAG, "setMeteredNetworkWhitelist " + uid + ": " + enable);
+    private void setMeteredNetworkAllowlist(int uid, boolean enable) {
+        if (LOGV) Slog.v(TAG, "setMeteredNetworkAllowlist " + uid + ": " + enable);
         try {
-            mNetworkManager.setUidMeteredNetworkWhitelist(uid, enable);
+            mNetworkManager.setUidMeteredNetworkAllowlist(uid, enable);
         } catch (IllegalStateException e) {
-            Log.wtf(TAG, "problem setting whitelist (" + enable + ") rules for " + uid, e);
+            Log.wtf(TAG, "problem setting allowlist (" + enable + ") rules for " + uid, e);
         } catch (RemoteException e) {
             // ignored; service lives in system_server
         }
@@ -4936,7 +4936,7 @@
     }
 
     /**
-     * Add or remove a uid to the firewall blacklist for all network ifaces.
+     * Add or remove a uid to the firewall denylist for all network ifaces.
      */
     private void setUidFirewallRule(int chain, int uid, int rule) {
         if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) {
@@ -4966,7 +4966,7 @@
     }
 
     /**
-     * Add or remove a uid to the firewall blacklist for all network ifaces.
+     * Add or remove a uid to the firewall denylist for all network ifaces.
      */
     @GuardedBy("mUidRulesFirstLock")
     private void enableFirewallChainUL(int chain, boolean enable) {
@@ -4995,8 +4995,8 @@
             mNetworkManager.setFirewallUidRule(FIREWALL_CHAIN_STANDBY, uid, FIREWALL_RULE_DEFAULT);
             mNetworkManager
                     .setFirewallUidRule(FIREWALL_CHAIN_POWERSAVE, uid, FIREWALL_RULE_DEFAULT);
-            mNetworkManager.setUidMeteredNetworkWhitelist(uid, false);
-            mNetworkManager.setUidMeteredNetworkBlacklist(uid, false);
+            mNetworkManager.setUidMeteredNetworkAllowlist(uid, false);
+            mNetworkManager.setUidMeteredNetworkDenylist(uid, false);
         } catch (IllegalStateException e) {
             Log.wtf(TAG, "problem resetting firewall uid rules for " + uid, e);
         } catch (RemoteException e) {
@@ -5189,13 +5189,13 @@
             reason = NTWK_ALLOWED_NON_METERED;
         }
         else if (hasRule(uidRules, RULE_REJECT_METERED)) {
-            reason = NTWK_BLOCKED_BLACKLIST;
+            reason = NTWK_BLOCKED_DENYLIST;
         }
         else if (hasRule(uidRules, RULE_ALLOW_METERED)) {
-            reason = NTWK_ALLOWED_WHITELIST;
+            reason = NTWK_ALLOWED_ALLOWLIST;
         }
         else if (hasRule(uidRules, RULE_TEMPORARY_ALLOW_METERED)) {
-            reason = NTWK_ALLOWED_TMP_WHITELIST;
+            reason = NTWK_ALLOWED_TMP_ALLOWLIST;
         }
         else if (isBackgroundRestricted) {
             reason = NTWK_BLOCKED_BG_RESTRICT;
@@ -5208,13 +5208,13 @@
         switch(reason) {
             case NTWK_ALLOWED_DEFAULT:
             case NTWK_ALLOWED_NON_METERED:
-            case NTWK_ALLOWED_TMP_WHITELIST:
-            case NTWK_ALLOWED_WHITELIST:
+            case NTWK_ALLOWED_TMP_ALLOWLIST:
+            case NTWK_ALLOWED_ALLOWLIST:
             case NTWK_ALLOWED_SYSTEM:
                 blocked = false;
                 break;
             case NTWK_BLOCKED_POWER:
-            case NTWK_BLOCKED_BLACKLIST:
+            case NTWK_BLOCKED_DENYLIST:
             case NTWK_BLOCKED_BG_RESTRICT:
                 blocked = true;
                 break;
@@ -5234,7 +5234,7 @@
         public void resetUserState(int userId) {
             synchronized (mUidRulesFirstLock) {
                 boolean changed = removeUserStateUL(userId, false, true);
-                changed = addDefaultRestrictBackgroundWhitelistUidsUL(userId) || changed;
+                changed = addDefaultRestrictBackgroundAllowlistUidsUL(userId) || changed;
                 if (changed) {
                     synchronized (mNetworkPoliciesSecondLock) {
                         writePolicyAL();
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/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 840645e..8af7463 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)) {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index ff9edd5..bc090f0 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);
@@ -3185,6 +3136,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 a874f612..b96aaf4 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2232,7 +2232,7 @@
                     sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
                             extras, 0 /*flags*/,
                             installerPackageName, null /*finishedReceiver*/,
-                            updateUserIds, instantUserIds, null /* broadcastWhitelist */);
+                            updateUserIds, instantUserIds, null /* broadcastAllowList */);
                 }
                 // if the required verifier is defined, but, is not the installer of record
                 // for the package, it gets notified
@@ -2242,7 +2242,7 @@
                     sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
                             extras, 0 /*flags*/,
                             mRequiredVerifierPackage, null /*finishedReceiver*/,
-                            updateUserIds, instantUserIds, null /* broadcastWhitelist */);
+                            updateUserIds, instantUserIds, null /* broadcastAllowList */);
                 }
                 // If package installer is defined, notify package installer about new
                 // app installed
@@ -2250,7 +2250,7 @@
                     sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
                             extras, Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND /*flags*/,
                             mRequiredInstallerPackage, null /*finishedReceiver*/,
-                            firstUserIds, instantUserIds, null /* broadcastWhitelist */);
+                            firstUserIds, instantUserIds, null /* broadcastAllowList */);
                 }
 
                 // Send replaced for users that don't see the package for the first time
@@ -2263,19 +2263,19 @@
                         sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName,
                                 extras, 0 /*flags*/,
                                 installerPackageName, null /*finishedReceiver*/,
-                                updateUserIds, instantUserIds, null /*broadcastWhitelist*/);
+                                updateUserIds, instantUserIds, null /*broadcastAllowList*/);
                     }
                     if (notifyVerifier) {
                         sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName,
                                 extras, 0 /*flags*/,
                                 mRequiredVerifierPackage, null /*finishedReceiver*/,
-                                updateUserIds, instantUserIds, null /*broadcastWhitelist*/);
+                                updateUserIds, instantUserIds, null /*broadcastAllowList*/);
                     }
                     sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED,
                             null /*package*/, null /*extras*/, 0 /*flags*/,
                             packageName /*targetPackage*/,
                             null /*finishedReceiver*/, updateUserIds, instantUserIds,
-                            null /*broadcastWhitelist*/);
+                            null /*broadcastAllowList*/);
                 } else if (launchedForRestore && !res.pkg.isSystem()) {
                     // First-install and we did a restore, so we're responsible for the
                     // first-launch broadcast.
@@ -14627,7 +14627,7 @@
     private void sendFirstLaunchBroadcast(String pkgName, String installerPkg,
             int[] userIds, int[] instantUserIds) {
         sendPackageBroadcast(Intent.ACTION_PACKAGE_FIRST_LAUNCH, pkgName, null, 0,
-                installerPkg, null, userIds, instantUserIds, null /* broadcastWhitelist */);
+                installerPkg, null, userIds, instantUserIds, null /* broadcastAllowList */);
     }
 
     private abstract class HandlerParams {
@@ -17591,9 +17591,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 +17598,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) {
@@ -18730,14 +18727,14 @@
             packageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, removedPackage,
                     extras, 0, null /*targetPackage*/, null, null, null, broadcastAllowList);
             packageSender.sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, null, null, 0,
-                    removedPackage, null, null, null, null /* broadcastWhitelist */);
+                    removedPackage, null, null, null, null /* broadcastAllowList */);
             if (installerPackageName != null) {
                 packageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
                         removedPackage, extras, 0 /*flags*/,
-                        installerPackageName, null, null, null, null /* broadcastWhitelist */);
+                        installerPackageName, null, null, null, null /* broadcastAllowList */);
                 packageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED,
                         removedPackage, extras, 0 /*flags*/,
-                        installerPackageName, null, null, null, null /* broadcastWhitelist */);
+                        installerPackageName, null, null, null, null /* broadcastAllowList */);
             }
         }
 
@@ -25700,9 +25697,9 @@
      * @param instantUserIds User IDs where the action occurred on an instant application
      */
     void sendPackageBroadcast(final String action, final String pkg,
-        final Bundle extras, final int flags, final String targetPkg,
-        final IIntentReceiver finishedReceiver, final int[] userIds, int[] instantUserIds,
-        @Nullable SparseArray<int[]> broadcastWhitelist);
+            final Bundle extras, final int flags, final String targetPkg,
+            final IIntentReceiver finishedReceiver, final int[] userIds, int[] instantUserIds,
+            @Nullable SparseArray<int[]> broadcastAllowList);
     void sendPackageAddedForNewUsers(String packageName, boolean sendBootCompleted,
             boolean includeStopped, int appId, int[] userIds, int[] instantUserIds,
             int dataLoaderType);
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 3e3e3c5..3e25367 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -4647,6 +4647,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..ac05aab 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;
@@ -650,6 +652,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");
@@ -829,6 +832,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 bf9506a..d137fd0 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -87,6 +87,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
+import android.util.IndentingPrintWriter;
 import android.util.IntArray;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -103,7 +104,6 @@
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.FrameworkStatsLog;
-import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
 import com.android.internal.widget.LockPatternUtils;
@@ -141,6 +141,7 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * Service for {@link UserManager}.
@@ -443,6 +444,11 @@
         }
     };
 
+    // TODO(b/161915546): remove once userWithName() is fixed / removed
+    // Use to debug / dump when user 0 is allocated at userWithName()
+    public static final boolean DBG_ALLOCATION = false; // DO NOT SUBMIT WITH TRUE
+    public final AtomicInteger mUser0Allocations;
+
     /**
      * Start an {@link IntentSender} when user is unlocked after disabling quiet mode.
      *
@@ -493,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();
@@ -629,6 +638,7 @@
         LocalServices.addService(UserManagerInternal.class, mLocalService);
         mLockPatternUtils = new LockPatternUtils(mContext);
         mUserStates.put(UserHandle.USER_SYSTEM, UserState.STATE_BOOTING);
+        mUser0Allocations = DBG_ALLOCATION ? new AtomicInteger() : null;
     }
 
     void systemReady() {
@@ -1310,6 +1320,10 @@
      */
     private UserInfo userWithName(UserInfo orig) {
         if (orig != null && orig.name == null && orig.id == UserHandle.USER_SYSTEM) {
+            if (DBG_ALLOCATION) {
+                final int number = mUser0Allocations.incrementAndGet();
+                Slog.w(LOG_TAG, "System user instantiated at least " + number + " times");
+            }
             UserInfo withName = new UserInfo(orig);
             withName.name = getOwnerName();
             return withName;
@@ -3550,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)) {
@@ -4786,6 +4807,7 @@
                 }
             }
 
+            pw.println();
             pw.println("Device properties:");
             pw.println("  Device owner id:" + mDeviceOwnerUserId);
             pw.println();
@@ -4814,12 +4836,9 @@
                 }
                 pw.println(']');
             }
-
             synchronized (mUsersLock) {
-                pw.println();
-                pw.print("Cached user IDs: ");
+                pw.print("  Cached user IDs: ");
                 pw.println(Arrays.toString(mUserIds));
-                pw.println();
             }
 
         } // synchronized (mPackagesLock)
@@ -4835,6 +4854,10 @@
         pw.println("  Is split-system user: " + UserManager.isSplitSystemUser());
         pw.println("  Is headless-system mode: " + UserManager.isHeadlessSystemUserMode());
         pw.println("  User version: " + mUserVersion);
+        pw.println("  Owner name: " + getOwnerName());
+        if (DBG_ALLOCATION) {
+            pw.println("  System user allocations: " + mUser0Allocations.get());
+        }
 
         // Dump UserTypes
         pw.println();
@@ -4844,9 +4867,17 @@
             mUserTypes.valueAt(i).dump(pw, "        ");
         }
 
-        // Dump package whitelist
-        pw.println();
-        mSystemPackageInstaller.dump(pw);
+        // TODO: create IndentingPrintWriter at the beginning of dump() and use the proper
+        // indentation methods instead of explicit printing "  "
+        try (IndentingPrintWriter ipw = new IndentingPrintWriter(pw)) {
+
+            // Dump SystemPackageInstaller info
+            ipw.println();
+            mSystemPackageInstaller.dump(ipw);
+
+            // NOTE: pw's not available after this point as it's auto-closed by ipw, so new dump
+            // statements should use ipw below
+        }
     }
 
     private static void dumpTimeAgo(PrintWriter pw, StringBuilder sb, long nowTime, long time) {
@@ -5204,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/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 3c1d189..f7e9e34 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -139,6 +139,11 @@
             UserManager.DISALLOW_CONFIG_PRIVATE_DNS
     });
 
+    public static final Set<String> DEPRECATED_USER_RESTRICTIONS = Sets.newArraySet(
+            UserManager.DISALLOW_ADD_MANAGED_PROFILE,
+            UserManager.DISALLOW_REMOVE_MANAGED_PROFILE
+    );
+
     /**
      * Set of user restriction which we don't want to persist.
      */
diff --git a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
index 492b84a..b95404f 100644
--- a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
+++ b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
@@ -28,15 +28,14 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.DebugUtils;
+import android.util.IndentingPrintWriter;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.LocalServices;
 import com.android.server.SystemConfig;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 
-import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -723,13 +722,7 @@
         return userTypeList;
     }
 
-    void dump(PrintWriter pw) {
-        try (IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "    ")) {
-            dumpIndented(ipw);
-        }
-    }
-
-    private void dumpIndented(IndentingPrintWriter pw) {
+    void dump(IndentingPrintWriter pw) {
         final int mode = getWhitelistMode();
         pw.println("Whitelisted packages per user type");
 
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 03771be..3d15704 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -83,6 +83,7 @@
 import android.content.pm.ParceledListSlice;
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
+import android.content.pm.UserInfo;
 import android.content.pm.parsing.component.ParsedPermission;
 import android.content.pm.parsing.component.ParsedPermissionGroup;
 import android.content.pm.permission.SplitPermissionInfoParcelable;
@@ -120,6 +121,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
+import android.util.TimingsTraceLog;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -879,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;
@@ -2521,12 +2523,12 @@
 
         final PermissionsState permissionsState = ps.getPermissionsState();
 
-        final int[] currentUserIds = UserManagerService.getInstance().getUserIds();
+        final int[] userIds = getAllUserIds();
 
         boolean runtimePermissionsRevoked = false;
         int[] updatedUserIds = EMPTY_INT_ARRAY;
 
-        for (int userId : currentUserIds) {
+        for (int userId : userIds) {
             if (permissionsState.isMissing(userId)) {
                 Collection<String> requestedPermissions;
                 int targetSdkVersion;
@@ -2592,7 +2594,7 @@
                 // runtime and revocation of a runtime from a shared user.
                 synchronized (mLock) {
                     updatedUserIds = revokeUnusedSharedUserPermissionsLocked(ps.getSharedUser(),
-                            currentUserIds);
+                            userIds);
                     if (!ArrayUtils.isEmpty(updatedUserIds)) {
                         runtimePermissionsRevoked = true;
                     }
@@ -2747,7 +2749,7 @@
                             // a runtime permission being downgraded to an install one.
                             // Also in permission review mode we keep dangerous permissions
                             // for legacy apps
-                            for (int userId : currentUserIds) {
+                            for (int userId : userIds) {
                                 if (origPermissions.getRuntimePermissionState(
                                         perm, userId) != null) {
                                     // Revoke the runtime permission and clear the flags.
@@ -2770,7 +2772,7 @@
                             boolean hardRestricted = bp.isHardRestricted();
                             boolean softRestricted = bp.isSoftRestricted();
 
-                            for (int userId : currentUserIds) {
+                            for (int userId : userIds) {
                                 // If permission policy is not ready we don't deal with restricted
                                 // permissions as the policy may whitelist some permissions. Once
                                 // the policy is initialized we would re-evaluate permissions.
@@ -2909,7 +2911,7 @@
                             boolean hardRestricted = bp.isHardRestricted();
                             boolean softRestricted = bp.isSoftRestricted();
 
-                            for (int userId : currentUserIds) {
+                            for (int userId : userIds) {
                                 // If permission policy is not ready we don't deal with restricted
                                 // permissions as the policy may whitelist some permissions. Once
                                 // the policy is initialized we would re-evaluate permissions.
@@ -3061,10 +3063,10 @@
 
         synchronized (mLock) {
             updatedUserIds = revokePermissionsNoLongerImplicitLocked(permissionsState, pkg,
-                    currentUserIds, updatedUserIds);
+                    userIds, updatedUserIds);
             updatedUserIds = setInitialGrantForNewImplicitPermissionsLocked(origPermissions,
-                    permissionsState, pkg, newImplicitPermissions, currentUserIds, updatedUserIds);
-            updatedUserIds = checkIfLegacyStorageOpsNeedToBeUpdated(pkg, replace, currentUserIds,
+                    permissionsState, pkg, newImplicitPermissions, userIds, updatedUserIds);
+            updatedUserIds = checkIfLegacyStorageOpsNeedToBeUpdated(pkg, replace, userIds,
                     updatedUserIds);
         }
 
@@ -3081,6 +3083,25 @@
     }
 
     /**
+     * Returns all relevant user ids.  This list include the current set of created user ids as well
+     * as pre-created user ids.
+     * @return user ids for created users and pre-created users
+     */
+    private int[] getAllUserIds() {
+        final TimingsTraceLog t = new TimingsTraceLog(TAG, Trace.TRACE_TAG_SYSTEM_SERVER);
+        t.traceBegin("getAllUserIds");
+        List<UserInfo> users = UserManagerService.getInstance().getUsers(
+                /*excludePartial=*/ true, /*excludeDying=*/ true, /*excludePreCreated=*/ false);
+        int size = users.size();
+        final int[] userIds = new int[size];
+        for (int i = 0; i < size; i++) {
+            userIds[i] = users.get(i).id;
+        }
+        t.traceEnd();
+        return userIds;
+    }
+
+    /**
      * Revoke permissions that are not implicit anymore and that have
      * {@link PackageManager#FLAG_PERMISSION_REVOKE_WHEN_REQUESTED} set.
      *
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/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index fa3bcb6..548cd70 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -450,7 +450,6 @@
     volatile int mPendingWakeKey = PENDING_KEY_NULL;
 
     int mRecentAppsHeldModifiers;
-    boolean mLanguageSwitchKeyPressed;
 
     int mCameraLensCoverState = CAMERA_LENS_COVER_ABSENT;
     boolean mHaveBuiltInKeyboard;
@@ -2938,12 +2937,6 @@
             mWindowManagerFuncs.switchKeyboardLayout(event.getDeviceId(), direction);
             return -1;
         }
-        if (mLanguageSwitchKeyPressed && !down
-                && (keyCode == KeyEvent.KEYCODE_LANGUAGE_SWITCH
-                        || keyCode == KeyEvent.KEYCODE_SPACE)) {
-            mLanguageSwitchKeyPressed = false;
-            return -1;
-        }
 
         if (isValidGlobalKey(keyCode)
                 && mGlobalKeyManager.handleGlobalKey(mContext, keyCode, event)) {
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/slice/SliceManagerService.java b/services/core/java/com/android/server/slice/SliceManagerService.java
index 2a74b3d..5aedfc1 100644
--- a/services/core/java/com/android/server/slice/SliceManagerService.java
+++ b/services/core/java/com/android/server/slice/SliceManagerService.java
@@ -28,8 +28,6 @@
 import android.Manifest.permission;
 import android.annotation.NonNull;
 import android.app.AppOpsManager;
-import android.app.role.OnRoleHoldersChangedListener;
-import android.app.role.RoleManager;
 import android.app.slice.ISliceManager;
 import android.app.slice.SliceSpec;
 import android.app.usage.UsageStatsManagerInternal;
@@ -41,9 +39,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
 import android.content.pm.ProviderInfo;
-import android.content.pm.ResolveInfo;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Handler;
@@ -65,7 +61,6 @@
 import com.android.server.LocalServices;
 import com.android.server.ServiceThread;
 import com.android.server.SystemService;
-import com.android.server.SystemService.TargetUser;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -77,10 +72,7 @@
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.List;
 import java.util.Objects;
-import java.util.concurrent.Executor;
-import java.util.function.Supplier;
 
 public class SliceManagerService extends ISliceManager.Stub {
 
@@ -88,16 +80,13 @@
     private final Object mLock = new Object();
 
     private final Context mContext;
-    private final PackageManagerInternal mPackageManagerInternal;
     private final AppOpsManager mAppOps;
     private final AssistUtils mAssistUtils;
 
     @GuardedBy("mLock")
     private final ArrayMap<Uri, PinnedSliceState> mPinnedSlicesByUri = new ArrayMap<>();
     @GuardedBy("mLock")
-    private final SparseArray<PackageMatchingCache> mAssistantLookup = new SparseArray<>();
-    @GuardedBy("mLock")
-    private final SparseArray<PackageMatchingCache> mHomeLookup = new SparseArray<>();
+    private final SparseArray<String> mLastAssistantPackage = new SparseArray<>();
     private final Handler mHandler;
 
     private final SlicePermissionManager mPermissions;
@@ -110,8 +99,6 @@
     @VisibleForTesting
     SliceManagerService(Context context, Looper looper) {
         mContext = context;
-        mPackageManagerInternal = Objects.requireNonNull(
-                LocalServices.getService(PackageManagerInternal.class));
         mAppOps = context.getSystemService(AppOpsManager.class);
         mAssistUtils = new AssistUtils(context);
         mHandler = new Handler(looper);
@@ -124,7 +111,6 @@
         filter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED);
         filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
         filter.addDataScheme("package");
-        mRoleObserver = new RoleObserver();
         mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, mHandler);
     }
 
@@ -174,8 +160,7 @@
         mHandler.post(() -> {
             if (slicePkg != null && !Objects.equals(pkg, slicePkg)) {
                 mAppUsageStats.reportEvent(slicePkg, user,
-                        isAssistant(pkg, user) || isDefaultHomeApp(pkg, user)
-                                ? SLICE_PINNED_PRIV : SLICE_PINNED);
+                        isAssistant(pkg, user) ? SLICE_PINNED_PRIV : SLICE_PINNED);
             }
         });
     }
@@ -440,38 +425,19 @@
     private boolean hasFullSliceAccess(String pkg, int userId) {
         long ident = Binder.clearCallingIdentity();
         try {
-            boolean ret = isDefaultHomeApp(pkg, userId) || isAssistant(pkg, userId)
-                    || isGrantedFullAccess(pkg, userId);
-            return ret;
+            return isAssistant(pkg, userId) || isGrantedFullAccess(pkg, userId);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
     }
 
     private boolean isAssistant(String pkg, int userId) {
-        return getAssistantMatcher(userId).matches(pkg);
-    }
-
-    private boolean isDefaultHomeApp(String pkg, int userId) {
-        return getHomeMatcher(userId).matches(pkg);
-    }
-
-    private PackageMatchingCache getAssistantMatcher(int userId) {
-        PackageMatchingCache matcher = mAssistantLookup.get(userId);
-        if (matcher == null) {
-            matcher = new PackageMatchingCache(() -> getAssistant(userId));
-            mAssistantLookup.put(userId, matcher);
+        if (pkg == null) return false;
+        if (!pkg.equals(mLastAssistantPackage.get(userId))) {
+            // Failed on cached value, try updating.
+            mLastAssistantPackage.put(userId, getAssistant(userId));
         }
-        return matcher;
-    }
-
-    private PackageMatchingCache getHomeMatcher(int userId) {
-        PackageMatchingCache matcher = mHomeLookup.get(userId);
-        if (matcher == null) {
-            matcher = new PackageMatchingCache(() -> getDefaultHome(userId));
-            mHomeLookup.put(userId, matcher);
-        }
-        return matcher;
+        return pkg.equals(mLastAssistantPackage.get(userId));
     }
 
     private String getAssistant(int userId) {
@@ -482,111 +448,6 @@
         return cn.getPackageName();
     }
 
-    /**
-     * A cached value of the default home app
-     */
-    private String mCachedDefaultHome = null;
-
-    // Based on getDefaultHome in ShortcutService.
-    // TODO: Unify if possible
-    @VisibleForTesting
-    protected String getDefaultHome(int userId) {
-
-        // Set VERIFY to true to run the cache in "shadow" mode for cache
-        // testing.  Do not commit set to true;
-        final boolean VERIFY = false;
-
-        if (mCachedDefaultHome != null) {
-            if (!VERIFY) {
-                return mCachedDefaultHome;
-            }
-        }
-
-        final long token = Binder.clearCallingIdentity();
-        try {
-            final List<ResolveInfo> allHomeCandidates = new ArrayList<>();
-
-            // Default launcher from package manager.
-            final ComponentName defaultLauncher = mPackageManagerInternal
-                    .getHomeActivitiesAsUser(allHomeCandidates, userId);
-
-            ComponentName detected = defaultLauncher;
-
-            // Cache the default launcher.  It is not a problem if the
-            // launcher is null - eventually, the default launcher will be
-            // set to something non-null.
-            mCachedDefaultHome = ((detected != null) ? detected.getPackageName() : null);
-
-            if (detected == null) {
-                // If we reach here, that means it's the first check since the user was created,
-                // and there's already multiple launchers and there's no default set.
-                // Find the system one with the highest priority.
-                // (We need to check the priority too because of FallbackHome in Settings.)
-                // If there's no system launcher yet, then no one can access slices, until
-                // the user explicitly sets one.
-                final int size = allHomeCandidates.size();
-
-                int lastPriority = Integer.MIN_VALUE;
-                for (int i = 0; i < size; i++) {
-                    final ResolveInfo ri = allHomeCandidates.get(i);
-                    if (!ri.activityInfo.applicationInfo.isSystemApp()) {
-                        continue;
-                    }
-                    if (ri.priority < lastPriority) {
-                        continue;
-                    }
-                    detected = ri.activityInfo.getComponentName();
-                    lastPriority = ri.priority;
-                }
-            }
-            final String ret = ((detected != null) ? detected.getPackageName() : null);
-            if (VERIFY) {
-                if (mCachedDefaultHome != null && !mCachedDefaultHome.equals(ret)) {
-                    Slog.e(TAG, "getDefaultHome() cache failure, is " +
-                           mCachedDefaultHome + " should be " + ret);
-                }
-            }
-            return ret;
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
-    public void invalidateCachedDefaultHome() {
-        mCachedDefaultHome = null;
-    }
-
-    /**
-     * Listen for changes in the roles, and invalidate the cached default
-     * home as necessary.
-     */
-    private RoleObserver mRoleObserver;
-
-    class RoleObserver implements OnRoleHoldersChangedListener {
-        private RoleManager mRm;
-        private final Executor mExecutor;
-
-        RoleObserver() {
-            mExecutor = mContext.getMainExecutor();
-            register();
-        }
-
-        public void register() {
-            mRm = mContext.getSystemService(RoleManager.class);
-            if (mRm != null) {
-                mRm.addOnRoleHoldersChangedListenerAsUser(mExecutor, this, UserHandle.ALL);
-                invalidateCachedDefaultHome();
-            }
-        }
-
-        @Override
-        public void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user) {
-            if (RoleManager.ROLE_HOME.equals(roleName)) {
-                invalidateCachedDefaultHome();
-            }
-        }
-    }
-
     private boolean isGrantedFullAccess(String pkg, int userId) {
         return mPermissions.hasFullAccess(pkg, userId);
     }
@@ -635,30 +496,6 @@
         return mPermissions.getAllPackagesGranted(pkg);
     }
 
-    /**
-     * Holder that caches a package that has access to a slice.
-     */
-    static class PackageMatchingCache {
-
-        private final Supplier<String> mPkgSource;
-        private String mCurrentPkg;
-
-        public PackageMatchingCache(Supplier<String> pkgSource) {
-            mPkgSource = pkgSource;
-        }
-
-        public boolean matches(String pkgCandidate) {
-            if (pkgCandidate == null) return false;
-
-            if (Objects.equals(pkgCandidate, mCurrentPkg)) {
-                return true;
-            }
-            // Failed on cached value, try updating.
-            mCurrentPkg = mPkgSource.get();
-            return Objects.equals(pkgCandidate, mCurrentPkg);
-        }
-    }
-
     public static class Lifecycle extends SystemService {
         private SliceManagerService mService;
 
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
index c2b0d106..0ca36e0 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
@@ -72,6 +72,10 @@
             builder.setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED);
         }
 
+        // TODO(b/149014708) Replace this with real logic when the settings storage is fully
+        // implemented.
+        builder.setConfigureGeoDetectionEnabled(CAPABILITY_NOT_SUPPORTED);
+
         // The ability to make manual time zone suggestions can also be restricted by policy. With
         // the current logic above, this could lead to a situation where a device hardware does not
         // support auto detection, the device has been forced into "auto" mode by an admin and the
@@ -90,6 +94,7 @@
     public TimeZoneConfiguration getConfiguration(@UserIdInt int userId) {
         return new TimeZoneConfiguration.Builder()
                 .setAutoDetectionEnabled(isAutoDetectionEnabled())
+                .setGeoDetectionEnabled(isGeoDetectionEnabled())
                 .build();
     }
 
@@ -105,8 +110,11 @@
         // detection: if we wrote it down then we'd set the default explicitly. That might influence
         // what happens on later releases that do support auto detection on the same hardware.
         if (isAutoDetectionSupported()) {
-            final int value = configuration.isAutoDetectionEnabled() ? 1 : 0;
-            Settings.Global.putInt(mCr, Settings.Global.AUTO_TIME_ZONE, value);
+            final int autoEnabledValue = configuration.isAutoDetectionEnabled() ? 1 : 0;
+            Settings.Global.putInt(mCr, Settings.Global.AUTO_TIME_ZONE, autoEnabledValue);
+
+            final boolean geoTzDetectionEnabledValue = configuration.isGeoDetectionEnabled();
+            // TODO(b/149014708) Write this down to user-scoped settings once implemented.
         }
     }
 
@@ -126,6 +134,14 @@
     }
 
     @Override
+    public boolean isGeoDetectionEnabled() {
+        // TODO(b/149014708) Read this from user-scoped settings once implemented. The user's
+        //  location toggle will act as an override for this setting, i.e. so that the setting will
+        //  return false if the location toggle is disabled.
+        return false;
+    }
+
+    @Override
     public boolean isDeviceTimeZoneInitialized() {
         // timezone.equals("GMT") will be true and only true if the time zone was
         // set to a default value by the system server (when starting, system server
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java
index 3d9ec64..fb7a73d 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java
@@ -22,6 +22,7 @@
  * The internal (in-process) system server API for the {@link
  * com.android.server.timezonedetector.TimeZoneDetectorService}.
  *
+ * <p>The methods on this class can be called from any thread.
  * @hide
  */
 public interface TimeZoneDetectorInternal extends Dumpable.Container {
@@ -29,7 +30,7 @@
     /**
      * Suggests the current time zone, determined using geolocation, to the detector. The
      * detector may ignore the signal based on system settings, whether better information is
-     * available, and so on.
+     * available, and so on. This method may be implemented asynchronously.
      */
     void suggestGeolocationTimeZone(@NonNull GeolocationTimeZoneSuggestion timeZoneSuggestion);
 }
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java
index 4464f7d..15412a0 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java
@@ -58,6 +58,7 @@
             @NonNull GeolocationTimeZoneSuggestion timeZoneSuggestion) {
         Objects.requireNonNull(timeZoneSuggestion);
 
+        // All strategy calls must take place on the mHandler thread.
         mHandler.post(
                 () -> mTimeZoneDetectorStrategy.suggestGeolocationTimeZone(timeZoneSuggestion));
     }
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
index 7467439..d81f949 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
@@ -130,6 +130,10 @@
                         service.handleAutoTimeZoneConfigChanged();
                     }
                 });
+        // TODO(b/149014708) Listen for changes to geolocation time zone detection enabled config.
+        //  This should also include listening to the current user and the current user's location
+        //  toggle since the config is user-scoped and the location toggle overrides the geolocation
+        //  time zone enabled setting.
         return service;
     }
 
@@ -280,7 +284,7 @@
 
     void handleConfigurationChanged() {
         // Note: we could trigger an async time zone detection operation here via a call to
-        // handleAutoTimeZoneDetectionChanged(), but that is triggered in response to the underlying
+        // handleAutoTimeZoneConfigChanged(), but that is triggered in response to the underlying
         // setting value changing so it is currently unnecessary. If we get to a point where all
         // configuration changes are guaranteed to happen in response to an updateConfiguration()
         // call, then we can remove that path and call it here instead.
@@ -288,7 +292,6 @@
         // Configuration has changed, but each user may have a different view of the configuration.
         // It's possible that this will cause unnecessary notifications but that shouldn't be a
         // problem.
-
         synchronized (mConfigurationListeners) {
             final int userCount = mConfigurationListeners.size();
             for (int userIndex = 0; userIndex < userCount; userIndex++) {
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
index f947a655..c5b7e39 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
@@ -30,9 +30,9 @@
  * <p>The strategy uses suggestions to decide whether to modify the device's time zone setting
  * and what to set it to.
  *
- * <p>Most calls will be handled by a single thread but that is not true for all calls. For example
- * {@link #dump(IndentingPrintWriter, String[])}) may be called on a different thread so
- * implementations mustvhandle thread safety.
+ * <p>Most calls will be handled by a single thread, but that is not true for all calls. For example
+ * {@link #dump(IndentingPrintWriter, String[])}) may be called on a different thread concurrently
+ * with other operations so implementations must still handle thread safety.
  *
  * @hide
  */
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
index 9c36c39..d1369a2 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
@@ -23,6 +23,7 @@
 import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE;
 import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_POSSESSED;
 import static android.app.timezonedetector.TimeZoneConfiguration.PROPERTY_AUTO_DETECTION_ENABLED;
+import static android.app.timezonedetector.TimeZoneConfiguration.PROPERTY_GEO_DETECTION_ENABLED;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -98,6 +99,12 @@
         boolean isAutoDetectionEnabled();
 
         /**
+         * Returns whether geolocation can be used for time zone detection when {@link
+         * #isAutoDetectionEnabled()} returns {@code true}.
+         */
+        boolean isGeoDetectionEnabled();
+
+        /**
          * Returns true if the device has had an explicit time zone set.
          */
         boolean isDeviceTimeZoneInitialized();
@@ -200,7 +207,15 @@
      */
     @GuardedBy("this")
     private ArrayMapWithHistory<Integer, QualifiedTelephonyTimeZoneSuggestion>
-            mSuggestionBySlotIndex = new ArrayMapWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE);
+            mTelephonySuggestionsBySlotIndex =
+            new ArrayMapWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE);
+
+    /**
+     * The latest geolocation suggestion received.
+     */
+    @GuardedBy("this")
+    private ReferenceWithHistory<GeolocationTimeZoneSuggestion> mLatestGeoLocationSuggestion =
+            new ReferenceWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE);
 
     @GuardedBy("this")
     private final List<Dumpable> mDumpables = new ArrayList<>();
@@ -284,17 +299,26 @@
 
     private static boolean containsAutoTimeDetectionProperties(
             @NonNull TimeZoneConfiguration configuration) {
-        return configuration.hasProperty(PROPERTY_AUTO_DETECTION_ENABLED);
+        return configuration.hasProperty(PROPERTY_AUTO_DETECTION_ENABLED)
+                || configuration.hasProperty(PROPERTY_GEO_DETECTION_ENABLED);
     }
 
     @Override
     public synchronized void suggestGeolocationTimeZone(
             @NonNull GeolocationTimeZoneSuggestion suggestion) {
-        Objects.requireNonNull(suggestion);
+        if (DBG) {
+            Slog.d(LOG_TAG, "Geolocation suggestion received. newSuggestion=" + suggestion);
+        }
 
-        // TODO Implement this.
-        throw new UnsupportedOperationException(
-                "Geo-location time zone detection is not currently implemented");
+        Objects.requireNonNull(suggestion);
+        mLatestGeoLocationSuggestion.set(suggestion);
+
+        // Now perform auto time zone detection. The new suggestion may be used to modify the time
+        // zone setting.
+        if (mCallback.isGeoDetectionEnabled()) {
+            String reason = "New geolocation time zone suggested. suggestion=" + suggestion;
+            doAutoTimeZoneDetection(reason);
+        }
     }
 
     @Override
@@ -332,12 +356,14 @@
                 new QualifiedTelephonyTimeZoneSuggestion(suggestion, score);
 
         // Store the suggestion against the correct slotIndex.
-        mSuggestionBySlotIndex.put(suggestion.getSlotIndex(), scoredSuggestion);
+        mTelephonySuggestionsBySlotIndex.put(suggestion.getSlotIndex(), scoredSuggestion);
 
         // Now perform auto time zone detection. The new suggestion may be used to modify the time
         // zone setting.
-        String reason = "New telephony time suggested. suggestion=" + suggestion;
-        doAutoTimeZoneDetection(reason);
+        if (!mCallback.isGeoDetectionEnabled()) {
+            String reason = "New telephony time zone suggested. suggestion=" + suggestion;
+            doAutoTimeZoneDetection(reason);
+        }
     }
 
     private static int scoreTelephonySuggestion(@NonNull TelephonyTimeZoneSuggestion suggestion) {
@@ -363,9 +389,7 @@
     }
 
     /**
-     * Finds the best available time zone suggestion from all slotIndexes. If it is high-enough
-     * quality and automatic time zone detection is enabled then it will be set on the device. The
-     * outcome can be that this strategy becomes / remains un-opinionated and nothing is set.
+     * Performs automatic time zone detection.
      */
     @GuardedBy("this")
     private void doAutoTimeZoneDetection(@NonNull String detectionReason) {
@@ -374,6 +398,62 @@
             return;
         }
 
+        // Use the right suggestions based on the current configuration. This check is potentially
+        // race-prone until this value is set via a call to TimeZoneDetectorStrategy.
+        if (mCallback.isGeoDetectionEnabled()) {
+            doGeolocationTimeZoneDetection(detectionReason);
+        } else  {
+            doTelephonyTimeZoneDetection(detectionReason);
+        }
+    }
+
+    /**
+     * Detects the time zone using the latest available geolocation time zone suggestion, if one is
+     * available. The outcome can be that this strategy becomes / remains un-opinionated and nothing
+     * is set.
+     */
+    @GuardedBy("this")
+    private void doGeolocationTimeZoneDetection(@NonNull String detectionReason) {
+        GeolocationTimeZoneSuggestion latestGeolocationSuggestion =
+                mLatestGeoLocationSuggestion.get();
+        if (latestGeolocationSuggestion == null) {
+            return;
+        }
+
+        List<String> zoneIds = latestGeolocationSuggestion.getZoneIds();
+        if (zoneIds == null || zoneIds.isEmpty()) {
+            // This means the client has become uncertain about the time zone or it is certain there
+            // is no known zone. In either case we must leave the existing time zone setting as it
+            // is.
+            return;
+        }
+
+        // GeolocationTimeZoneSuggestion has no measure of quality. We assume all suggestions are
+        // reliable.
+        String zoneId;
+
+        // Introduce bias towards the device's current zone when there are multiple zone suggested.
+        String deviceTimeZone = mCallback.getDeviceTimeZone();
+        if (zoneIds.contains(deviceTimeZone)) {
+            if (DBG) {
+                Slog.d(LOG_TAG,
+                        "Geo tz suggestion contains current device time zone. Applying bias.");
+            }
+            zoneId = deviceTimeZone;
+        } else {
+            zoneId = zoneIds.get(0);
+        }
+        setDeviceTimeZoneIfRequired(zoneId, detectionReason);
+    }
+
+    /**
+     * Detects the time zone using the latest available telephony time zone suggestions.
+     * Finds the best available time zone suggestion from all slotIndexes. If it is high-enough
+     * quality and automatic time zone detection is enabled then it will be set on the device. The
+     * outcome can be that this strategy becomes / remains un-opinionated and nothing is set.
+     */
+    @GuardedBy("this")
+    private void doTelephonyTimeZoneDetection(@NonNull String detectionReason) {
         QualifiedTelephonyTimeZoneSuggestion bestTelephonySuggestion =
                 findBestTelephonySuggestion();
 
@@ -468,9 +548,9 @@
         // slotIndex and find the best. Note that we deliberately do not look at age: the caller can
         // rate-limit so age is not a strong indicator of confidence. Instead, the callers are
         // expected to withdraw suggestions they no longer have confidence in.
-        for (int i = 0; i < mSuggestionBySlotIndex.size(); i++) {
+        for (int i = 0; i < mTelephonySuggestionsBySlotIndex.size(); i++) {
             QualifiedTelephonyTimeZoneSuggestion candidateSuggestion =
-                    mSuggestionBySlotIndex.valueAt(i);
+                    mTelephonySuggestionsBySlotIndex.valueAt(i);
             if (candidateSuggestion == null) {
                 // Unexpected
                 continue;
@@ -505,14 +585,10 @@
     @Override
     public synchronized void handleAutoTimeZoneConfigChanged() {
         if (DBG) {
-            Slog.d(LOG_TAG, "handleTimeZoneDetectionChange() called");
+            Slog.d(LOG_TAG, "handleAutoTimeZoneConfigChanged()");
         }
-        if (mCallback.isAutoDetectionEnabled()) {
-            // When the user enabled time zone detection, run the time zone detection and change the
-            // device time zone if possible.
-            String reason = "Auto time zone detection setting enabled.";
-            doAutoTimeZoneDetection(reason);
-        }
+
+        doAutoTimeZoneDetection("handleAutoTimeZoneConfigChanged()");
     }
 
     @Override
@@ -532,15 +608,21 @@
         ipw.println("mCallback.isDeviceTimeZoneInitialized()="
                 + mCallback.isDeviceTimeZoneInitialized());
         ipw.println("mCallback.getDeviceTimeZone()=" + mCallback.getDeviceTimeZone());
+        ipw.println("mCallback.isGeoDetectionEnabled()=" + mCallback.isGeoDetectionEnabled());
 
         ipw.println("Time zone change log:");
         ipw.increaseIndent(); // level 2
         mTimeZoneChangesLog.dump(ipw);
         ipw.decreaseIndent(); // level 2
 
+        ipw.println("Geolocation suggestion history:");
+        ipw.increaseIndent(); // level 2
+        mLatestGeoLocationSuggestion.dump(ipw);
+        ipw.decreaseIndent(); // level 2
+
         ipw.println("Telephony suggestion history:");
         ipw.increaseIndent(); // level 2
-        mSuggestionBySlotIndex.dump(ipw);
+        mTelephonySuggestionsBySlotIndex.dump(ipw);
         ipw.decreaseIndent(); // level 2
         ipw.decreaseIndent(); // level 1
 
@@ -555,7 +637,15 @@
     @VisibleForTesting
     public synchronized QualifiedTelephonyTimeZoneSuggestion getLatestTelephonySuggestion(
             int slotIndex) {
-        return mSuggestionBySlotIndex.get(slotIndex);
+        return mTelephonySuggestionsBySlotIndex.get(slotIndex);
+    }
+
+    /**
+     * A method used to inspect strategy state during tests. Not intended for general use.
+     */
+    @VisibleForTesting
+    public GeolocationTimeZoneSuggestion getLatestGeolocationSuggestion() {
+        return mLatestGeoLocationSuggestion.get();
     }
 
     /**
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/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 2f695c6..202a3dc 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -3070,7 +3070,7 @@
                     mLockWallpaperMap.put(userId, wallpaper);
                     ensureSaneWallpaperData(wallpaper);
                 } else {
-                    // sanity fallback: we're in bad shape, but establishing a known
+                    // rationality fallback: we're in bad shape, but establishing a known
                     // valid system+lock WallpaperData will keep us from dying.
                     Slog.wtf(TAG, "Didn't find wallpaper in non-lock case!");
                     wallpaper = new WallpaperData(userId, getWallpaperDir(userId),
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index e573d36..e4f2854 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -107,6 +107,12 @@
 import static android.view.WindowManager.TRANSIT_TASK_OPEN_BEHIND;
 import static android.view.WindowManager.TRANSIT_UNSET;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import static com.android.server.wm.ActivityRecordProto.ALL_DRAWN;
@@ -172,12 +178,6 @@
 import static com.android.server.wm.IdentifierProto.HASH_CODE;
 import static com.android.server.wm.IdentifierProto.TITLE;
 import static com.android.server.wm.IdentifierProto.USER_ID;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
 import static com.android.server.wm.Task.ActivityState.DESTROYED;
@@ -294,6 +294,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.ResolverActivity;
 import com.android.internal.content.ReferrerIntent;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.ToBooleanFunction;
 import com.android.internal.util.XmlUtils;
 import com.android.internal.util.function.pooled.PooledConsumer;
@@ -305,7 +306,6 @@
 import com.android.server.am.PendingIntentRecord;
 import com.android.server.display.color.ColorDisplayService;
 import com.android.server.policy.WindowManagerPolicy;
-import com.android.server.protolog.common.ProtoLog;
 import com.android.server.uri.NeededUriGrants;
 import com.android.server.uri.UriPermissionOwner;
 import com.android.server.wm.ActivityMetricsLogger.TransitionInfoSnapshot;
@@ -2549,7 +2549,9 @@
 
         final Task stack = getRootTask();
         final boolean mayAdjustTop = (isState(RESUMED) || stack.mResumedActivity == null)
-                && stack.isFocusedStackOnDisplay();
+                && stack.isFocusedStackOnDisplay()
+                // Do not adjust focus task because the task will be reused to launch new activity.
+                && !task.isClearingToReuseTask();
         final boolean shouldAdjustGlobalFocus = mayAdjustTop
                 // It must be checked before {@link #makeFinishingLocked} is called, because a stack
                 // is not visible if it only contains finishing activities.
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index f7cb014..15e88fc 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2023,8 +2023,6 @@
             // of history or if it is finished immediately), thus disassociating the task. Also note
             // that mReuseTask is reset as a result of {@link Task#performClearTaskLocked}
             // launching another activity.
-            // TODO(b/36119896):  We shouldn't trigger activity launches in this path since we are
-            // already launching one.
             targetTask.performClearTaskLocked();
             targetTask.setIntent(mStartActivity);
             mAddingToTask = true;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 31a9c5d..8204b36 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -251,7 +251,6 @@
 import com.android.server.AttributeCache;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
-import com.android.server.SystemService.TargetUser;
 import com.android.server.SystemServiceManager;
 import com.android.server.UiThread;
 import com.android.server.Watchdog;
@@ -5347,15 +5346,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 +6439,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/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 11a468b..f76108f 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -67,10 +67,10 @@
 import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation;
 import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenEnterAnimation;
 import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenExitAnimation;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
 import static com.android.server.wm.AppTransitionProto.APP_TRANSITION_STATE;
 import static com.android.server.wm.AppTransitionProto.LAST_USED_APP_TRANSITION;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -127,11 +127,11 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.DumpUtils.Dump;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.internal.util.function.pooled.PooledPredicate;
 import com.android.server.AttributeCache;
-import com.android.server.protolog.common.ProtoLog;
 import com.android.server.wm.animation.ClipRectLRAnimation;
 import com.android.server.wm.animation.ClipRectTBAnimation;
 import com.android.server.wm.animation.CurvedTranslateAnimation;
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 5720e9b..57d51c5 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -41,14 +41,14 @@
 import static android.view.WindowManager.TRANSIT_WALLPAPER_INTRA_OPEN;
 import static android.view.WindowManager.TRANSIT_WALLPAPER_OPEN;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
 import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_SNAPSHOT;
 import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_SPLASH_SCREEN;
 import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_WINDOWS_DRAWN;
 import static com.android.server.wm.AppTransition.isKeyguardGoingAwayTransit;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
 import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
@@ -69,7 +69,7 @@
 import android.view.animation.Animation;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.protolog.common.ProtoLog;
+import com.android.internal.protolog.common.ProtoLog;
 
 import java.util.ArrayList;
 import java.util.LinkedList;
diff --git a/services/core/java/com/android/server/wm/BlackFrame.java b/services/core/java/com/android/server/wm/BlackFrame.java
index f563e57..0b2c851 100644
--- a/services/core/java/com/android/server/wm/BlackFrame.java
+++ b/services/core/java/com/android/server/wm/BlackFrame.java
@@ -16,13 +16,13 @@
 
 package com.android.server.wm;
 
-import static com.android.server.wm.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
+import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
 
 import android.graphics.Rect;
 import android.view.Surface.OutOfResourcesException;
 import android.view.SurfaceControl;
 
-import com.android.server.protolog.common.ProtoLog;
+import com.android.internal.protolog.common.ProtoLog;
 
 import java.io.PrintWriter;
 import java.util.function.Supplier;
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index 6ffb482..8bd42f0 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -24,11 +24,11 @@
 import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
 import static android.window.DisplayAreaOrganizer.FEATURE_WINDOW_TOKENS;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
 import static com.android.internal.util.Preconditions.checkState;
 import static com.android.server.wm.DisplayAreaProto.IS_TASK_DISPLAY_AREA;
 import static com.android.server.wm.DisplayAreaProto.NAME;
 import static com.android.server.wm.DisplayAreaProto.WINDOW_CONTAINER;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
 import static com.android.server.wm.WindowContainerChildProto.DISPLAY_AREA;
 
 import android.annotation.Nullable;
@@ -38,8 +38,8 @@
 import android.window.DisplayAreaInfo;
 import android.window.IDisplayAreaOrganizer;
 
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.policy.WindowManagerPolicy;
-import com.android.server.protolog.common.ProtoLog;
 
 import java.util.Comparator;
 import java.util.function.BiFunction;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index fc17053..affbafa 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -74,6 +74,14 @@
 import static android.window.DisplayAreaOrganizer.FEATURE_ROOT;
 import static android.window.DisplayAreaOrganizer.FEATURE_WINDOWED_MAGNIFICATION;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BOOT;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SCREEN_ON;
+import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
@@ -94,14 +102,6 @@
 import static com.android.server.wm.DisplayContentProto.ROTATION;
 import static com.android.server.wm.DisplayContentProto.SCREEN_ROTATION_ANIMATION;
 import static com.android.server.wm.DisplayContentProto.SINGLE_TASK_INSTANCE;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_BOOT;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_FOCUS;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_IME;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_SCREEN_ON;
-import static com.android.server.wm.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
 import static com.android.server.wm.Task.ActivityState.RESUMED;
 import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
@@ -203,6 +203,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.ToBooleanFunction;
 import com.android.internal.util.function.TriConsumer;
 import com.android.internal.util.function.pooled.PooledConsumer;
@@ -211,7 +212,6 @@
 import com.android.internal.util.function.pooled.PooledPredicate;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.policy.WindowManagerPolicy;
-import com.android.server.protolog.common.ProtoLog;
 import com.android.server.wm.utils.DisplayRotationUtil;
 import com.android.server.wm.utils.RotationCache;
 import com.android.server.wm.utils.WmDisplayCutout;
@@ -1557,7 +1557,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;
         }
 
@@ -4063,9 +4068,14 @@
         if (DEBUG_SCREENSHOT && inRotation) Slog.v(TAG_WM, "Taking screenshot while rotating");
 
         // Send invalid rect and no width and height since it will screenshot the entire display.
-        Rect frame = new Rect(0, 0, -1, -1);
-        final Bitmap bitmap = SurfaceControl.screenshot(frame, 0, 0, inRotation,
-                mDisplay.getRotation());
+        final IBinder displayToken = SurfaceControl.getInternalDisplayToken();
+        final SurfaceControl.DisplayCaptureArgs captureArgs =
+                new SurfaceControl.DisplayCaptureArgs.Builder(displayToken)
+                        .setUseIdentityTransform(inRotation)
+                        .build();
+        final SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =
+                SurfaceControl.captureDisplay(captureArgs);
+        final Bitmap bitmap = screenshotBuffer == null ? null : screenshotBuffer.asBitmap();
         if (bitmap == null) {
             Slog.w(TAG_WM, "Failed to take screenshot");
             return null;
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 4f6f75d..2e03cb8 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -112,6 +112,7 @@
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_RIGHT;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SCREEN_ON;
 import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_TIMEOUT;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
 import static com.android.server.policy.WindowManagerPolicy.TRANSIT_ENTER;
@@ -120,7 +121,6 @@
 import static com.android.server.policy.WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
 import static com.android.server.policy.WindowManagerPolicy.TRANSIT_SHOW;
 import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_SCREEN_ON;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -187,6 +187,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.GestureNavigationSettingsObserver;
 import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.ScreenshotHelper;
 import com.android.internal.util.function.TriConsumer;
 import com.android.internal.view.AppearanceRegion;
@@ -199,7 +200,6 @@
 import com.android.server.policy.WindowManagerPolicy.ScreenOnListener;
 import com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs;
 import com.android.server.policy.WindowOrientationListener;
-import com.android.server.protolog.common.ProtoLog;
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.wallpaper.WallpaperManagerInternal;
 import com.android.server.wm.utils.InsetUtils;
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index c63128c..0206787 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -22,8 +22,8 @@
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
 import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_OPEN;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -58,12 +58,12 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.LocalServices;
 import com.android.server.UiThread;
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.policy.WindowOrientationListener;
-import com.android.server.protolog.common.ProtoLog;
 import com.android.server.statusbar.StatusBarManagerInternal;
 
 import java.io.PrintWriter;
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 22dd1d3..133b111 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -18,11 +18,11 @@
 
 import static android.os.IInputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
+import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
 import static com.android.server.wm.DragDropController.MSG_ANIMATION_END;
 import static com.android.server.wm.DragDropController.MSG_DRAG_END_TIMEOUT;
 import static com.android.server.wm.DragDropController.MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
-import static com.android.server.wm.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -58,9 +58,9 @@
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
 
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.view.IDragAndDropPermissions;
 import com.android.server.LocalServices;
-import com.android.server.protolog.common.ProtoLog;
 
 import java.util.ArrayList;
 
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index e7fbc334..86e2698 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -16,13 +16,13 @@
 
 package com.android.server.wm;
 
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_IME;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME;
 
 import android.view.InsetsSource;
 import android.view.WindowInsets;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.protolog.common.ProtoLog;
+import com.android.internal.protolog.common.ProtoLog;
 
 import java.io.PrintWriter;
 
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 791f471..0fe9735 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -41,7 +41,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
@@ -60,8 +60,8 @@
 import android.view.InputWindowHandle;
 import android.view.SurfaceControl;
 
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.policy.WindowManagerPolicy;
-import com.android.server.protolog.common.ProtoLog;
 
 import java.io.PrintWriter;
 import java.util.Set;
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index f64149c..d1eb795 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -24,7 +24,7 @@
 import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
 import static android.view.ViewRootImpl.sNewInsetsMode;
 
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_IME;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_INSETS_CONTROL;
 import static com.android.server.wm.WindowManagerService.H.LAYOUT_AND_ASSIGN_WINDOW_LAYERS_IF_NEEDED;
 
@@ -40,8 +40,8 @@
 import android.view.SurfaceControl.Transaction;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.function.TriConsumer;
-import com.android.server.protolog.common.ProtoLog;
 import com.android.server.wm.SurfaceAnimator.AnimationType;
 import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
 
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 9c978fd..ab1074e 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -30,7 +30,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
 
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_IME;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -45,8 +45,8 @@
 import android.view.InsetsState.InternalInsetsType;
 import android.view.WindowManager;
 
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.inputmethod.InputMethodManagerInternal;
-import com.android.server.protolog.common.ProtoLog;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -105,6 +105,10 @@
      * @return The state stripped of the necessary information.
      */
     InsetsState getInsetsForDispatch(@NonNull WindowState target) {
+        final InsetsState rotatedState = target.mToken.getFixedRotationTransformInsetsState();
+        if (rotatedState != null) {
+            return rotatedState;
+        }
         final InsetsSourceProvider provider = target.getControllableInsetProvider();
         final @InternalInsetsType int type = provider != null
                 ? provider.getSource().getType() : ITYPE_INVALID;
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index df53563..3c64ffb 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -1627,8 +1627,8 @@
         }
         if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: adding affilliates starting at "
                 + topIndex + " from intial " + taskIndex);
-        // Find the end of the chain, doing a sanity check along the way.
-        boolean sane = top.mAffiliatedTaskId == task.mAffiliatedTaskId;
+        // Find the end of the chain, doing a validity check along the way.
+        boolean isValid = top.mAffiliatedTaskId == task.mAffiliatedTaskId;
         int endIndex = topIndex;
         Task prev = top;
         while (endIndex < recentsCount) {
@@ -1640,7 +1640,7 @@
                 if (cur.mNextAffiliate != null || cur.mNextAffiliateTaskId != INVALID_TASK_ID) {
                     Slog.wtf(TAG, "Bad chain @" + endIndex
                             + ": first task has next affiliate: " + prev);
-                    sane = false;
+                    isValid = false;
                     break;
                 }
             } else {
@@ -1652,7 +1652,7 @@
                             + " has bad next affiliate "
                             + cur.mNextAffiliate + " id " + cur.mNextAffiliateTaskId
                             + ", expected " + prev);
-                    sane = false;
+                    isValid = false;
                     break;
                 }
             }
@@ -1662,7 +1662,7 @@
                     Slog.wtf(TAG, "Bad chain @" + endIndex
                             + ": last task " + cur + " has previous affiliate "
                             + cur.mPrevAffiliate);
-                    sane = false;
+                    isValid = false;
                 }
                 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: end of chain @" + endIndex);
                 break;
@@ -1673,7 +1673,7 @@
                             + ": task " + cur + " has previous affiliate "
                             + cur.mPrevAffiliate + " but should be id "
                             + cur.mPrevAffiliate);
-                    sane = false;
+                    isValid = false;
                     break;
                 }
             }
@@ -1682,7 +1682,7 @@
                         + ": task " + cur + " has affiliated id "
                         + cur.mAffiliatedTaskId + " but should be "
                         + task.mAffiliatedTaskId);
-                sane = false;
+                isValid = false;
                 break;
             }
             prev = cur;
@@ -1690,18 +1690,18 @@
             if (endIndex >= recentsCount) {
                 Slog.wtf(TAG, "Bad chain ran off index " + endIndex
                         + ": last task " + prev);
-                sane = false;
+                isValid = false;
                 break;
             }
         }
-        if (sane) {
+        if (isValid) {
             if (endIndex < taskIndex) {
                 Slog.wtf(TAG, "Bad chain @" + endIndex
                         + ": did not extend to task " + task + " @" + taskIndex);
-                sane = false;
+                isValid = false;
             }
         }
-        if (sane) {
+        if (isValid) {
             // All looks good, we can just move all of the affiliated tasks
             // to the top.
             for (int i=topIndex; i<=endIndex; i++) {
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index d7b43bc..6c41683 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -25,8 +25,8 @@
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.WindowManager.TRANSIT_NONE;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
 import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
 import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE;
 import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_ORIGINAL_POSITION;
 import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_TOP;
@@ -41,9 +41,9 @@
 import android.util.Slog;
 import android.view.IRecentsAnimationRunner;
 
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.internal.util.function.pooled.PooledPredicate;
-import com.android.server.protolog.common.ProtoLog;
 import com.android.server.wm.ActivityMetricsLogger.LaunchingState;
 import com.android.server.wm.RecentsAnimationController.RecentsAnimationCallbacks;
 
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 6b3a5d6..143fbb0 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -23,10 +23,10 @@
 import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_RECENTS_ANIM;
 import static com.android.server.wm.AnimationAdapterProto.REMOTE;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
 import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
 import static com.android.server.wm.WindowManagerInternal.AppTransitionListener;
@@ -56,12 +56,12 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.inputmethod.SoftInputShowHideReason;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.function.pooled.PooledConsumer;
 import com.android.internal.util.function.pooled.PooledFunction;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.LocalServices;
 import com.android.server.inputmethod.InputMethodManagerInternal;
-import com.android.server.protolog.common.ProtoLog;
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.wm.SurfaceAnimator.AnimationType;
 import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index c255a18..e7461e7 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -16,8 +16,8 @@
 
 package com.android.server.wm;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_REMOTE_ANIMATIONS;
 import static com.android.server.wm.AnimationAdapterProto.REMOTE;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_REMOTE_ANIMATIONS;
 import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -37,9 +37,9 @@
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
 
+import com.android.internal.protolog.ProtoLogImpl;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.FastPrintWriter;
-import com.android.server.protolog.ProtoLogImpl;
-import com.android.server.protolog.common.ProtoLog;
 import com.android.server.wm.SurfaceAnimator.AnimationType;
 import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
 
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 1cb483c..fe2d08f 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -41,6 +41,11 @@
 import static android.view.WindowManager.TRANSIT_SHOW_SINGLE_TASK_DISPLAY;
 import static android.view.WindowManager.TRANSIT_TASK_TO_BACK;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_KEEP_SCREEN_ON;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
+import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
+import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
 import static com.android.server.policy.PhoneWindowManager.SYSTEM_DIALOG_REASON_ASSIST;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
@@ -59,11 +64,6 @@
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_TASKS;
 import static com.android.server.wm.ActivityTaskManagerService.ANIMATE;
 import static com.android.server.wm.ActivityTaskManagerService.TAG_SWITCH;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_KEEP_SCREEN_ON;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
-import static com.android.server.wm.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
-import static com.android.server.wm.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
 import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE;
 import static com.android.server.wm.RootWindowContainerProto.IS_HOME_RECENTS_COMPONENT;
 import static com.android.server.wm.RootWindowContainerProto.KEYGUARD_CONTROLLER;
@@ -146,6 +146,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.ResolverActivity;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.function.pooled.PooledConsumer;
 import com.android.internal.util.function.pooled.PooledFunction;
 import com.android.internal.util.function.pooled.PooledLambda;
@@ -155,7 +156,6 @@
 import com.android.server.am.AppTimeTracker;
 import com.android.server.am.UserState;
 import com.android.server.policy.WindowManagerPolicy;
-import com.android.server.protolog.common.ProtoLog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -737,7 +737,7 @@
                 winAnimator.mSession.mPid, operation);
         final long callingIdentity = Binder.clearCallingIdentity();
         try {
-            // There was some problem...first, do a sanity check of the window list to make sure
+            // There was some problem...first, do a validity check of the window list to make sure
             // we haven't left any dangling surfaces around.
 
             Slog.i(TAG_WM, "Out of memory for surface!  Looking for leaks...");
@@ -811,7 +811,7 @@
     }
 
     // "Something has changed!  Let's make it correct now."
-    // TODO: Super crazy long method that should be broken down...
+    // TODO: Super long method that should be broken down...
     void performSurfacePlacementNoTrace() {
         if (DEBUG_WINDOW_TRACE) Slog.v(TAG, "performSurfacePlacementInner: entry. Called by "
                 + Debug.getCallers(3));
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index d7b8fb0..3c8036d 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -18,9 +18,9 @@
 
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
+import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
 import static com.android.server.wm.AnimationSpecProto.ROTATE;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
-import static com.android.server.wm.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
 import static com.android.server.wm.RotationAnimationSpecProto.DURATION_MS;
 import static com.android.server.wm.RotationAnimationSpecProto.END_LUMA;
 import static com.android.server.wm.RotationAnimationSpecProto.START_LUMA;
@@ -50,7 +50,7 @@
 import android.view.animation.Transformation;
 
 import com.android.internal.R;
-import com.android.server.protolog.common.ProtoLog;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.wm.SurfaceAnimator.AnimationType;
 import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
 import com.android.server.wm.utils.RotationAnimationUtils;
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 86cbf3e..3b32a9d 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -24,7 +24,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType;
 
-import static com.android.server.wm.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_POSITIONING;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -58,7 +58,7 @@
 import android.view.WindowManager;
 
 import com.android.internal.os.logging.MetricsLoggerWrapper;
-import com.android.server.protolog.common.ProtoLog;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.wm.WindowManagerService.H;
 
 import java.io.PrintWriter;
diff --git a/services/core/java/com/android/server/wm/SurfaceFreezer.java b/services/core/java/com/android/server/wm/SurfaceFreezer.java
index 1c97430..d9365c5 100644
--- a/services/core/java/com/android/server/wm/SurfaceFreezer.java
+++ b/services/core/java/com/android/server/wm/SurfaceFreezer.java
@@ -16,7 +16,7 @@
 
 package com.android.server.wm;
 
-import static com.android.server.wm.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_SCREEN_ROTATION;
 
@@ -28,7 +28,7 @@
 import android.view.Surface;
 import android.view.SurfaceControl;
 
-import com.android.server.protolog.common.ProtoLog;
+import com.android.internal.protolog.common.ProtoLog;
 
 import java.util.function.Supplier;
 
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index be0815b0..0529abf 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -78,6 +78,8 @@
 
 import static com.android.internal.policy.DecorView.DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP;
 import static com.android.internal.policy.DecorView.DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
 import static com.android.server.wm.ActivityRecord.STARTING_WINDOW_SHOWN;
 import static com.android.server.wm.ActivityStackSupervisor.DEFER_RESUME;
 import static com.android.server.wm.ActivityStackSupervisor.ON_TOP;
@@ -116,8 +118,6 @@
 import static com.android.server.wm.IdentifierProto.HASH_CODE;
 import static com.android.server.wm.IdentifierProto.TITLE;
 import static com.android.server.wm.IdentifierProto.USER_ID;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
 import static com.android.server.wm.Task.ActivityState.PAUSED;
 import static com.android.server.wm.Task.ActivityState.PAUSING;
 import static com.android.server.wm.Task.ActivityState.RESUMED;
@@ -210,6 +210,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IVoiceInteractor;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.XmlUtils;
 import com.android.internal.util.function.pooled.PooledConsumer;
 import com.android.internal.util.function.pooled.PooledFunction;
@@ -218,7 +219,6 @@
 import com.android.server.Watchdog;
 import com.android.server.am.ActivityManagerService;
 import com.android.server.am.AppTimeTracker;
-import com.android.server.protolog.common.ProtoLog;
 import com.android.server.uri.NeededUriGrants;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -1836,14 +1836,25 @@
      */
     void performClearTaskLocked() {
         mReuseTask = true;
-        performClearTask("clear-task-all");
-        mReuseTask = false;
+        mStackSupervisor.beginDeferResume();
+        try {
+            performClearTask("clear-task-all");
+        } finally {
+            mStackSupervisor.endDeferResume();
+            mReuseTask = false;
+        }
     }
 
     ActivityRecord performClearTaskForReuseLocked(ActivityRecord newR, int launchFlags) {
         mReuseTask = true;
-        final ActivityRecord result = performClearTaskLocked(newR, launchFlags);
-        mReuseTask = false;
+        mStackSupervisor.beginDeferResume();
+        final ActivityRecord result;
+        try {
+            result = performClearTaskLocked(newR, launchFlags);
+        } finally {
+            mStackSupervisor.endDeferResume();
+            mReuseTask = false;
+        }
         return result;
     }
 
@@ -7056,17 +7067,21 @@
     /**
      * Reset local parameters because an app's activity died.
      * @param app The app of the activity that died.
+     * @return {@code true} if the process of the pausing activity is died.
      */
-    void handleAppDied(WindowProcessController app) {
+    boolean handleAppDied(WindowProcessController app) {
+        boolean isPausingDied = false;
         if (mPausingActivity != null && mPausingActivity.app == app) {
             if (DEBUG_PAUSE || DEBUG_CLEANUP) Slog.v(TAG_PAUSE,
                     "App died while pausing: " + mPausingActivity);
             mPausingActivity = null;
+            isPausingDied = true;
         }
         if (mLastPausedActivity != null && mLastPausedActivity.app == app) {
             mLastPausedActivity = null;
             mLastNoHistoryActivity = null;
         }
+        return isPausingDied;
     }
 
     boolean dump(FileDescriptor fd, PrintWriter pw, boolean dumpAll, boolean dumpClient,
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index a847744..3251110 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -32,13 +32,13 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
 import static com.android.server.wm.ActivityStackSupervisor.TAG_TASKS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_STATES;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TASKS;
 import static com.android.server.wm.ActivityTaskManagerService.TAG_STACK;
 import static com.android.server.wm.DisplayContent.alwaysCreateStack;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
 import static com.android.server.wm.RootWindowContainer.TAG_STATES;
 import static com.android.server.wm.Task.ActivityState.RESUMED;
 import static com.android.server.wm.Task.STACK_VISIBILITY_VISIBLE;
@@ -58,10 +58,10 @@
 import android.window.WindowContainerTransaction;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.ToBooleanFunction;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.internal.util.function.pooled.PooledPredicate;
-import com.android.server.protolog.common.ProtoLog;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index 3fbc037..a66cd84 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -26,8 +26,8 @@
 import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_NONE;
 import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_RIGHT;
 import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_TOP;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
 import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_POSITIONING;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -59,7 +59,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.TaskResizingAlgorithm;
 import com.android.internal.policy.TaskResizingAlgorithm.CtrlType;
-import com.android.server.protolog.common.ProtoLog;
+import com.android.internal.protolog.common.ProtoLog;
 
 class TaskPositioner implements IBinder.DeathRecipient {
     private static final boolean DEBUG_ORIENTATION_VIOLATIONS = false;
diff --git a/services/core/java/com/android/server/wm/TaskScreenshotAnimatable.java b/services/core/java/com/android/server/wm/TaskScreenshotAnimatable.java
index 1103bf1..3def091 100644
--- a/services/core/java/com/android/server/wm/TaskScreenshotAnimatable.java
+++ b/services/core/java/com/android/server/wm/TaskScreenshotAnimatable.java
@@ -15,14 +15,14 @@
  */
 package com.android.server.wm;
 
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
 
 import android.hardware.HardwareBuffer;
 import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
 
-import com.android.server.protolog.common.ProtoLog;
+import com.android.internal.protolog.common.ProtoLog;
 
 import java.util.function.Function;
 
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 68445f6..dbbb7ff 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -27,7 +27,6 @@
 import android.app.ActivityManager.TaskSnapshot;
 import android.content.pm.PackageManager;
 import android.graphics.Bitmap;
-import android.graphics.Insets;
 import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.graphics.RecordingCanvas;
@@ -82,7 +81,7 @@
 
     /**
      * Return value for {@link #getSnapshotMode}: We are not allowed to take a real screenshot but
-     * we should try to use the app theme to create a dummy representation of the app.
+     * we should try to use the app theme to create a fake representation of the app.
      */
     @VisibleForTesting
     static final int SNAPSHOT_MODE_APP_THEME = 1;
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
index 9b18ac8..e9ada6b 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
@@ -41,7 +41,7 @@
 import static com.android.internal.policy.DecorView.NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES;
 import static com.android.internal.policy.DecorView.STATUS_BAR_COLOR_VIEW_ATTRIBUTES;
 import static com.android.internal.policy.DecorView.getNavigationBarRect;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
 import static com.android.server.wm.TaskSnapshotController.getSystemBarInsets;
 import static com.android.server.wm.TaskSnapshotController.mergeInsetsSources;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -83,9 +83,9 @@
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.DecorView;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.view.BaseIWindow;
 import com.android.server.policy.WindowManagerPolicy.StartingSurface;
-import com.android.server.protolog.common.ProtoLog;
 
 /**
  * This class represents a starting window that shows a snapshot.
diff --git a/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java b/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java
index f467015..38bff9e 100644
--- a/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java
@@ -15,8 +15,8 @@
  */
 package com.android.server.wm;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_REMOTE_ANIMATIONS;
 import static com.android.server.wm.AnimationAdapterProto.REMOTE;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_REMOTE_ANIMATIONS;
 import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
 
@@ -26,7 +26,7 @@
 import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
 
-import com.android.server.protolog.common.ProtoLog;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.wm.SurfaceAnimator.AnimationType;
 
 import java.io.PrintWriter;
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 6377a21..5c6266a 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -16,7 +16,7 @@
 
 package com.android.server.wm;
 
-import static com.android.server.wm.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
@@ -36,8 +36,8 @@
 import android.view.Choreographer;
 import android.view.SurfaceControl;
 
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.policy.WindowManagerPolicy;
-import com.android.server.protolog.common.ProtoLog;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index e24d185..8a5e70f 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -28,14 +28,14 @@
 import static android.os.UserHandle.USER_NULL;
 import static android.view.SurfaceControl.Transaction;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import static com.android.server.wm.AppTransition.MAX_APP_TRANSITION_DURATION;
 import static com.android.server.wm.IdentifierProto.HASH_CODE;
 import static com.android.server.wm.IdentifierProto.TITLE;
 import static com.android.server.wm.IdentifierProto.USER_ID;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
 import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
@@ -82,8 +82,8 @@
 import android.window.WindowContainerToken;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.ToBooleanFunction;
-import com.android.server.protolog.common.ProtoLog;
 import com.android.server.wm.SurfaceAnimator.Animatable;
 import com.android.server.wm.SurfaceAnimator.AnimationType;
 import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
diff --git a/services/core/java/com/android/server/wm/WindowContainerThumbnail.java b/services/core/java/com/android/server/wm/WindowContainerThumbnail.java
index b75f886..b9f67a5 100644
--- a/services/core/java/com/android/server/wm/WindowContainerThumbnail.java
+++ b/services/core/java/com/android/server/wm/WindowContainerThumbnail.java
@@ -19,7 +19,7 @@
 import static android.view.SurfaceControl.METADATA_OWNER_UID;
 import static android.view.SurfaceControl.METADATA_WINDOW_TYPE;
 
-import static com.android.server.wm.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
 import static com.android.server.wm.WindowContainerThumbnailProto.HEIGHT;
 import static com.android.server.wm.WindowContainerThumbnailProto.SURFACE_ANIMATOR;
@@ -39,7 +39,7 @@
 import android.view.SurfaceControl.Transaction;
 import android.view.animation.Animation;
 
-import com.android.server.protolog.common.ProtoLog;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.wm.SurfaceAnimator.Animatable;
 import com.android.server.wm.SurfaceAnimator.AnimationType;
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 0b1d6bc..7624d4c 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -84,22 +84,22 @@
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BOOT;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_KEEP_SCREEN_ON;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SCREEN_ON;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_MOVEMENT;
+import static com.android.internal.protolog.ProtoLogGroup.WM_ERROR;
+import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
 import static com.android.internal.util.LatencyTracker.ACTION_ROTATE_SCREEN;
 import static com.android.server.LockGuard.INDEX_WINDOW;
 import static com.android.server.LockGuard.installLock;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_BOOT;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_FOCUS;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_IME;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_KEEP_SCREEN_ON;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_SCREEN_ON;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_WINDOW_MOVEMENT;
-import static com.android.server.wm.ProtoLogGroup.WM_ERROR;
-import static com.android.server.wm.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
 import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
 import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
@@ -265,6 +265,8 @@
 import com.android.internal.policy.IKeyguardDismissCallback;
 import com.android.internal.policy.IShortcutService;
 import com.android.internal.policy.KeyInterceptionInfo;
+import com.android.internal.protolog.ProtoLogImpl;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FastPrintWriter;
 import com.android.internal.util.LatencyTracker;
@@ -281,8 +283,6 @@
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.policy.WindowManagerPolicy.ScreenOffListener;
 import com.android.server.power.ShutdownThread;
-import com.android.server.protolog.ProtoLogImpl;
-import com.android.server.protolog.common.ProtoLog;
 import com.android.server.utils.PriorityDump;
 import com.android.server.wm.utils.DeviceConfigInterface;
 
@@ -3612,7 +3612,7 @@
             }
 
             if (SHOW_VERBOSE_TRANSACTIONS) Slog.i(TAG_WM, ">>> showStrictModeViolation");
-            // TODO: Modify this to use the surface trace once it is not going crazy.
+            // TODO: Modify this to use the surface trace once it is not going baffling.
             // b/31532461
             // TODO(multi-display): support multiple displays
             if (mStrictModeFlash == null) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index bdecb8d..271d2b1 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -31,7 +31,7 @@
 import android.view.ViewDebug;
 
 import com.android.internal.os.ByteTransferPipe;
-import com.android.server.protolog.ProtoLogImpl;
+import com.android.internal.protolog.ProtoLogImpl;
 
 import java.io.IOException;
 import java.io.PrintWriter;
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 70c30c9..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;
@@ -1201,7 +1200,10 @@
 
             final Task rootTask = r.getRootTask();
             if (rootTask != null) {
-                rootTask.handleAppDied(this);
+                // There may be a pausing activity that hasn't shown any window and was requested
+                // to be hidden. But pausing is also a visible state, it should be regarded as
+                // visible, so the caller can know the next activity should be resumed.
+                hasVisibleActivities |= rootTask.handleAppDied(this);
             }
             r.handleAppDied();
         }
@@ -1289,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/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index ab78e74..49e623d 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -101,6 +101,14 @@
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME;
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RESIZE;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
 import static com.android.server.am.ActivityManagerService.MY_PID;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import static com.android.server.policy.WindowManagerPolicy.TRANSIT_ENTER;
@@ -116,14 +124,6 @@
 import static com.android.server.wm.MoveAnimationSpecProto.DURATION_MS;
 import static com.android.server.wm.MoveAnimationSpecProto.FROM;
 import static com.android.server.wm.MoveAnimationSpecProto.TO;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_FOCUS;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_IME;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_RESIZE;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
 import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
@@ -236,10 +236,10 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.KeyInterceptionInfo;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.ToBooleanFunction;
 import com.android.server.policy.WindowManagerPolicy;
-import com.android.server.protolog.common.ProtoLog;
 import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
 import com.android.server.wm.SurfaceAnimator.AnimationType;
 import com.android.server.wm.utils.WmDisplayCutout;
@@ -1535,10 +1535,6 @@
     }
 
     InsetsState getInsetsState() {
-        final InsetsState insetsState = mToken.getFixedRotationTransformInsetsState();
-        if (insetsState != null) {
-            return insetsState;
-        }
         return getDisplayContent().getInsetsPolicy().getInsetsForDispatch(this);
     }
 
@@ -2460,9 +2456,7 @@
             finishInputEvent(event, true);
         }
     }
-    /**
-     *  Dummy event receiver for windows that died visible.
-     */
+    /** Fake event receiver for windows that died visible. */
     private DeadWindowEventReceiver mDeadWindowEventReceiver;
 
     void openInputChannel(InputChannel outInputChannel) {
@@ -2480,9 +2474,9 @@
             mClientChannel.dispose();
             mClientChannel = null;
         } else {
-            // If the window died visible, we setup a dummy input channel, so that taps
+            // If the window died visible, we setup a fake input channel, so that taps
             // can still detected by input monitor channel, and we can relaunch the app.
-            // Create dummy event receiver that simply reports all events as handled.
+            // Create fake event receiver that simply reports all events as handled.
             mDeadWindowEventReceiver = new DeadWindowEventReceiver(mClientChannel);
         }
         mWmService.mInputToWindowMap.put(mInputWindowHandle.token, this);
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index d0101ad..92177ab 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -31,13 +31,13 @@
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 import static android.view.WindowManager.TRANSIT_NONE;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_DRAW;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
+import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
+import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_DRAW;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
-import static com.android.server.wm.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
-import static com.android.server.wm.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
 import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
@@ -76,8 +76,8 @@
 import android.view.animation.Animation;
 import android.view.animation.AnimationUtils;
 
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.policy.WindowManagerPolicy;
-import com.android.server.protolog.common.ProtoLog;
 
 import java.io.PrintWriter;
 
@@ -560,9 +560,9 @@
             }
         }
 
-        // Something is wrong and SurfaceFlinger will not like this, try to revert to sane values.
-        // This doesn't necessarily mean that there is an error in the system. The sizes might be
-        // incorrect, because it is before the first layout or draw.
+        // Something is wrong and SurfaceFlinger will not like this, try to revert to reasonable
+        // values. This doesn't necessarily mean that there is an error in the system. The sizes
+        // might be incorrect, because it is before the first layout or draw.
         if (outSize.width() < 1) {
             outSize.right = 1;
         }
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index b89cdd3..9b40822 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -21,8 +21,8 @@
 import static android.view.SurfaceControl.METADATA_OWNER_UID;
 import static android.view.SurfaceControl.METADATA_WINDOW_TYPE;
 
-import static com.android.server.wm.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
-import static com.android.server.wm.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
+import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -40,7 +40,7 @@
 import android.view.WindowContentFrameStats;
 import android.view.WindowManager;
 
-import com.android.server.protolog.common.ProtoLog;
+import com.android.internal.protolog.common.ProtoLog;
 
 import java.io.PrintWriter;
 
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 2c1bb3e..bc4d9a9 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -23,10 +23,10 @@
 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
 
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_FOCUS;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_WINDOW_MOVEMENT;
-import static com.android.server.wm.ProtoLogGroup.WM_ERROR;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_MOVEMENT;
+import static com.android.internal.protolog.ProtoLogGroup.WM_ERROR;
 import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
 import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
@@ -59,8 +59,8 @@
 import android.view.WindowManager;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.policy.WindowManagerPolicy;
-import com.android.server.protolog.common.ProtoLog;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -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/java/com/android/server/wm/WindowTracing.java b/services/core/java/com/android/server/wm/WindowTracing.java
index ba3dc60..e8b8bfc 100644
--- a/services/core/java/com/android/server/wm/WindowTracing.java
+++ b/services/core/java/com/android/server/wm/WindowTracing.java
@@ -34,7 +34,7 @@
 import android.util.proto.ProtoOutputStream;
 import android.view.Choreographer;
 
-import com.android.server.protolog.ProtoLogImpl;
+import com.android.internal.protolog.ProtoLogImpl;
 import com.android.internal.util.TraceBuffer;
 
 import java.io.File;
diff --git a/services/core/jni/com_android_server_VibratorService.cpp b/services/core/jni/com_android_server_VibratorService.cpp
index b6633ce..b3f3a5e 100644
--- a/services/core/jni/com_android_server_VibratorService.cpp
+++ b/services/core/jni/com_android_server_VibratorService.cpp
@@ -17,9 +17,7 @@
 #define LOG_TAG "VibratorService"
 
 #include <android/hardware/vibrator/1.3/IVibrator.h>
-#include <android/hardware/vibrator/BnVibratorCallback.h>
 #include <android/hardware/vibrator/IVibrator.h>
-#include <binder/IServiceManager.h>
 
 #include "jni.h"
 #include <nativehelper/JNIHelp.h>
@@ -28,19 +26,11 @@
 
 #include <utils/misc.h>
 #include <utils/Log.h>
-#include <hardware/vibrator.h>
 
 #include <inttypes.h>
-#include <stdio.h>
 
 #include <vibratorservice/VibratorHalController.h>
 
-using android::hardware::Return;
-using android::hardware::Void;
-using android::hardware::vibrator::V1_0::EffectStrength;
-using android::hardware::vibrator::V1_0::Status;
-using android::hardware::vibrator::V1_1::Effect_1_1;
-
 namespace V1_0 = android::hardware::vibrator::V1_0;
 namespace V1_1 = android::hardware::vibrator::V1_1;
 namespace V1_2 = android::hardware::vibrator::V1_2;
@@ -87,150 +77,7 @@
 static_assert(static_cast<uint8_t>(V1_3::Effect::TEXTURE_TICK) ==
                 static_cast<uint8_t>(aidl::Effect::TEXTURE_TICK));
 
-class VibratorCallback {
-    public:
-        VibratorCallback(JNIEnv *env, jobject vibration) :
-        mVibration(MakeGlobalRefOrDie(env, vibration)) {}
-
-        ~VibratorCallback() {
-            JNIEnv *env = AndroidRuntime::getJNIEnv();
-            env->DeleteGlobalRef(mVibration);
-        }
-
-        void onComplete() {
-            auto env = AndroidRuntime::getJNIEnv();
-            env->CallVoidMethod(mVibration, sMethodIdOnComplete);
-        }
-
-    private:
-        jobject mVibration;
-};
-
-class AidlVibratorCallback : public aidl::BnVibratorCallback {
-  public:
-    AidlVibratorCallback(JNIEnv *env, jobject vibration) :
-    mCb(env, vibration) {}
-
-    binder::Status onComplete() override {
-        mCb.onComplete();
-        return binder::Status::ok(); // oneway, local call
-    }
-
-  private:
-    VibratorCallback mCb;
-};
-
-static constexpr int NUM_TRIES = 2;
-
-template<class R>
-inline R NoneStatus() {
-    using ::android::hardware::Status;
-    return Status::fromExceptionCode(Status::EX_NONE);
-}
-
-template<>
-inline binder::Status NoneStatus() {
-    using binder::Status;
-    return Status::fromExceptionCode(Status::EX_NONE);
-}
-
-// Creates a Return<R> with STATUS::EX_NULL_POINTER.
-template<class R>
-inline R NullptrStatus() {
-    using ::android::hardware::Status;
-    return Status::fromExceptionCode(Status::EX_NULL_POINTER);
-}
-
-template<>
-inline binder::Status NullptrStatus() {
-    using binder::Status;
-    return Status::fromExceptionCode(Status::EX_NULL_POINTER);
-}
-
-template <typename I>
-sp<I> getService() {
-    return I::getService();
-}
-
-template <>
-sp<aidl::IVibrator> getService() {
-    return waitForVintfService<aidl::IVibrator>();
-}
-
-template <typename I>
-sp<I> tryGetService() {
-    return I::tryGetService();
-}
-
-template <>
-sp<aidl::IVibrator> tryGetService() {
-    return checkVintfService<aidl::IVibrator>();
-}
-
-template <typename I>
-class HalWrapper {
-  public:
-    static std::unique_ptr<HalWrapper> Create() {
-        // Assume that if getService returns a nullptr, HAL is not available on the
-        // device.
-        auto hal = getService<I>();
-        return hal ? std::unique_ptr<HalWrapper>(new HalWrapper(std::move(hal))) : nullptr;
-    }
-
-    // Helper used to transparently deal with the vibrator HAL becoming unavailable.
-    template<class R, class... Args0, class... Args1>
-    R call(R (I::* fn)(Args0...), Args1&&... args1) {
-        // Return<R> doesn't have a default constructor, so make a Return<R> with
-        // STATUS::EX_NONE.
-        R ret{NoneStatus<R>()};
-
-        // Note that ret is guaranteed to be changed after this loop.
-        for (int i = 0; i < NUM_TRIES; ++i) {
-            ret = (mHal == nullptr) ? NullptrStatus<R>()
-                    : (*mHal.*fn)(std::forward<Args1>(args1)...);
-
-            if (ret.isOk()) {
-                break;
-            }
-
-            ALOGE("Failed to issue command to vibrator HAL. Retrying.");
-
-            // Restoring connection to the HAL.
-            mHal = tryGetService<I>();
-        }
-        return ret;
-    }
-
-  private:
-    HalWrapper(sp<I> &&hal) : mHal(std::move(hal)) {}
-
-  private:
-    sp<I> mHal;
-};
-
-template <typename I>
-static auto getHal() {
-    static auto sHalWrapper = HalWrapper<I>::Create();
-    return sHalWrapper.get();
-}
-
-template<class R, class I, class... Args0, class... Args1>
-R halCall(R (I::* fn)(Args0...), Args1&&... args1) {
-    auto hal = getHal<I>();
-    return hal ? hal->call(fn, std::forward<Args1>(args1)...) : NullptrStatus<R>();
-}
-
-template<class R>
-bool isValidEffect(jlong effect) {
-    if (effect < 0) {
-        return false;
-    }
-    R val = static_cast<R>(effect);
-    auto iter = hardware::hidl_enum_range<R>();
-    return val >= *iter.begin() && val <= *std::prev(iter.end());
-}
-
-static void callVibrationOnComplete(jobject vibration) {
+static inline void callVibrationOnComplete(jobject vibration) {
     if (vibration == nullptr) {
         return;
     }
@@ -257,14 +104,6 @@
 }
 
 static jlong vibratorInit(JNIEnv* /* env */, jclass /* clazz */) {
-    if (auto hal = getHal<aidl::IVibrator>()) {
-        // IBinder::pingBinder isn't accessible as a pointer function
-        // but getCapabilities can serve the same purpose
-        int32_t cap;
-        hal->call(&aidl::IVibrator::getCapabilities, &cap).isOk();
-    } else {
-        halCall(&V1_0::IVibrator::ping).isOk();
-    }
     std::unique_ptr<vibrator::HalController> controller =
             std::make_unique<vibrator::HalController>();
     controller->init();
@@ -342,70 +181,37 @@
     return effects;
 }
 
-static jlong vibratorPerformEffect(JNIEnv* env, jclass /* clazz */, jlong effect, jlong strength,
-                                   jobject vibration, jboolean withCallback) {
-    if (auto hal = getHal<aidl::IVibrator>()) {
-        int32_t lengthMs;
-        sp<AidlVibratorCallback> effectCallback =
-                (withCallback != JNI_FALSE ? new AidlVibratorCallback(env, vibration) : nullptr);
-        aidl::Effect effectType(static_cast<aidl::Effect>(effect));
-        aidl::EffectStrength effectStrength(static_cast<aidl::EffectStrength>(strength));
-
-        auto status = hal->call(&aidl::IVibrator::perform, effectType, effectStrength, effectCallback, &lengthMs);
-        if (!status.isOk()) {
-            if (status.exceptionCode() != binder::Status::EX_UNSUPPORTED_OPERATION) {
-                ALOGE("Failed to perform haptic effect: effect=%" PRId64 ", strength=%" PRId32
-                        ": %s", static_cast<int64_t>(effect), static_cast<int32_t>(strength), status.toString8().string());
-            }
-            return -1;
-        }
-        return lengthMs;
-    } else {
-        Status status;
-        uint32_t lengthMs;
-        auto callback = [&status, &lengthMs](Status retStatus, uint32_t retLengthMs) {
-            status = retStatus;
-            lengthMs = retLengthMs;
-        };
-        EffectStrength effectStrength(static_cast<EffectStrength>(strength));
-
-        Return<void> ret;
-        if (isValidEffect<V1_0::Effect>(effect)) {
-            ret = halCall(&V1_0::IVibrator::perform, static_cast<V1_0::Effect>(effect),
-                    effectStrength, callback);
-        } else if (isValidEffect<Effect_1_1>(effect)) {
-            ret = halCall(&V1_1::IVibrator::perform_1_1, static_cast<Effect_1_1>(effect),
-                            effectStrength, callback);
-        } else if (isValidEffect<V1_2::Effect>(effect)) {
-            ret = halCall(&V1_2::IVibrator::perform_1_2, static_cast<V1_2::Effect>(effect),
-                            effectStrength, callback);
-        } else if (isValidEffect<V1_3::Effect>(effect)) {
-            ret = halCall(&V1_3::IVibrator::perform_1_3, static_cast<V1_3::Effect>(effect),
-                            effectStrength, callback);
-        } else {
-            ALOGW("Unable to perform haptic effect, invalid effect ID (%" PRId32 ")",
-                    static_cast<int32_t>(effect));
-            return -1;
-        }
-
-        if (!ret.isOk()) {
-            ALOGW("Failed to perform effect (%" PRId32 ")", static_cast<int32_t>(effect));
-            return -1;
-        }
-
-        if (status == Status::OK) {
-            return lengthMs;
-        } else if (status != Status::UNSUPPORTED_OPERATION) {
-            // Don't warn on UNSUPPORTED_OPERATION, that's a normal event and just means the motor
-            // doesn't have a pre-defined waveform to perform for it, so we should just give the
-            // opportunity to fall back to the framework waveforms.
-            ALOGE("Failed to perform haptic effect: effect=%" PRId64 ", strength=%" PRId32
-                    ", error=%" PRIu32 ").", static_cast<int64_t>(effect),
-                    static_cast<int32_t>(strength), static_cast<uint32_t>(status));
-        }
+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;
+}
 
-    return -1;
+static jlong vibratorPerformEffect(JNIEnv* env, jclass /* clazz */, jlong controllerPtr,
+                                   jlong effect, jlong strength, jobject vibration) {
+    vibrator::HalController* controller = reinterpret_cast<vibrator::HalController*>(controllerPtr);
+    if (controller == nullptr) {
+        ALOGE("vibratorPerformEffect failed because controller was not initialized");
+        return -1;
+    }
+    aidl::Effect effectType = static_cast<aidl::Effect>(effect);
+    aidl::EffectStrength effectStrength = static_cast<aidl::EffectStrength>(strength);
+    jobject vibrationRef = vibration == nullptr ? vibration : MakeGlobalRefOrDie(env, vibration);
+    auto callback = [vibrationRef]() { callVibrationOnComplete(vibrationRef); };
+    auto result = controller->performEffect(effectType, effectStrength, callback);
+    return result.isOk() ? result.value().count() : -1;
 }
 
 static void vibratorPerformComposedEffect(JNIEnv* env, jclass /* clazz */, jlong controllerPtr,
@@ -464,13 +270,14 @@
         {"vibratorOn", "(JJLcom/android/server/VibratorService$Vibration;)V", (void*)vibratorOn},
         {"vibratorOff", "(J)V", (void*)vibratorOff},
         {"vibratorSetAmplitude", "(JI)V", (void*)vibratorSetAmplitude},
-        {"vibratorPerformEffect", "(JJLcom/android/server/VibratorService$Vibration;Z)J",
+        {"vibratorPerformEffect", "(JJJLcom/android/server/VibratorService$Vibration;)J",
          (void*)vibratorPerformEffect},
         {"vibratorPerformComposedEffect",
          "(J[Landroid/os/VibrationEffect$Composition$PrimitiveEffect;Lcom/android/server/"
          "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/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
new file mode 100644
index 0000000..6fea2aa
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
@@ -0,0 +1,1116 @@
+/*
+ * 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 static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+
+import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
+import static org.xmlpull.v1.XmlPullParser.END_TAG;
+import static org.xmlpull.v1.XmlPullParser.TEXT;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.admin.DeviceAdminInfo;
+import android.app.admin.DevicePolicyManager;
+import android.app.admin.FactoryResetProtectionPolicy;
+import android.app.admin.PasswordPolicy;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.Preconditions;
+import com.android.internal.util.XmlUtils;
+import com.android.server.pm.UserRestrictionsUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Predicate;
+
+class ActiveAdmin {
+    private static final String TAG_DISABLE_KEYGUARD_FEATURES = "disable-keyguard-features";
+    private static final String TAG_TEST_ONLY_ADMIN = "test-only-admin";
+    private static final String TAG_DISABLE_CAMERA = "disable-camera";
+    private static final String TAG_DISABLE_CALLER_ID = "disable-caller-id";
+    private static final String TAG_DISABLE_CONTACTS_SEARCH = "disable-contacts-search";
+    private static final String TAG_DISABLE_BLUETOOTH_CONTACT_SHARING =
+            "disable-bt-contacts-sharing";
+    private static final String TAG_DISABLE_SCREEN_CAPTURE = "disable-screen-capture";
+    private static final String TAG_DISABLE_ACCOUNT_MANAGEMENT = "disable-account-management";
+    private static final String TAG_REQUIRE_AUTO_TIME = "require_auto_time";
+    private static final String TAG_FORCE_EPHEMERAL_USERS = "force_ephemeral_users";
+    private static final String TAG_IS_NETWORK_LOGGING_ENABLED = "is_network_logging_enabled";
+    private static final String TAG_ACCOUNT_TYPE = "account-type";
+    private static final String TAG_PERMITTED_ACCESSIBILITY_SERVICES =
+            "permitted-accessiblity-services";
+    private static final String TAG_ENCRYPTION_REQUESTED = "encryption-requested";
+    private static final String TAG_MANAGE_TRUST_AGENT_FEATURES = "manage-trust-agent-features";
+    private static final String TAG_TRUST_AGENT_COMPONENT_OPTIONS = "trust-agent-component-options";
+    private static final String TAG_TRUST_AGENT_COMPONENT = "component";
+    private static final String TAG_PASSWORD_EXPIRATION_DATE = "password-expiration-date";
+    private static final String TAG_PASSWORD_EXPIRATION_TIMEOUT = "password-expiration-timeout";
+    private static final String TAG_GLOBAL_PROXY_EXCLUSION_LIST = "global-proxy-exclusion-list";
+    private static final String TAG_GLOBAL_PROXY_SPEC = "global-proxy-spec";
+    private static final String TAG_SPECIFIES_GLOBAL_PROXY = "specifies-global-proxy";
+    private static final String TAG_PERMITTED_IMES = "permitted-imes";
+    private static final String TAG_PERMITTED_NOTIFICATION_LISTENERS =
+            "permitted-notification-listeners";
+    private static final String TAG_MAX_FAILED_PASSWORD_WIPE = "max-failed-password-wipe";
+    private static final String TAG_MAX_TIME_TO_UNLOCK = "max-time-to-unlock";
+    private static final String TAG_STRONG_AUTH_UNLOCK_TIMEOUT = "strong-auth-unlock-timeout";
+    private static final String TAG_MIN_PASSWORD_NONLETTER = "min-password-nonletter";
+    private static final String TAG_MIN_PASSWORD_SYMBOLS = "min-password-symbols";
+    private static final String TAG_MIN_PASSWORD_NUMERIC = "min-password-numeric";
+    private static final String TAG_MIN_PASSWORD_LETTERS = "min-password-letters";
+    private static final String TAG_MIN_PASSWORD_LOWERCASE = "min-password-lowercase";
+    private static final String TAG_MIN_PASSWORD_UPPERCASE = "min-password-uppercase";
+    private static final String TAG_PASSWORD_HISTORY_LENGTH = "password-history-length";
+    private static final String TAG_MIN_PASSWORD_LENGTH = "min-password-length";
+    private static final String TAG_PASSWORD_QUALITY = "password-quality";
+    private static final String TAG_POLICIES = "policies";
+    private static final String TAG_CROSS_PROFILE_WIDGET_PROVIDERS =
+            "cross-profile-widget-providers";
+    private static final String TAG_PROVIDER = "provider";
+    private static final String TAG_PACKAGE_LIST_ITEM  = "item";
+    private static final String TAG_KEEP_UNINSTALLED_PACKAGES  = "keep-uninstalled-packages";
+    private static final String TAG_USER_RESTRICTIONS = "user-restrictions";
+    private static final String TAG_DEFAULT_ENABLED_USER_RESTRICTIONS =
+            "default-enabled-user-restrictions";
+    private static final String TAG_RESTRICTION = "restriction";
+    private static final String TAG_SHORT_SUPPORT_MESSAGE = "short-support-message";
+    private static final String TAG_LONG_SUPPORT_MESSAGE = "long-support-message";
+    private static final String TAG_PARENT_ADMIN = "parent-admin";
+    private static final String TAG_ORGANIZATION_COLOR = "organization-color";
+    private static final String TAG_ORGANIZATION_NAME = "organization-name";
+    private static final String TAG_IS_LOGOUT_ENABLED = "is_logout_enabled";
+    private static final String TAG_START_USER_SESSION_MESSAGE = "start_user_session_message";
+    private static final String TAG_END_USER_SESSION_MESSAGE = "end_user_session_message";
+    private static final String TAG_METERED_DATA_DISABLED_PACKAGES =
+            "metered_data_disabled_packages";
+    private static final String TAG_CROSS_PROFILE_CALENDAR_PACKAGES =
+            "cross-profile-calendar-packages";
+    private static final String TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL =
+            "cross-profile-calendar-packages-null";
+    private static final String TAG_CROSS_PROFILE_PACKAGES = "cross-profile-packages";
+    private static final String TAG_FACTORY_RESET_PROTECTION_POLICY =
+            "factory_reset_protection_policy";
+    private static final String TAG_SUSPEND_PERSONAL_APPS = "suspend-personal-apps";
+    private static final String TAG_PROFILE_MAXIMUM_TIME_OFF = "profile-max-time-off";
+    private static final String TAG_PROFILE_OFF_DEADLINE = "profile-off-deadline";
+    private static final String TAG_ALWAYS_ON_VPN_PACKAGE = "vpn-package";
+    private static final String TAG_ALWAYS_ON_VPN_LOCKDOWN = "vpn-lockdown";
+    private static final String TAG_COMMON_CRITERIA_MODE = "common-criteria-mode";
+    private static final String ATTR_VALUE = "value";
+    private static final String ATTR_LAST_NETWORK_LOGGING_NOTIFICATION = "last-notification";
+    private static final String ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS = "num-notifications";
+
+    DeviceAdminInfo info;
+
+    static final int DEF_PASSWORD_HISTORY_LENGTH = 0;
+    int passwordHistoryLength = DEF_PASSWORD_HISTORY_LENGTH;
+
+    @NonNull
+    PasswordPolicy mPasswordPolicy = new PasswordPolicy();
+
+    @Nullable
+    FactoryResetProtectionPolicy mFactoryResetProtectionPolicy = null;
+
+    static final long DEF_MAXIMUM_TIME_TO_UNLOCK = 0;
+    long maximumTimeToUnlock = DEF_MAXIMUM_TIME_TO_UNLOCK;
+
+    long strongAuthUnlockTimeout = 0; // admin doesn't participate by default
+
+    static final int DEF_MAXIMUM_FAILED_PASSWORDS_FOR_WIPE = 0;
+    int maximumFailedPasswordsForWipe = DEF_MAXIMUM_FAILED_PASSWORDS_FOR_WIPE;
+
+    static final long DEF_PASSWORD_EXPIRATION_TIMEOUT = 0;
+    long passwordExpirationTimeout = DEF_PASSWORD_EXPIRATION_TIMEOUT;
+
+    static final long DEF_PASSWORD_EXPIRATION_DATE = 0;
+    long passwordExpirationDate = DEF_PASSWORD_EXPIRATION_DATE;
+
+    static final int DEF_KEYGUARD_FEATURES_DISABLED = 0; // none
+
+    int disabledKeyguardFeatures = DEF_KEYGUARD_FEATURES_DISABLED;
+
+    boolean encryptionRequested = false;
+    boolean testOnlyAdmin = false;
+    boolean disableCamera = false;
+    boolean disableCallerId = false;
+    boolean disableContactsSearch = false;
+    boolean disableBluetoothContactSharing = true;
+    boolean disableScreenCapture = false;
+    boolean requireAutoTime = false;
+    boolean forceEphemeralUsers = false;
+    boolean isNetworkLoggingEnabled = false;
+    boolean isLogoutEnabled = false;
+
+    // one notification after enabling + one more after reboots
+    static final int DEF_MAXIMUM_NETWORK_LOGGING_NOTIFICATIONS_SHOWN = 2;
+    int numNetworkLoggingNotifications = 0;
+    long lastNetworkLoggingNotificationTimeMs = 0; // Time in milliseconds since epoch
+
+    ActiveAdmin parentAdmin;
+    final boolean isParent;
+
+    static class TrustAgentInfo {
+        public PersistableBundle options;
+        TrustAgentInfo(PersistableBundle bundle) {
+            options = bundle;
+        }
+    }
+
+    // The list of packages which are not allowed to use metered data.
+    List<String> meteredDisabledPackages;
+
+    final Set<String> accountTypesWithManagementDisabled = new ArraySet<>();
+
+    // The list of permitted accessibility services package namesas set by a profile
+    // or device owner. Null means all accessibility services are allowed, empty means
+    // none except system services are allowed.
+    List<String> permittedAccessiblityServices;
+
+    // The list of permitted input methods package names as set by a profile or device owner.
+    // Null means all input methods are allowed, empty means none except system imes are
+    // allowed.
+    List<String> permittedInputMethods;
+
+    // The list of packages allowed to use a NotificationListenerService to receive events for
+    // notifications from this user. Null means that all packages are allowed. Empty list means
+    // that only packages from the system are allowed.
+    List<String> permittedNotificationListeners;
+
+    // List of package names to keep cached.
+    List<String> keepUninstalledPackages;
+
+    // TODO: review implementation decisions with frameworks team
+    boolean specifiesGlobalProxy = false;
+    String globalProxySpec = null;
+    String globalProxyExclusionList = null;
+
+    @NonNull
+    ArrayMap<String, TrustAgentInfo> trustAgentInfos = new ArrayMap<>();
+
+    List<String> crossProfileWidgetProviders;
+
+    Bundle userRestrictions;
+
+    // User restrictions that have already been enabled by default for this admin (either when
+    // setting the device or profile owner, or during a system update if one of those "enabled
+    // by default" restrictions is newly added).
+    final Set<String> defaultEnabledRestrictionsAlreadySet = new ArraySet<>();
+
+    // Support text provided by the admin to display to the user.
+    CharSequence shortSupportMessage = null;
+    CharSequence longSupportMessage = null;
+
+    // Background color of confirm credentials screen. Default: teal.
+    static final int DEF_ORGANIZATION_COLOR = Color.parseColor("#00796B");
+    int organizationColor = DEF_ORGANIZATION_COLOR;
+
+    // Default title of confirm credentials screen
+    String organizationName = null;
+
+    // Message for user switcher
+    String startUserSessionMessage = null;
+    String endUserSessionMessage = null;
+
+    // The allow list of packages that can access cross profile calendar APIs.
+    // This allow list should be in default an empty list, which indicates that no package
+    // is allow listed.
+    List<String> mCrossProfileCalendarPackages = Collections.emptyList();
+
+    // The allow list of packages that the admin has enabled to be able to request consent from
+    // the user to communicate cross-profile. By default, no packages are allowed, which is
+    // represented as an empty list.
+    List<String> mCrossProfilePackages = Collections.emptyList();
+
+    // Whether the admin explicitly requires personal apps to be suspended
+    boolean mSuspendPersonalApps = false;
+    // Maximum time the profile owned by this admin can be off.
+    long mProfileMaximumTimeOffMillis = 0;
+    // Time by which the profile should be turned on according to System.currentTimeMillis().
+    long mProfileOffDeadline = 0;
+
+    public String mAlwaysOnVpnPackage;
+    public boolean mAlwaysOnVpnLockdown;
+    boolean mCommonCriteriaMode;
+
+    ActiveAdmin(DeviceAdminInfo info, boolean isParent) {
+        this.info = info;
+        this.isParent = isParent;
+    }
+
+    ActiveAdmin getParentActiveAdmin() {
+        Preconditions.checkState(!isParent);
+
+        if (parentAdmin == null) {
+            parentAdmin = new ActiveAdmin(info, /* parent */ true);
+        }
+        return parentAdmin;
+    }
+
+    boolean hasParentActiveAdmin() {
+        return parentAdmin != null;
+    }
+
+    int getUid() {
+        return info.getActivityInfo().applicationInfo.uid;
+    }
+
+    public UserHandle getUserHandle() {
+        return UserHandle.of(UserHandle.getUserId(info.getActivityInfo().applicationInfo.uid));
+    }
+
+    void writeToXml(XmlSerializer out)
+            throws IllegalArgumentException, IllegalStateException, IOException {
+        out.startTag(null, TAG_POLICIES);
+        info.writePoliciesToXml(out);
+        out.endTag(null, TAG_POLICIES);
+        if (mPasswordPolicy.quality != PASSWORD_QUALITY_UNSPECIFIED) {
+            writeAttributeValueToXml(
+                    out, TAG_PASSWORD_QUALITY, mPasswordPolicy.quality);
+            if (mPasswordPolicy.length != PasswordPolicy.DEF_MINIMUM_LENGTH) {
+                writeAttributeValueToXml(
+                        out, TAG_MIN_PASSWORD_LENGTH, mPasswordPolicy.length);
+            }
+            if (mPasswordPolicy.upperCase != PasswordPolicy.DEF_MINIMUM_UPPER_CASE) {
+                writeAttributeValueToXml(
+                        out, TAG_MIN_PASSWORD_UPPERCASE, mPasswordPolicy.upperCase);
+            }
+            if (mPasswordPolicy.lowerCase != PasswordPolicy.DEF_MINIMUM_LOWER_CASE) {
+                writeAttributeValueToXml(
+                        out, TAG_MIN_PASSWORD_LOWERCASE, mPasswordPolicy.lowerCase);
+            }
+            if (mPasswordPolicy.letters != PasswordPolicy.DEF_MINIMUM_LETTERS) {
+                writeAttributeValueToXml(
+                        out, TAG_MIN_PASSWORD_LETTERS, mPasswordPolicy.letters);
+            }
+            if (mPasswordPolicy.numeric != PasswordPolicy.DEF_MINIMUM_NUMERIC) {
+                writeAttributeValueToXml(
+                        out, TAG_MIN_PASSWORD_NUMERIC, mPasswordPolicy.numeric);
+            }
+            if (mPasswordPolicy.symbols != PasswordPolicy.DEF_MINIMUM_SYMBOLS) {
+                writeAttributeValueToXml(
+                        out, TAG_MIN_PASSWORD_SYMBOLS, mPasswordPolicy.symbols);
+            }
+            if (mPasswordPolicy.nonLetter > PasswordPolicy.DEF_MINIMUM_NON_LETTER) {
+                writeAttributeValueToXml(
+                        out, TAG_MIN_PASSWORD_NONLETTER, mPasswordPolicy.nonLetter);
+            }
+        }
+        if (passwordHistoryLength != DEF_PASSWORD_HISTORY_LENGTH) {
+            writeAttributeValueToXml(
+                    out, TAG_PASSWORD_HISTORY_LENGTH, passwordHistoryLength);
+        }
+        if (maximumTimeToUnlock != DEF_MAXIMUM_TIME_TO_UNLOCK) {
+            writeAttributeValueToXml(
+                    out, TAG_MAX_TIME_TO_UNLOCK, maximumTimeToUnlock);
+        }
+        if (strongAuthUnlockTimeout != DevicePolicyManager.DEFAULT_STRONG_AUTH_TIMEOUT_MS) {
+            writeAttributeValueToXml(
+                    out, TAG_STRONG_AUTH_UNLOCK_TIMEOUT, strongAuthUnlockTimeout);
+        }
+        if (maximumFailedPasswordsForWipe != DEF_MAXIMUM_FAILED_PASSWORDS_FOR_WIPE) {
+            writeAttributeValueToXml(
+                    out, TAG_MAX_FAILED_PASSWORD_WIPE, maximumFailedPasswordsForWipe);
+        }
+        if (specifiesGlobalProxy) {
+            writeAttributeValueToXml(
+                    out, TAG_SPECIFIES_GLOBAL_PROXY, specifiesGlobalProxy);
+            if (globalProxySpec != null) {
+                writeAttributeValueToXml(out, TAG_GLOBAL_PROXY_SPEC, globalProxySpec);
+            }
+            if (globalProxyExclusionList != null) {
+                writeAttributeValueToXml(
+                        out, TAG_GLOBAL_PROXY_EXCLUSION_LIST, globalProxyExclusionList);
+            }
+        }
+        if (passwordExpirationTimeout != DEF_PASSWORD_EXPIRATION_TIMEOUT) {
+            writeAttributeValueToXml(
+                    out, TAG_PASSWORD_EXPIRATION_TIMEOUT, passwordExpirationTimeout);
+        }
+        if (passwordExpirationDate != DEF_PASSWORD_EXPIRATION_DATE) {
+            writeAttributeValueToXml(
+                    out, TAG_PASSWORD_EXPIRATION_DATE, passwordExpirationDate);
+        }
+        if (encryptionRequested) {
+            writeAttributeValueToXml(
+                    out, TAG_ENCRYPTION_REQUESTED, encryptionRequested);
+        }
+        if (testOnlyAdmin) {
+            writeAttributeValueToXml(
+                    out, TAG_TEST_ONLY_ADMIN, testOnlyAdmin);
+        }
+        if (disableCamera) {
+            writeAttributeValueToXml(
+                    out, TAG_DISABLE_CAMERA, disableCamera);
+        }
+        if (disableCallerId) {
+            writeAttributeValueToXml(
+                    out, TAG_DISABLE_CALLER_ID, disableCallerId);
+        }
+        if (disableContactsSearch) {
+            writeAttributeValueToXml(
+                    out, TAG_DISABLE_CONTACTS_SEARCH, disableContactsSearch);
+        }
+        if (!disableBluetoothContactSharing) {
+            writeAttributeValueToXml(
+                    out, TAG_DISABLE_BLUETOOTH_CONTACT_SHARING, disableBluetoothContactSharing);
+        }
+        if (disableScreenCapture) {
+            writeAttributeValueToXml(
+                    out, TAG_DISABLE_SCREEN_CAPTURE, disableScreenCapture);
+        }
+        if (requireAutoTime) {
+            writeAttributeValueToXml(
+                    out, TAG_REQUIRE_AUTO_TIME, requireAutoTime);
+        }
+        if (forceEphemeralUsers) {
+            writeAttributeValueToXml(
+                    out, TAG_FORCE_EPHEMERAL_USERS, forceEphemeralUsers);
+        }
+        if (isNetworkLoggingEnabled) {
+            out.startTag(null, TAG_IS_NETWORK_LOGGING_ENABLED);
+            out.attribute(null, ATTR_VALUE, Boolean.toString(isNetworkLoggingEnabled));
+            out.attribute(null, ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS,
+                    Integer.toString(numNetworkLoggingNotifications));
+            out.attribute(null, ATTR_LAST_NETWORK_LOGGING_NOTIFICATION,
+                    Long.toString(lastNetworkLoggingNotificationTimeMs));
+            out.endTag(null, TAG_IS_NETWORK_LOGGING_ENABLED);
+        }
+        if (disabledKeyguardFeatures != DEF_KEYGUARD_FEATURES_DISABLED) {
+            writeAttributeValueToXml(
+                    out, TAG_DISABLE_KEYGUARD_FEATURES, disabledKeyguardFeatures);
+        }
+        if (!accountTypesWithManagementDisabled.isEmpty()) {
+            writeAttributeValuesToXml(
+                    out, TAG_DISABLE_ACCOUNT_MANAGEMENT, TAG_ACCOUNT_TYPE,
+                    accountTypesWithManagementDisabled);
+        }
+        if (!trustAgentInfos.isEmpty()) {
+            Set<Map.Entry<String, TrustAgentInfo>> set = trustAgentInfos.entrySet();
+            out.startTag(null, TAG_MANAGE_TRUST_AGENT_FEATURES);
+            for (Map.Entry<String, TrustAgentInfo> entry : set) {
+                TrustAgentInfo trustAgentInfo = entry.getValue();
+                out.startTag(null, TAG_TRUST_AGENT_COMPONENT);
+                out.attribute(null, ATTR_VALUE, entry.getKey());
+                if (trustAgentInfo.options != null) {
+                    out.startTag(null, TAG_TRUST_AGENT_COMPONENT_OPTIONS);
+                    try {
+                        trustAgentInfo.options.saveToXml(out);
+                    } catch (XmlPullParserException e) {
+                        Log.e(DevicePolicyManagerService.LOG_TAG,
+                                "Failed to save TrustAgent options", e);
+                    }
+                    out.endTag(null, TAG_TRUST_AGENT_COMPONENT_OPTIONS);
+                }
+                out.endTag(null, TAG_TRUST_AGENT_COMPONENT);
+            }
+            out.endTag(null, TAG_MANAGE_TRUST_AGENT_FEATURES);
+        }
+        if (crossProfileWidgetProviders != null && !crossProfileWidgetProviders.isEmpty()) {
+            writeAttributeValuesToXml(
+                    out, TAG_CROSS_PROFILE_WIDGET_PROVIDERS, TAG_PROVIDER,
+                    crossProfileWidgetProviders);
+        }
+        writePackageListToXml(out, TAG_PERMITTED_ACCESSIBILITY_SERVICES,
+                permittedAccessiblityServices);
+        writePackageListToXml(out, TAG_PERMITTED_IMES, permittedInputMethods);
+        writePackageListToXml(out, TAG_PERMITTED_NOTIFICATION_LISTENERS,
+                permittedNotificationListeners);
+        writePackageListToXml(out, TAG_KEEP_UNINSTALLED_PACKAGES, keepUninstalledPackages);
+        writePackageListToXml(out, TAG_METERED_DATA_DISABLED_PACKAGES, meteredDisabledPackages);
+        if (hasUserRestrictions()) {
+            UserRestrictionsUtils.writeRestrictions(
+                    out, userRestrictions, TAG_USER_RESTRICTIONS);
+        }
+        if (!defaultEnabledRestrictionsAlreadySet.isEmpty()) {
+            writeAttributeValuesToXml(out, TAG_DEFAULT_ENABLED_USER_RESTRICTIONS,
+                    TAG_RESTRICTION,
+                    defaultEnabledRestrictionsAlreadySet);
+        }
+        if (!TextUtils.isEmpty(shortSupportMessage)) {
+            writeTextToXml(out, TAG_SHORT_SUPPORT_MESSAGE, shortSupportMessage.toString());
+        }
+        if (!TextUtils.isEmpty(longSupportMessage)) {
+            writeTextToXml(out, TAG_LONG_SUPPORT_MESSAGE, longSupportMessage.toString());
+        }
+        if (parentAdmin != null) {
+            out.startTag(null, TAG_PARENT_ADMIN);
+            parentAdmin.writeToXml(out);
+            out.endTag(null, TAG_PARENT_ADMIN);
+        }
+        if (organizationColor != DEF_ORGANIZATION_COLOR) {
+            writeAttributeValueToXml(out, TAG_ORGANIZATION_COLOR, organizationColor);
+        }
+        if (organizationName != null) {
+            writeTextToXml(out, TAG_ORGANIZATION_NAME, organizationName);
+        }
+        if (isLogoutEnabled) {
+            writeAttributeValueToXml(out, TAG_IS_LOGOUT_ENABLED, isLogoutEnabled);
+        }
+        if (startUserSessionMessage != null) {
+            writeTextToXml(out, TAG_START_USER_SESSION_MESSAGE, startUserSessionMessage);
+        }
+        if (endUserSessionMessage != null) {
+            writeTextToXml(out, TAG_END_USER_SESSION_MESSAGE, endUserSessionMessage);
+        }
+        if (mCrossProfileCalendarPackages == null) {
+            out.startTag(null, TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL);
+            out.endTag(null, TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL);
+        } else {
+            writePackageListToXml(out, TAG_CROSS_PROFILE_CALENDAR_PACKAGES,
+                    mCrossProfileCalendarPackages);
+        }
+        writePackageListToXml(out, TAG_CROSS_PROFILE_PACKAGES, mCrossProfilePackages);
+        if (mFactoryResetProtectionPolicy != null) {
+            out.startTag(null, TAG_FACTORY_RESET_PROTECTION_POLICY);
+            mFactoryResetProtectionPolicy.writeToXml(out);
+            out.endTag(null, TAG_FACTORY_RESET_PROTECTION_POLICY);
+        }
+        if (mSuspendPersonalApps) {
+            writeAttributeValueToXml(out, TAG_SUSPEND_PERSONAL_APPS, mSuspendPersonalApps);
+        }
+        if (mProfileMaximumTimeOffMillis != 0) {
+            writeAttributeValueToXml(out, TAG_PROFILE_MAXIMUM_TIME_OFF,
+                    mProfileMaximumTimeOffMillis);
+        }
+        if (mProfileMaximumTimeOffMillis != 0) {
+            writeAttributeValueToXml(out, TAG_PROFILE_OFF_DEADLINE, mProfileOffDeadline);
+        }
+        if (!TextUtils.isEmpty(mAlwaysOnVpnPackage)) {
+            writeAttributeValueToXml(out, TAG_ALWAYS_ON_VPN_PACKAGE, mAlwaysOnVpnPackage);
+        }
+        if (mAlwaysOnVpnLockdown) {
+            writeAttributeValueToXml(out, TAG_ALWAYS_ON_VPN_LOCKDOWN, mAlwaysOnVpnLockdown);
+        }
+        if (mCommonCriteriaMode) {
+            writeAttributeValueToXml(out, TAG_COMMON_CRITERIA_MODE, mCommonCriteriaMode);
+        }
+    }
+
+    void writeTextToXml(XmlSerializer out, String tag, String text) throws IOException {
+        out.startTag(null, tag);
+        out.text(text);
+        out.endTag(null, tag);
+    }
+
+    void writePackageListToXml(XmlSerializer out, String outerTag,
+            List<String> packageList)
+            throws IllegalArgumentException, IllegalStateException, IOException {
+        if (packageList == null) {
+            return;
+        }
+        writeAttributeValuesToXml(out, outerTag, TAG_PACKAGE_LIST_ITEM, packageList);
+    }
+
+    void writeAttributeValueToXml(XmlSerializer out, String tag, String value)
+            throws IOException {
+        out.startTag(null, tag);
+        out.attribute(null, ATTR_VALUE, value);
+        out.endTag(null, tag);
+    }
+
+    void writeAttributeValueToXml(XmlSerializer out, String tag, int value)
+            throws IOException {
+        out.startTag(null, tag);
+        out.attribute(null, ATTR_VALUE, Integer.toString(value));
+        out.endTag(null, tag);
+    }
+
+    void writeAttributeValueToXml(XmlSerializer out, String tag, long value)
+            throws IOException {
+        out.startTag(null, tag);
+        out.attribute(null, ATTR_VALUE, Long.toString(value));
+        out.endTag(null, tag);
+    }
+
+    void writeAttributeValueToXml(XmlSerializer out, String tag, boolean value)
+            throws IOException {
+        out.startTag(null, tag);
+        out.attribute(null, ATTR_VALUE, Boolean.toString(value));
+        out.endTag(null, tag);
+    }
+
+    void writeAttributeValuesToXml(XmlSerializer out, String outerTag, String innerTag,
+            @NonNull Collection<String> values) throws IOException {
+        out.startTag(null, outerTag);
+        for (String value : values) {
+            out.startTag(null, innerTag);
+            out.attribute(null, ATTR_VALUE, value);
+            out.endTag(null, innerTag);
+        }
+        out.endTag(null, outerTag);
+    }
+
+    void readFromXml(XmlPullParser parser, boolean shouldOverridePolicies)
+            throws XmlPullParserException, IOException {
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != END_DOCUMENT
+               && (type != END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == END_TAG || type == TEXT) {
+                continue;
+            }
+            String tag = parser.getName();
+            if (TAG_POLICIES.equals(tag)) {
+                if (shouldOverridePolicies) {
+                    Log.d(DevicePolicyManagerService.LOG_TAG,
+                            "Overriding device admin policies from XML.");
+                    info.readPoliciesFromXml(parser);
+                }
+            } else if (TAG_PASSWORD_QUALITY.equals(tag)) {
+                mPasswordPolicy.quality = Integer.parseInt(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_MIN_PASSWORD_LENGTH.equals(tag)) {
+                mPasswordPolicy.length = Integer.parseInt(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_PASSWORD_HISTORY_LENGTH.equals(tag)) {
+                passwordHistoryLength = Integer.parseInt(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_MIN_PASSWORD_UPPERCASE.equals(tag)) {
+                mPasswordPolicy.upperCase = Integer.parseInt(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_MIN_PASSWORD_LOWERCASE.equals(tag)) {
+                mPasswordPolicy.lowerCase = Integer.parseInt(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_MIN_PASSWORD_LETTERS.equals(tag)) {
+                mPasswordPolicy.letters = Integer.parseInt(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_MIN_PASSWORD_NUMERIC.equals(tag)) {
+                mPasswordPolicy.numeric = Integer.parseInt(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_MIN_PASSWORD_SYMBOLS.equals(tag)) {
+                mPasswordPolicy.symbols = Integer.parseInt(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_MIN_PASSWORD_NONLETTER.equals(tag)) {
+                mPasswordPolicy.nonLetter = Integer.parseInt(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_MAX_TIME_TO_UNLOCK.equals(tag)) {
+                maximumTimeToUnlock = Long.parseLong(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_STRONG_AUTH_UNLOCK_TIMEOUT.equals(tag)) {
+                strongAuthUnlockTimeout = Long.parseLong(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_MAX_FAILED_PASSWORD_WIPE.equals(tag)) {
+                maximumFailedPasswordsForWipe = Integer.parseInt(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_SPECIFIES_GLOBAL_PROXY.equals(tag)) {
+                specifiesGlobalProxy = Boolean.parseBoolean(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_GLOBAL_PROXY_SPEC.equals(tag)) {
+                globalProxySpec =
+                    parser.getAttributeValue(null, ATTR_VALUE);
+            } else if (TAG_GLOBAL_PROXY_EXCLUSION_LIST.equals(tag)) {
+                globalProxyExclusionList =
+                    parser.getAttributeValue(null, ATTR_VALUE);
+            } else if (TAG_PASSWORD_EXPIRATION_TIMEOUT.equals(tag)) {
+                passwordExpirationTimeout = Long.parseLong(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_PASSWORD_EXPIRATION_DATE.equals(tag)) {
+                passwordExpirationDate = Long.parseLong(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_ENCRYPTION_REQUESTED.equals(tag)) {
+                encryptionRequested = Boolean.parseBoolean(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_TEST_ONLY_ADMIN.equals(tag)) {
+                testOnlyAdmin = Boolean.parseBoolean(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_DISABLE_CAMERA.equals(tag)) {
+                disableCamera = Boolean.parseBoolean(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_DISABLE_CALLER_ID.equals(tag)) {
+                disableCallerId = Boolean.parseBoolean(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_DISABLE_CONTACTS_SEARCH.equals(tag)) {
+                disableContactsSearch = Boolean.parseBoolean(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_DISABLE_BLUETOOTH_CONTACT_SHARING.equals(tag)) {
+                disableBluetoothContactSharing = Boolean.parseBoolean(parser
+                        .getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_DISABLE_SCREEN_CAPTURE.equals(tag)) {
+                disableScreenCapture = Boolean.parseBoolean(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_REQUIRE_AUTO_TIME.equals(tag)) {
+                requireAutoTime = Boolean.parseBoolean(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_FORCE_EPHEMERAL_USERS.equals(tag)) {
+                forceEphemeralUsers = Boolean.parseBoolean(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_IS_NETWORK_LOGGING_ENABLED.equals(tag)) {
+                isNetworkLoggingEnabled = Boolean.parseBoolean(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+                lastNetworkLoggingNotificationTimeMs = Long.parseLong(
+                        parser.getAttributeValue(null, ATTR_LAST_NETWORK_LOGGING_NOTIFICATION));
+                numNetworkLoggingNotifications = Integer.parseInt(
+                        parser.getAttributeValue(null, ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS));
+            } else if (TAG_DISABLE_KEYGUARD_FEATURES.equals(tag)) {
+                disabledKeyguardFeatures = Integer.parseInt(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_DISABLE_ACCOUNT_MANAGEMENT.equals(tag)) {
+                readAttributeValues(
+                        parser, TAG_ACCOUNT_TYPE, accountTypesWithManagementDisabled);
+            } else if (TAG_MANAGE_TRUST_AGENT_FEATURES.equals(tag)) {
+                trustAgentInfos = getAllTrustAgentInfos(parser, tag);
+            } else if (TAG_CROSS_PROFILE_WIDGET_PROVIDERS.equals(tag)) {
+                crossProfileWidgetProviders = new ArrayList<>();
+                readAttributeValues(parser, TAG_PROVIDER, crossProfileWidgetProviders);
+            } else if (TAG_PERMITTED_ACCESSIBILITY_SERVICES.equals(tag)) {
+                permittedAccessiblityServices = readPackageList(parser, tag);
+            } else if (TAG_PERMITTED_IMES.equals(tag)) {
+                permittedInputMethods = readPackageList(parser, tag);
+            } else if (TAG_PERMITTED_NOTIFICATION_LISTENERS.equals(tag)) {
+                permittedNotificationListeners = readPackageList(parser, tag);
+            } else if (TAG_KEEP_UNINSTALLED_PACKAGES.equals(tag)) {
+                keepUninstalledPackages = readPackageList(parser, tag);
+            } else if (TAG_METERED_DATA_DISABLED_PACKAGES.equals(tag)) {
+                meteredDisabledPackages = readPackageList(parser, tag);
+            } else if (TAG_USER_RESTRICTIONS.equals(tag)) {
+                userRestrictions = UserRestrictionsUtils.readRestrictions(parser);
+            } else if (TAG_DEFAULT_ENABLED_USER_RESTRICTIONS.equals(tag)) {
+                readAttributeValues(
+                        parser, TAG_RESTRICTION, defaultEnabledRestrictionsAlreadySet);
+            } else if (TAG_SHORT_SUPPORT_MESSAGE.equals(tag)) {
+                type = parser.next();
+                if (type == XmlPullParser.TEXT) {
+                    shortSupportMessage = parser.getText();
+                } else {
+                    Log.w(DevicePolicyManagerService.LOG_TAG,
+                            "Missing text when loading short support message");
+                }
+            } else if (TAG_LONG_SUPPORT_MESSAGE.equals(tag)) {
+                type = parser.next();
+                if (type == XmlPullParser.TEXT) {
+                    longSupportMessage = parser.getText();
+                } else {
+                    Log.w(DevicePolicyManagerService.LOG_TAG,
+                            "Missing text when loading long support message");
+                }
+            } else if (TAG_PARENT_ADMIN.equals(tag)) {
+                Preconditions.checkState(!isParent);
+                parentAdmin = new ActiveAdmin(info, /* parent */ true);
+                parentAdmin.readFromXml(parser, shouldOverridePolicies);
+            } else if (TAG_ORGANIZATION_COLOR.equals(tag)) {
+                organizationColor = Integer.parseInt(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_ORGANIZATION_NAME.equals(tag)) {
+                type = parser.next();
+                if (type == XmlPullParser.TEXT) {
+                    organizationName = parser.getText();
+                } else {
+                    Log.w(DevicePolicyManagerService.LOG_TAG,
+                            "Missing text when loading organization name");
+                }
+            } else if (TAG_IS_LOGOUT_ENABLED.equals(tag)) {
+                isLogoutEnabled = Boolean.parseBoolean(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_START_USER_SESSION_MESSAGE.equals(tag)) {
+                type = parser.next();
+                if (type == XmlPullParser.TEXT) {
+                    startUserSessionMessage = parser.getText();
+                } else {
+                    Log.w(DevicePolicyManagerService.LOG_TAG,
+                            "Missing text when loading start session message");
+                }
+            } else if (TAG_END_USER_SESSION_MESSAGE.equals(tag)) {
+                type = parser.next();
+                if (type == XmlPullParser.TEXT) {
+                    endUserSessionMessage = parser.getText();
+                } else {
+                    Log.w(DevicePolicyManagerService.LOG_TAG,
+                            "Missing text when loading end session message");
+                }
+            } else if (TAG_CROSS_PROFILE_CALENDAR_PACKAGES.equals(tag)) {
+                mCrossProfileCalendarPackages = readPackageList(parser, tag);
+            } else if (TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL.equals(tag)) {
+                mCrossProfileCalendarPackages = null;
+            } else if (TAG_CROSS_PROFILE_PACKAGES.equals(tag)) {
+                mCrossProfilePackages = readPackageList(parser, tag);
+            } else if (TAG_FACTORY_RESET_PROTECTION_POLICY.equals(tag)) {
+                mFactoryResetProtectionPolicy = FactoryResetProtectionPolicy.readFromXml(
+                            parser);
+            } else if (TAG_SUSPEND_PERSONAL_APPS.equals(tag)) {
+                mSuspendPersonalApps = Boolean.parseBoolean(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_PROFILE_MAXIMUM_TIME_OFF.equals(tag)) {
+                mProfileMaximumTimeOffMillis =
+                        Long.parseLong(parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_PROFILE_OFF_DEADLINE.equals(tag)) {
+                mProfileOffDeadline =
+                        Long.parseLong(parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_ALWAYS_ON_VPN_PACKAGE.equals(tag)) {
+                mAlwaysOnVpnPackage = parser.getAttributeValue(null, ATTR_VALUE);
+            } else if (TAG_ALWAYS_ON_VPN_LOCKDOWN.equals(tag)) {
+                mAlwaysOnVpnLockdown = Boolean.parseBoolean(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_COMMON_CRITERIA_MODE.equals(tag)) {
+                mCommonCriteriaMode = Boolean.parseBoolean(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else {
+                Slog.w(DevicePolicyManagerService.LOG_TAG, "Unknown admin tag: " + tag);
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+    }
+
+    private List<String> readPackageList(XmlPullParser parser,
+            String tag) throws XmlPullParserException, IOException {
+        List<String> result = new ArrayList<String>();
+        int outerDepth = parser.getDepth();
+        int outerType;
+        while ((outerType = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (outerType != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (outerType == XmlPullParser.END_TAG || outerType == XmlPullParser.TEXT) {
+                continue;
+            }
+            String outerTag = parser.getName();
+            if (TAG_PACKAGE_LIST_ITEM.equals(outerTag)) {
+                String packageName = parser.getAttributeValue(null, ATTR_VALUE);
+                if (packageName != null) {
+                    result.add(packageName);
+                } else {
+                    Slog.w(DevicePolicyManagerService.LOG_TAG,
+                            "Package name missing under " + outerTag);
+                }
+            } else {
+                Slog.w(DevicePolicyManagerService.LOG_TAG,
+                        "Unknown tag under " + tag +  ": " + outerTag);
+            }
+        }
+        return result;
+    }
+
+    private void readAttributeValues(
+            XmlPullParser parser, String tag, Collection<String> result)
+            throws XmlPullParserException, IOException {
+        result.clear();
+        int outerDepthDAM = parser.getDepth();
+        int typeDAM;
+        while ((typeDAM = parser.next()) != END_DOCUMENT
+                && (typeDAM != END_TAG || parser.getDepth() > outerDepthDAM)) {
+            if (typeDAM == END_TAG || typeDAM == TEXT) {
+                continue;
+            }
+            String tagDAM = parser.getName();
+            if (tag.equals(tagDAM)) {
+                result.add(parser.getAttributeValue(null, ATTR_VALUE));
+            } else {
+                Slog.e(DevicePolicyManagerService.LOG_TAG,
+                        "Expected tag " + tag +  " but found " + tagDAM);
+            }
+        }
+    }
+
+    @NonNull
+    private ArrayMap<String, TrustAgentInfo> getAllTrustAgentInfos(
+            XmlPullParser parser, String tag) throws XmlPullParserException, IOException {
+        int outerDepthDAM = parser.getDepth();
+        int typeDAM;
+        final ArrayMap<String, TrustAgentInfo> result = new ArrayMap<>();
+        while ((typeDAM = parser.next()) != END_DOCUMENT
+                && (typeDAM != END_TAG || parser.getDepth() > outerDepthDAM)) {
+            if (typeDAM == END_TAG || typeDAM == TEXT) {
+                continue;
+            }
+            String tagDAM = parser.getName();
+            if (TAG_TRUST_AGENT_COMPONENT.equals(tagDAM)) {
+                final String component = parser.getAttributeValue(null, ATTR_VALUE);
+                final TrustAgentInfo trustAgentInfo = getTrustAgentInfo(parser, tag);
+                result.put(component, trustAgentInfo);
+            } else {
+                Slog.w(DevicePolicyManagerService.LOG_TAG,
+                        "Unknown tag under " + tag +  ": " + tagDAM);
+            }
+        }
+        return result;
+    }
+
+    private TrustAgentInfo getTrustAgentInfo(XmlPullParser parser, String tag)
+            throws XmlPullParserException, IOException  {
+        int outerDepthDAM = parser.getDepth();
+        int typeDAM;
+        TrustAgentInfo result = new TrustAgentInfo(null);
+        while ((typeDAM = parser.next()) != END_DOCUMENT
+                && (typeDAM != END_TAG || parser.getDepth() > outerDepthDAM)) {
+            if (typeDAM == END_TAG || typeDAM == TEXT) {
+                continue;
+            }
+            String tagDAM = parser.getName();
+            if (TAG_TRUST_AGENT_COMPONENT_OPTIONS.equals(tagDAM)) {
+                result.options = PersistableBundle.restoreFromXml(parser);
+            } else {
+                Slog.w(DevicePolicyManagerService.LOG_TAG,
+                        "Unknown tag under " + tag +  ": " + tagDAM);
+            }
+        }
+        return result;
+    }
+
+    boolean hasUserRestrictions() {
+        return userRestrictions != null && userRestrictions.size() > 0;
+    }
+
+    Bundle ensureUserRestrictions() {
+        if (userRestrictions == null) {
+            userRestrictions = new Bundle();
+        }
+        return userRestrictions;
+    }
+
+    public void transfer(DeviceAdminInfo deviceAdminInfo) {
+        if (hasParentActiveAdmin()) {
+            parentAdmin.info = deviceAdminInfo;
+        }
+        info = deviceAdminInfo;
+    }
+
+    Bundle addSyntheticRestrictions(Bundle restrictions) {
+        if (disableCamera) {
+            restrictions.putBoolean(UserManager.DISALLOW_CAMERA, true);
+        }
+        if (requireAutoTime) {
+            restrictions.putBoolean(UserManager.DISALLOW_CONFIG_DATE_TIME, true);
+        }
+        return restrictions;
+    }
+
+    static Bundle removeDeprecatedRestrictions(Bundle restrictions) {
+        for (String deprecatedRestriction: UserRestrictionsUtils.DEPRECATED_USER_RESTRICTIONS) {
+            restrictions.remove(deprecatedRestriction);
+        }
+        return restrictions;
+    }
+
+    static Bundle filterRestrictions(Bundle restrictions, Predicate<String> filter) {
+        Bundle result = new Bundle();
+        for (String key : restrictions.keySet()) {
+            if (!restrictions.getBoolean(key)) {
+                continue;
+            }
+            if (filter.test(key)) {
+                result.putBoolean(key, true);
+            }
+        }
+        return result;
+    }
+
+    Bundle getEffectiveRestrictions() {
+        return addSyntheticRestrictions(
+                removeDeprecatedRestrictions(new Bundle(ensureUserRestrictions())));
+    }
+
+    Bundle getLocalUserRestrictions(int adminType) {
+        return filterRestrictions(getEffectiveRestrictions(),
+                key -> UserRestrictionsUtils.isLocal(adminType, key));
+    }
+
+    Bundle getGlobalUserRestrictions(int adminType) {
+        return filterRestrictions(getEffectiveRestrictions(),
+                key -> UserRestrictionsUtils.isGlobal(adminType, key));
+    }
+
+    void dump(IndentingPrintWriter pw) {
+        pw.print("uid=");
+        pw.println(getUid());
+
+        pw.print("testOnlyAdmin=");
+        pw.println(testOnlyAdmin);
+
+        pw.println("policies:");
+        ArrayList<DeviceAdminInfo.PolicyInfo> pols = info.getUsedPolicies();
+        if (pols != null) {
+            pw.increaseIndent();
+            for (int i = 0; i < pols.size(); i++) {
+                pw.println(pols.get(i).tag);
+            }
+            pw.decreaseIndent();
+        }
+
+        pw.print("passwordQuality=0x");
+        pw.println(Integer.toHexString(mPasswordPolicy.quality));
+
+        pw.print("minimumPasswordLength=");
+        pw.println(mPasswordPolicy.length);
+
+        pw.print("passwordHistoryLength=");
+        pw.println(passwordHistoryLength);
+
+        pw.print("minimumPasswordUpperCase=");
+        pw.println(mPasswordPolicy.upperCase);
+
+        pw.print("minimumPasswordLowerCase=");
+        pw.println(mPasswordPolicy.lowerCase);
+
+        pw.print("minimumPasswordLetters=");
+        pw.println(mPasswordPolicy.letters);
+
+        pw.print("minimumPasswordNumeric=");
+        pw.println(mPasswordPolicy.numeric);
+
+        pw.print("minimumPasswordSymbols=");
+        pw.println(mPasswordPolicy.symbols);
+
+        pw.print("minimumPasswordNonLetter=");
+        pw.println(mPasswordPolicy.nonLetter);
+
+        pw.print("maximumTimeToUnlock=");
+        pw.println(maximumTimeToUnlock);
+
+        pw.print("strongAuthUnlockTimeout=");
+        pw.println(strongAuthUnlockTimeout);
+
+        pw.print("maximumFailedPasswordsForWipe=");
+        pw.println(maximumFailedPasswordsForWipe);
+
+        pw.print("specifiesGlobalProxy=");
+        pw.println(specifiesGlobalProxy);
+
+        pw.print("passwordExpirationTimeout=");
+        pw.println(passwordExpirationTimeout);
+
+        pw.print("passwordExpirationDate=");
+        pw.println(passwordExpirationDate);
+
+        if (globalProxySpec != null) {
+            pw.print("globalProxySpec=");
+            pw.println(globalProxySpec);
+        }
+        if (globalProxyExclusionList != null) {
+            pw.print("globalProxyEclusionList=");
+            pw.println(globalProxyExclusionList);
+        }
+        pw.print("encryptionRequested=");
+        pw.println(encryptionRequested);
+
+        pw.print("disableCamera=");
+        pw.println(disableCamera);
+
+        pw.print("disableCallerId=");
+        pw.println(disableCallerId);
+
+        pw.print("disableContactsSearch=");
+        pw.println(disableContactsSearch);
+
+        pw.print("disableBluetoothContactSharing=");
+        pw.println(disableBluetoothContactSharing);
+
+        pw.print("disableScreenCapture=");
+        pw.println(disableScreenCapture);
+
+        pw.print("requireAutoTime=");
+        pw.println(requireAutoTime);
+
+        pw.print("forceEphemeralUsers=");
+        pw.println(forceEphemeralUsers);
+
+        pw.print("isNetworkLoggingEnabled=");
+        pw.println(isNetworkLoggingEnabled);
+
+        pw.print("disabledKeyguardFeatures=");
+        pw.println(disabledKeyguardFeatures);
+
+        pw.print("crossProfileWidgetProviders=");
+        pw.println(crossProfileWidgetProviders);
+
+        if (permittedAccessiblityServices != null) {
+            pw.print("permittedAccessibilityServices=");
+            pw.println(permittedAccessiblityServices);
+        }
+
+        if (permittedInputMethods != null) {
+            pw.print("permittedInputMethods=");
+            pw.println(permittedInputMethods);
+        }
+
+        if (permittedNotificationListeners != null) {
+            pw.print("permittedNotificationListeners=");
+            pw.println(permittedNotificationListeners);
+        }
+
+        if (keepUninstalledPackages != null) {
+            pw.print("keepUninstalledPackages=");
+            pw.println(keepUninstalledPackages);
+        }
+
+        pw.print("organizationColor=");
+        pw.println(organizationColor);
+
+        if (organizationName != null) {
+            pw.print("organizationName=");
+            pw.println(organizationName);
+        }
+
+        pw.println("userRestrictions:");
+        UserRestrictionsUtils.dumpRestrictions(pw, "  ", userRestrictions);
+
+        pw.print("defaultEnabledRestrictionsAlreadySet=");
+        pw.println(defaultEnabledRestrictionsAlreadySet);
+
+        pw.print("isParent=");
+        pw.println(isParent);
+
+        if (parentAdmin != null) {
+            pw.println("parentAdmin:");
+            pw.increaseIndent();
+            parentAdmin.dump(pw);
+            pw.decreaseIndent();
+        }
+
+        if (mCrossProfileCalendarPackages != null) {
+            pw.print("mCrossProfileCalendarPackages=");
+            pw.println(mCrossProfileCalendarPackages);
+        }
+
+        pw.print("mCrossProfilePackages=");
+        pw.println(mCrossProfilePackages);
+
+        pw.print("mSuspendPersonalApps=");
+        pw.println(mSuspendPersonalApps);
+
+        pw.print("mProfileMaximumTimeOffMillis=");
+        pw.println(mProfileMaximumTimeOffMillis);
+
+        pw.print("mProfileOffDeadline=");
+        pw.println(mProfileOffDeadline);
+
+        pw.print("mAlwaysOnVpnPackage=");
+        pw.println(mAlwaysOnVpnPackage);
+
+        pw.print("mAlwaysOnVpnLockdown=");
+        pw.println(mAlwaysOnVpnLockdown);
+
+        pw.print("mCommonCriteriaMode=");
+        pw.println(mCommonCriteriaMode);
+    }
+}
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/DevicePolicyData.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
new file mode 100644
index 0000000..130cfd5
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
@@ -0,0 +1,566 @@
+/*
+ * 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.app.admin.DeviceAdminInfo;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.os.FileUtils;
+import android.os.PersistableBundle;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.JournaledFile;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+
+class DevicePolicyData {
+    private static final String TAG_ACCEPTED_CA_CERTIFICATES = "accepted-ca-certificate";
+    private static final String TAG_LOCK_TASK_COMPONENTS = "lock-task-component";
+    private static final String TAG_LOCK_TASK_FEATURES = "lock-task-features";
+    private static final String TAG_STATUS_BAR = "statusbar";
+    private static final String TAG_APPS_SUSPENDED = "apps-suspended";
+    private static final String TAG_SECONDARY_LOCK_SCREEN = "secondary-lock-screen";
+    private static final String TAG_DO_NOT_ASK_CREDENTIALS_ON_BOOT =
+            "do-not-ask-credentials-on-boot";
+    private static final String TAG_AFFILIATION_ID = "affiliation-id";
+    private static final String TAG_LAST_SECURITY_LOG_RETRIEVAL = "last-security-log-retrieval";
+    private static final String TAG_LAST_BUG_REPORT_REQUEST = "last-bug-report-request";
+    private static final String TAG_LAST_NETWORK_LOG_RETRIEVAL = "last-network-log-retrieval";
+    private static final String TAG_ADMIN_BROADCAST_PENDING = "admin-broadcast-pending";
+    private static final String TAG_CURRENT_INPUT_METHOD_SET = "current-ime-set";
+    private static final String TAG_OWNER_INSTALLED_CA_CERT = "owner-installed-ca-cert";
+    private static final String TAG_INITIALIZATION_BUNDLE = "initialization-bundle";
+    private static final String TAG_PASSWORD_VALIDITY = "password-validity";
+    private static final String TAG_PASSWORD_TOKEN_HANDLE = "password-token";
+    private static final String TAG_PROTECTED_PACKAGES = "protected-packages";
+    private static final String ATTR_VALUE = "value";
+    private static final String ATTR_ALIAS = "alias";
+    private static final String ATTR_ID = "id";
+    private static final String ATTR_PERMISSION_PROVIDER = "permission-provider";
+    private static final String ATTR_NAME = "name";
+    private static final String ATTR_DISABLED = "disabled";
+    private static final String ATTR_SETUP_COMPLETE = "setup-complete";
+    private static final String ATTR_PROVISIONING_STATE = "provisioning-state";
+    private static final String ATTR_PERMISSION_POLICY = "permission-policy";
+    private static final String ATTR_DEVICE_PROVISIONING_CONFIG_APPLIED =
+            "device-provisioning-config-applied";
+    private static final String ATTR_DEVICE_PAIRED = "device-paired";
+
+    int mFailedPasswordAttempts = 0;
+    boolean mPasswordValidAtLastCheckpoint = true;
+
+    int mUserHandle;
+    int mPasswordOwner = -1;
+    long mLastMaximumTimeToLock = -1;
+    boolean mUserSetupComplete = false;
+    boolean mPaired = false;
+    int mUserProvisioningState;
+    int mPermissionPolicy;
+
+    boolean mDeviceProvisioningConfigApplied = false;
+
+    final ArrayMap<ComponentName, ActiveAdmin> mAdminMap = new ArrayMap<>();
+    final ArrayList<ActiveAdmin> mAdminList = new ArrayList<>();
+    final ArrayList<ComponentName> mRemovingAdmins = new ArrayList<>();
+
+    // TODO(b/35385311): Keep track of metadata in TrustedCertificateStore instead.
+    final ArraySet<String> mAcceptedCaCertificates = new ArraySet<>();
+
+    // This is the list of component allowed to start lock task mode.
+    List<String> mLockTaskPackages = new ArrayList<>();
+
+    // List of packages protected by device owner
+    List<String> mUserControlDisabledPackages = new ArrayList<>();
+
+    // Bitfield of feature flags to be enabled during LockTask mode.
+    // We default on the power button menu, in order to be consistent with pre-P behaviour.
+    int mLockTaskFeatures = DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS;
+
+    boolean mStatusBarDisabled = false;
+
+    ComponentName mRestrictionsProvider;
+
+    // Map of delegate package to delegation scopes
+    final ArrayMap<String, List<String>> mDelegationMap = new ArrayMap<>();
+
+    boolean mDoNotAskCredentialsOnBoot = false;
+
+    Set<String> mAffiliationIds = new ArraySet<>();
+
+    long mLastSecurityLogRetrievalTime = -1;
+
+    long mLastBugReportRequestTime = -1;
+
+    long mLastNetworkLogsRetrievalTime = -1;
+
+    boolean mCurrentInputMethodSet = false;
+
+    boolean mSecondaryLockscreenEnabled = false;
+
+    // TODO(b/35385311): Keep track of metadata in TrustedCertificateStore instead.
+    Set<String> mOwnerInstalledCaCerts = new ArraySet<>();
+
+    // Used for initialization of users created by createAndManageUser.
+    boolean mAdminBroadcastPending = false;
+    PersistableBundle mInitBundle = null;
+
+    long mPasswordTokenHandle = 0;
+
+    // Whether user's apps are suspended. This flag should only be written AFTER all the needed
+    // apps were suspended or unsuspended.
+    boolean mAppsSuspended = false;
+
+    DevicePolicyData(int userHandle) {
+        mUserHandle = userHandle;
+    }
+
+    /**
+     * Serializes DevicePolicyData object as XML.
+     */
+    static boolean store(DevicePolicyData policyData, JournaledFile file, boolean isFdeDevice) {
+        FileOutputStream stream = null;
+        try {
+            stream = new FileOutputStream(file.chooseForWrite(), false);
+            XmlSerializer out = new FastXmlSerializer();
+            out.setOutput(stream, StandardCharsets.UTF_8.name());
+            out.startDocument(null, true);
+
+            out.startTag(null, "policies");
+            if (policyData.mRestrictionsProvider != null) {
+                out.attribute(null, ATTR_PERMISSION_PROVIDER,
+                        policyData.mRestrictionsProvider.flattenToString());
+            }
+            if (policyData.mUserSetupComplete) {
+                out.attribute(null, ATTR_SETUP_COMPLETE,
+                        Boolean.toString(true));
+            }
+            if (policyData.mPaired) {
+                out.attribute(null, ATTR_DEVICE_PAIRED,
+                        Boolean.toString(true));
+            }
+            if (policyData.mDeviceProvisioningConfigApplied) {
+                out.attribute(null, ATTR_DEVICE_PROVISIONING_CONFIG_APPLIED,
+                        Boolean.toString(true));
+            }
+            if (policyData.mUserProvisioningState != DevicePolicyManager.STATE_USER_UNMANAGED) {
+                out.attribute(null, ATTR_PROVISIONING_STATE,
+                        Integer.toString(policyData.mUserProvisioningState));
+            }
+            if (policyData.mPermissionPolicy != DevicePolicyManager.PERMISSION_POLICY_PROMPT) {
+                out.attribute(null, ATTR_PERMISSION_POLICY,
+                        Integer.toString(policyData.mPermissionPolicy));
+            }
+
+            // Serialize delegations.
+            for (int i = 0; i < policyData.mDelegationMap.size(); ++i) {
+                final String delegatePackage = policyData.mDelegationMap.keyAt(i);
+                final List<String> scopes = policyData.mDelegationMap.valueAt(i);
+
+                // Every "delegation" tag serializes the information of one delegate-scope pair.
+                for (String scope : scopes) {
+                    out.startTag(null, "delegation");
+                    out.attribute(null, "delegatePackage", delegatePackage);
+                    out.attribute(null, "scope", scope);
+                    out.endTag(null, "delegation");
+                }
+            }
+
+            final int n = policyData.mAdminList.size();
+            for (int i = 0; i < n; i++) {
+                ActiveAdmin ap = policyData.mAdminList.get(i);
+                if (ap != null) {
+                    out.startTag(null, "admin");
+                    out.attribute(null, "name", ap.info.getComponent().flattenToString());
+                    ap.writeToXml(out);
+                    out.endTag(null, "admin");
+                }
+            }
+
+            if (policyData.mPasswordOwner >= 0) {
+                out.startTag(null, "password-owner");
+                out.attribute(null, "value", Integer.toString(policyData.mPasswordOwner));
+                out.endTag(null, "password-owner");
+            }
+
+            if (policyData.mFailedPasswordAttempts != 0) {
+                out.startTag(null, "failed-password-attempts");
+                out.attribute(null, "value", Integer.toString(policyData.mFailedPasswordAttempts));
+                out.endTag(null, "failed-password-attempts");
+            }
+
+            // For FDE devices only, we save this flag so we can report on password sufficiency
+            // before the user enters their password for the first time after a reboot.  For
+            // security reasons, we don't want to store the full set of active password metrics.
+            if (isFdeDevice) {
+                out.startTag(null, TAG_PASSWORD_VALIDITY);
+                out.attribute(null, ATTR_VALUE,
+                        Boolean.toString(policyData.mPasswordValidAtLastCheckpoint));
+                out.endTag(null, TAG_PASSWORD_VALIDITY);
+            }
+
+            for (int i = 0; i < policyData.mAcceptedCaCertificates.size(); i++) {
+                out.startTag(null, TAG_ACCEPTED_CA_CERTIFICATES);
+                out.attribute(null, ATTR_NAME, policyData.mAcceptedCaCertificates.valueAt(i));
+                out.endTag(null, TAG_ACCEPTED_CA_CERTIFICATES);
+            }
+
+            for (int i = 0; i < policyData.mLockTaskPackages.size(); i++) {
+                String component = policyData.mLockTaskPackages.get(i);
+                out.startTag(null, TAG_LOCK_TASK_COMPONENTS);
+                out.attribute(null, "name", component);
+                out.endTag(null, TAG_LOCK_TASK_COMPONENTS);
+            }
+
+            if (policyData.mLockTaskFeatures != DevicePolicyManager.LOCK_TASK_FEATURE_NONE) {
+                out.startTag(null, TAG_LOCK_TASK_FEATURES);
+                out.attribute(null, ATTR_VALUE, Integer.toString(policyData.mLockTaskFeatures));
+                out.endTag(null, TAG_LOCK_TASK_FEATURES);
+            }
+
+            if (policyData.mSecondaryLockscreenEnabled) {
+                out.startTag(null, TAG_SECONDARY_LOCK_SCREEN);
+                out.attribute(null, ATTR_VALUE, Boolean.toString(true));
+                out.endTag(null, TAG_SECONDARY_LOCK_SCREEN);
+            }
+
+            if (policyData.mStatusBarDisabled) {
+                out.startTag(null, TAG_STATUS_BAR);
+                out.attribute(null, ATTR_DISABLED, Boolean.toString(policyData.mStatusBarDisabled));
+                out.endTag(null, TAG_STATUS_BAR);
+            }
+
+            if (policyData.mDoNotAskCredentialsOnBoot) {
+                out.startTag(null, TAG_DO_NOT_ASK_CREDENTIALS_ON_BOOT);
+                out.endTag(null, TAG_DO_NOT_ASK_CREDENTIALS_ON_BOOT);
+            }
+
+            for (String id : policyData.mAffiliationIds) {
+                out.startTag(null, TAG_AFFILIATION_ID);
+                out.attribute(null, ATTR_ID, id);
+                out.endTag(null, TAG_AFFILIATION_ID);
+            }
+
+            if (policyData.mLastSecurityLogRetrievalTime >= 0) {
+                out.startTag(null, TAG_LAST_SECURITY_LOG_RETRIEVAL);
+                out.attribute(null, ATTR_VALUE,
+                        Long.toString(policyData.mLastSecurityLogRetrievalTime));
+                out.endTag(null, TAG_LAST_SECURITY_LOG_RETRIEVAL);
+            }
+
+            if (policyData.mLastBugReportRequestTime >= 0) {
+                out.startTag(null, TAG_LAST_BUG_REPORT_REQUEST);
+                out.attribute(null, ATTR_VALUE,
+                        Long.toString(policyData.mLastBugReportRequestTime));
+                out.endTag(null, TAG_LAST_BUG_REPORT_REQUEST);
+            }
+
+            if (policyData.mLastNetworkLogsRetrievalTime >= 0) {
+                out.startTag(null, TAG_LAST_NETWORK_LOG_RETRIEVAL);
+                out.attribute(null, ATTR_VALUE,
+                        Long.toString(policyData.mLastNetworkLogsRetrievalTime));
+                out.endTag(null, TAG_LAST_NETWORK_LOG_RETRIEVAL);
+            }
+
+            if (policyData.mAdminBroadcastPending) {
+                out.startTag(null, TAG_ADMIN_BROADCAST_PENDING);
+                out.attribute(null, ATTR_VALUE,
+                        Boolean.toString(policyData.mAdminBroadcastPending));
+                out.endTag(null, TAG_ADMIN_BROADCAST_PENDING);
+            }
+
+            if (policyData.mInitBundle != null) {
+                out.startTag(null, TAG_INITIALIZATION_BUNDLE);
+                policyData.mInitBundle.saveToXml(out);
+                out.endTag(null, TAG_INITIALIZATION_BUNDLE);
+            }
+
+            if (policyData.mPasswordTokenHandle != 0) {
+                out.startTag(null, TAG_PASSWORD_TOKEN_HANDLE);
+                out.attribute(null, ATTR_VALUE,
+                        Long.toString(policyData.mPasswordTokenHandle));
+                out.endTag(null, TAG_PASSWORD_TOKEN_HANDLE);
+            }
+
+            if (policyData.mCurrentInputMethodSet) {
+                out.startTag(null, TAG_CURRENT_INPUT_METHOD_SET);
+                out.endTag(null, TAG_CURRENT_INPUT_METHOD_SET);
+            }
+
+            for (final String cert : policyData.mOwnerInstalledCaCerts) {
+                out.startTag(null, TAG_OWNER_INSTALLED_CA_CERT);
+                out.attribute(null, ATTR_ALIAS, cert);
+                out.endTag(null, TAG_OWNER_INSTALLED_CA_CERT);
+            }
+
+            for (int i = 0, size = policyData.mUserControlDisabledPackages.size(); i < size; i++) {
+                String packageName = policyData.mUserControlDisabledPackages.get(i);
+                out.startTag(null, TAG_PROTECTED_PACKAGES);
+                out.attribute(null, ATTR_NAME, packageName);
+                out.endTag(null, TAG_PROTECTED_PACKAGES);
+            }
+
+            if (policyData.mAppsSuspended) {
+                out.startTag(null, TAG_APPS_SUSPENDED);
+                out.attribute(null, ATTR_VALUE, Boolean.toString(policyData.mAppsSuspended));
+                out.endTag(null, TAG_APPS_SUSPENDED);
+            }
+
+            out.endTag(null, "policies");
+
+            out.endDocument();
+            stream.flush();
+            FileUtils.sync(stream);
+            stream.close();
+            file.commit();
+            return true;
+        } catch (XmlPullParserException | IOException e) {
+            Slog.w(DevicePolicyManagerService.LOG_TAG, "failed writing file", e);
+            try {
+                if (stream != null) {
+                    stream.close();
+                }
+            } catch (IOException ex) {
+                // Ignore
+            }
+            file.rollback();
+            return false;
+        }
+    }
+
+    /**
+     * @param adminInfoSupplier function that queries DeviceAdminInfo from PackageManager
+     * @param ownerComponent device or profile owner component if any.
+     */
+    static boolean load(DevicePolicyData policy, boolean isFdeDevice, JournaledFile journaledFile,
+            Function<ComponentName, DeviceAdminInfo> adminInfoSupplier,
+            ComponentName ownerComponent) {
+        FileInputStream stream = null;
+        File file = journaledFile.chooseForRead();
+        boolean needsRewrite = false;
+        try {
+            stream = new FileInputStream(file);
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(stream, StandardCharsets.UTF_8.name());
+
+            int type;
+            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                    && type != XmlPullParser.START_TAG) {
+            }
+            String tag = parser.getName();
+            if (!"policies".equals(tag)) {
+                throw new XmlPullParserException(
+                        "Settings do not start with policies tag: found " + tag);
+            }
+
+            // Extract the permission provider component name if available
+            String permissionProvider = parser.getAttributeValue(null, ATTR_PERMISSION_PROVIDER);
+            if (permissionProvider != null) {
+                policy.mRestrictionsProvider =
+                        ComponentName.unflattenFromString(permissionProvider);
+            }
+            String userSetupComplete = parser.getAttributeValue(null, ATTR_SETUP_COMPLETE);
+            if (Boolean.toString(true).equals(userSetupComplete)) {
+                policy.mUserSetupComplete = true;
+            }
+            String paired = parser.getAttributeValue(null, ATTR_DEVICE_PAIRED);
+            if (Boolean.toString(true).equals(paired)) {
+                policy.mPaired = true;
+            }
+            String deviceProvisioningConfigApplied = parser.getAttributeValue(null,
+                    ATTR_DEVICE_PROVISIONING_CONFIG_APPLIED);
+            if (Boolean.toString(true).equals(deviceProvisioningConfigApplied)) {
+                policy.mDeviceProvisioningConfigApplied = true;
+            }
+            String provisioningState = parser.getAttributeValue(null, ATTR_PROVISIONING_STATE);
+            if (!TextUtils.isEmpty(provisioningState)) {
+                policy.mUserProvisioningState = Integer.parseInt(provisioningState);
+            }
+            String permissionPolicy = parser.getAttributeValue(null, ATTR_PERMISSION_POLICY);
+            if (!TextUtils.isEmpty(permissionPolicy)) {
+                policy.mPermissionPolicy = Integer.parseInt(permissionPolicy);
+            }
+
+            parser.next();
+            int outerDepth = parser.getDepth();
+            policy.mLockTaskPackages.clear();
+            policy.mAdminList.clear();
+            policy.mAdminMap.clear();
+            policy.mAffiliationIds.clear();
+            policy.mOwnerInstalledCaCerts.clear();
+            policy.mUserControlDisabledPackages.clear();
+            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                   && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+                if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                    continue;
+                }
+                tag = parser.getName();
+                if ("admin".equals(tag)) {
+                    String name = parser.getAttributeValue(null, "name");
+                    try {
+                        DeviceAdminInfo dai = adminInfoSupplier.apply(
+                                ComponentName.unflattenFromString(name));
+
+                        if (dai != null) {
+                            // b/123415062: If DA, overwrite with the stored policies that were
+                            // agreed by the user to prevent apps from sneaking additional policies
+                            // into updates.
+                            boolean overwritePolicies = !dai.getComponent().equals(ownerComponent);
+                            ActiveAdmin ap = new ActiveAdmin(dai, /* parent */ false);
+                            ap.readFromXml(parser, overwritePolicies);
+                            policy.mAdminMap.put(ap.info.getComponent(), ap);
+                        }
+                    } catch (RuntimeException e) {
+                        Slog.w(DevicePolicyManagerService.LOG_TAG,
+                                "Failed loading admin " + name, e);
+                    }
+                } else if ("delegation".equals(tag)) {
+                    // Parse delegation info.
+                    final String delegatePackage = parser.getAttributeValue(null,
+                            "delegatePackage");
+                    final String scope = parser.getAttributeValue(null, "scope");
+
+                    // Get a reference to the scopes list for the delegatePackage.
+                    List<String> scopes = policy.mDelegationMap.get(delegatePackage);
+                    // Or make a new list if none was found.
+                    if (scopes == null) {
+                        scopes = new ArrayList<>();
+                        policy.mDelegationMap.put(delegatePackage, scopes);
+                    }
+                    // Add the new scope to the list of delegatePackage if it's not already there.
+                    if (!scopes.contains(scope)) {
+                        scopes.add(scope);
+                    }
+                } else if ("failed-password-attempts".equals(tag)) {
+                    policy.mFailedPasswordAttempts = Integer.parseInt(
+                            parser.getAttributeValue(null, "value"));
+                } else if ("password-owner".equals(tag)) {
+                    policy.mPasswordOwner = Integer.parseInt(
+                            parser.getAttributeValue(null, "value"));
+                } else if (TAG_ACCEPTED_CA_CERTIFICATES.equals(tag)) {
+                    policy.mAcceptedCaCertificates.add(parser.getAttributeValue(null, ATTR_NAME));
+                } else if (TAG_LOCK_TASK_COMPONENTS.equals(tag)) {
+                    policy.mLockTaskPackages.add(parser.getAttributeValue(null, "name"));
+                } else if (TAG_LOCK_TASK_FEATURES.equals(tag)) {
+                    policy.mLockTaskFeatures = Integer.parseInt(
+                            parser.getAttributeValue(null, ATTR_VALUE));
+                } else if (TAG_SECONDARY_LOCK_SCREEN.equals(tag)) {
+                    policy.mSecondaryLockscreenEnabled = Boolean.parseBoolean(
+                            parser.getAttributeValue(null, ATTR_VALUE));
+                } else if (TAG_STATUS_BAR.equals(tag)) {
+                    policy.mStatusBarDisabled = Boolean.parseBoolean(
+                            parser.getAttributeValue(null, ATTR_DISABLED));
+                } else if (TAG_DO_NOT_ASK_CREDENTIALS_ON_BOOT.equals(tag)) {
+                    policy.mDoNotAskCredentialsOnBoot = true;
+                } else if (TAG_AFFILIATION_ID.equals(tag)) {
+                    policy.mAffiliationIds.add(parser.getAttributeValue(null, ATTR_ID));
+                } else if (TAG_LAST_SECURITY_LOG_RETRIEVAL.equals(tag)) {
+                    policy.mLastSecurityLogRetrievalTime = Long.parseLong(
+                            parser.getAttributeValue(null, ATTR_VALUE));
+                } else if (TAG_LAST_BUG_REPORT_REQUEST.equals(tag)) {
+                    policy.mLastBugReportRequestTime = Long.parseLong(
+                            parser.getAttributeValue(null, ATTR_VALUE));
+                } else if (TAG_LAST_NETWORK_LOG_RETRIEVAL.equals(tag)) {
+                    policy.mLastNetworkLogsRetrievalTime = Long.parseLong(
+                            parser.getAttributeValue(null, ATTR_VALUE));
+                } else if (TAG_ADMIN_BROADCAST_PENDING.equals(tag)) {
+                    String pending = parser.getAttributeValue(null, ATTR_VALUE);
+                    policy.mAdminBroadcastPending = Boolean.toString(true).equals(pending);
+                } else if (TAG_INITIALIZATION_BUNDLE.equals(tag)) {
+                    policy.mInitBundle = PersistableBundle.restoreFromXml(parser);
+                } else if ("active-password".equals(tag)) {
+                    // Remove password metrics from saved settings, as we no longer wish to store
+                    // these on disk
+                    needsRewrite = true;
+                } else if (TAG_PASSWORD_VALIDITY.equals(tag)) {
+                    if (isFdeDevice) {
+                        // This flag is only used for FDE devices
+                        policy.mPasswordValidAtLastCheckpoint = Boolean.parseBoolean(
+                                parser.getAttributeValue(null, ATTR_VALUE));
+                    }
+                } else if (TAG_PASSWORD_TOKEN_HANDLE.equals(tag)) {
+                    policy.mPasswordTokenHandle = Long.parseLong(
+                            parser.getAttributeValue(null, ATTR_VALUE));
+                } else if (TAG_CURRENT_INPUT_METHOD_SET.equals(tag)) {
+                    policy.mCurrentInputMethodSet = true;
+                } else if (TAG_OWNER_INSTALLED_CA_CERT.equals(tag)) {
+                    policy.mOwnerInstalledCaCerts.add(parser.getAttributeValue(null, ATTR_ALIAS));
+                } else if (TAG_PROTECTED_PACKAGES.equals(tag)) {
+                    policy.mUserControlDisabledPackages.add(
+                            parser.getAttributeValue(null, ATTR_NAME));
+                } else if (TAG_APPS_SUSPENDED.equals(tag)) {
+                    policy.mAppsSuspended =
+                            Boolean.parseBoolean(parser.getAttributeValue(null, ATTR_VALUE));
+                } else {
+                    Slog.w(DevicePolicyManagerService.LOG_TAG, "Unknown tag: " + tag);
+                    XmlUtils.skipCurrentTag(parser);
+                }
+            }
+        } catch (FileNotFoundException e) {
+            // Don't be noisy, this is normal if we haven't defined any policies.
+        } catch (NullPointerException | NumberFormatException | XmlPullParserException | IOException
+                | IndexOutOfBoundsException e) {
+            Slog.w(DevicePolicyManagerService.LOG_TAG, "failed parsing " + file, e);
+        }
+        try {
+            if (stream != null) {
+                stream.close();
+            }
+        } catch (IOException e) {
+            // Ignore
+        }
+
+        // Generate a list of admins from the admin map
+        policy.mAdminList.addAll(policy.mAdminMap.values());
+        return needsRewrite;
+    }
+
+    void validatePasswordOwner() {
+        if (mPasswordOwner >= 0) {
+            boolean haveOwner = false;
+            for (int i = mAdminList.size() - 1; i >= 0; i--) {
+                if (mAdminList.get(i).getUid() == mPasswordOwner) {
+                    haveOwner = true;
+                    break;
+                }
+            }
+            if (!haveOwner) {
+                Slog.w(DevicePolicyManagerService.LOG_TAG, "Previous password owner "
+                        + mPasswordOwner + " no longer active; disabling");
+                mPasswordOwner = -1;
+            }
+        }
+    }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 9a2bef8..6154bef 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -106,10 +106,6 @@
 import static com.android.server.devicepolicy.TransferOwnershipMetadataManager.ADMIN_TYPE_PROFILE_OWNER;
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
 
-import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
-import static org.xmlpull.v1.XmlPullParser.END_TAG;
-import static org.xmlpull.v1.XmlPullParser.TEXT;
-
 import android.Manifest.permission;
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accounts.Account;
@@ -189,7 +185,6 @@
 import android.database.ContentObserver;
 import android.database.Cursor;
 import android.graphics.Bitmap;
-import android.graphics.Color;
 import android.location.LocationManager;
 import android.media.AudioManager;
 import android.media.IAudioService;
@@ -204,7 +199,6 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Environment;
-import android.os.FileUtils;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -248,7 +242,6 @@
 import android.telephony.data.ApnSetting;
 import android.text.TextUtils;
 import android.text.format.DateUtils;
-import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.Log;
@@ -280,7 +273,6 @@
 import com.android.internal.util.JournaledFile;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.StatLogger;
-import com.android.internal.util.XmlUtils;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockSettingsInternal;
 import com.android.internal.widget.LockscreenCredential;
@@ -290,8 +282,7 @@
 import com.android.server.PersistentDataBlockManagerInternal;
 import com.android.server.SystemServerInitThreadPool;
 import com.android.server.SystemService;
-import com.android.server.SystemService.TargetUser;
-import com.android.server.devicepolicy.DevicePolicyManagerService.ActiveAdmin.TrustAgentInfo;
+import com.android.server.devicepolicy.ActiveAdmin.TrustAgentInfo;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.net.NetworkPolicyManagerInternal;
 import com.android.server.pm.RestrictionsSet;
@@ -328,7 +319,6 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
@@ -350,55 +340,8 @@
     private static final String TRANSFER_OWNERSHIP_PARAMETERS_XML =
             "transfer-ownership-parameters.xml";
 
-    private static final String TAG_ACCEPTED_CA_CERTIFICATES = "accepted-ca-certificate";
-
-    private static final String TAG_LOCK_TASK_COMPONENTS = "lock-task-component";
-
-    private static final String TAG_LOCK_TASK_FEATURES = "lock-task-features";
-
-    private static final String TAG_STATUS_BAR = "statusbar";
-
-    private static final String ATTR_DISABLED = "disabled";
-
-    private static final String ATTR_NAME = "name";
-
-    private static final String DO_NOT_ASK_CREDENTIALS_ON_BOOT_XML =
-            "do-not-ask-credentials-on-boot";
-
-    private static final String TAG_AFFILIATION_ID = "affiliation-id";
-
-    private static final String TAG_LAST_SECURITY_LOG_RETRIEVAL = "last-security-log-retrieval";
-
-    private static final String TAG_LAST_BUG_REPORT_REQUEST = "last-bug-report-request";
-
-    private static final String TAG_LAST_NETWORK_LOG_RETRIEVAL = "last-network-log-retrieval";
-
-    private static final String TAG_ADMIN_BROADCAST_PENDING = "admin-broadcast-pending";
-
-    private static final String TAG_CURRENT_INPUT_METHOD_SET = "current-ime-set";
-
-    private static final String TAG_OWNER_INSTALLED_CA_CERT = "owner-installed-ca-cert";
-
-    private static final String ATTR_ID = "id";
-
-    private static final String ATTR_VALUE = "value";
-
-    private static final String ATTR_ALIAS = "alias";
-
-    private static final String TAG_INITIALIZATION_BUNDLE = "initialization-bundle";
-
-    private static final String TAG_PASSWORD_TOKEN_HANDLE = "password-token";
-
-    private static final String TAG_PASSWORD_VALIDITY = "password-validity";
-
     private static final String TAG_TRANSFER_OWNERSHIP_BUNDLE = "transfer-ownership-bundle";
 
-    private static final String TAG_PROTECTED_PACKAGES = "protected-packages";
-
-    private static final String TAG_SECONDARY_LOCK_SCREEN = "secondary-lock-screen";
-
-    private static final String TAG_APPS_SUSPENDED = "apps-suspended";
-
     private static final int REQUEST_EXPIRE_PASSWORD = 5571;
 
     private static final int REQUEST_PROFILE_OFF_DEADLINE = 5572;
@@ -423,17 +366,6 @@
     static final String ACTION_PROFILE_OFF_DEADLINE =
             "com.android.server.ACTION_PROFILE_OFF_DEADLINE";
 
-    private static final String ATTR_PERMISSION_PROVIDER = "permission-provider";
-    private static final String ATTR_SETUP_COMPLETE = "setup-complete";
-    private static final String ATTR_PROVISIONING_STATE = "provisioning-state";
-    private static final String ATTR_PERMISSION_POLICY = "permission-policy";
-    private static final String ATTR_DEVICE_PROVISIONING_CONFIG_APPLIED =
-            "device-provisioning-config-applied";
-    private static final String ATTR_DEVICE_PAIRED = "device-paired";
-    private static final String ATTR_DELEGATED_CERT_INSTALLER = "delegated-cert-installer";
-    private static final String ATTR_APPLICATION_RESTRICTIONS_MANAGER
-            = "application-restrictions-manager";
-
     private static final String CALLED_FROM_PARENT = "calledFromParent";
     private static final String NOT_CALLED_FROM_PARENT = "notCalledFromParent";
 
@@ -488,7 +420,6 @@
     private static final Set<String> SYSTEM_SETTINGS_WHITELIST;
     private static final Set<Integer> DA_DISALLOWED_POLICIES;
     // A collection of user restrictions that are deprecated and should simply be ignored.
-    private static final Set<String> DEPRECATED_USER_RESTRICTIONS;
     private static final String AB_DEVICE_KEY = "ro.build.ab_update";
 
     static {
@@ -532,10 +463,6 @@
         DA_DISALLOWED_POLICIES.add(DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_FEATURES);
         DA_DISALLOWED_POLICIES.add(DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD);
         DA_DISALLOWED_POLICIES.add(DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
-
-        DEPRECATED_USER_RESTRICTIONS = Sets.newHashSet(
-                UserManager.DISALLOW_ADD_MANAGED_PROFILE,
-                UserManager.DISALLOW_REMOVE_MANAGED_PROFILE);
     }
 
     /**
@@ -791,76 +718,6 @@
         }
     }
 
-    public static class DevicePolicyData {
-        int mFailedPasswordAttempts = 0;
-        boolean mPasswordValidAtLastCheckpoint = true;
-
-        int mUserHandle;
-        int mPasswordOwner = -1;
-        long mLastMaximumTimeToLock = -1;
-        boolean mUserSetupComplete = false;
-        boolean mPaired = false;
-        int mUserProvisioningState;
-        int mPermissionPolicy;
-
-        boolean mDeviceProvisioningConfigApplied = false;
-
-        final ArrayMap<ComponentName, ActiveAdmin> mAdminMap = new ArrayMap<>();
-        final ArrayList<ActiveAdmin> mAdminList = new ArrayList<>();
-        final ArrayList<ComponentName> mRemovingAdmins = new ArrayList<>();
-
-        // TODO(b/35385311): Keep track of metadata in TrustedCertificateStore instead.
-        final ArraySet<String> mAcceptedCaCertificates = new ArraySet<>();
-
-        // This is the list of component allowed to start lock task mode.
-        List<String> mLockTaskPackages = new ArrayList<>();
-
-        // List of packages protected by device owner
-        List<String> mUserControlDisabledPackages = new ArrayList<>();
-
-        // Bitfield of feature flags to be enabled during LockTask mode.
-        // We default on the power button menu, in order to be consistent with pre-P behaviour.
-        int mLockTaskFeatures = DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS;
-
-        boolean mStatusBarDisabled = false;
-
-        ComponentName mRestrictionsProvider;
-
-        // Map of delegate package to delegation scopes
-        final ArrayMap<String, List<String>> mDelegationMap = new ArrayMap<>();
-
-        boolean doNotAskCredentialsOnBoot = false;
-
-        Set<String> mAffiliationIds = new ArraySet<>();
-
-        long mLastSecurityLogRetrievalTime = -1;
-
-        long mLastBugReportRequestTime = -1;
-
-        long mLastNetworkLogsRetrievalTime = -1;
-
-        boolean mCurrentInputMethodSet = false;
-
-        boolean mSecondaryLockscreenEnabled = false;
-
-        // TODO(b/35385311): Keep track of metadata in TrustedCertificateStore instead.
-        Set<String> mOwnerInstalledCaCerts = new ArraySet<>();
-
-        // Used for initialization of users created by createAndManageUser.
-        boolean mAdminBroadcastPending = false;
-        PersistableBundle mInitBundle = null;
-
-        long mPasswordTokenHandle = 0;
-
-        // Whether user's apps are suspended. This flag should only be written AFTER all the needed
-        // apps were suspended or unsuspended.
-        boolean mAppsSuspended = false;
-
-        public DevicePolicyData(int userHandle) {
-            mUserHandle = userHandle;
-        }
-    }
-
     @GuardedBy("getLockObject()")
     final SparseArray<DevicePolicyData> mUserData = new SparseArray<>();
 
@@ -1045,1002 +902,6 @@
         }
     }
 
-    static class ActiveAdmin {
-        private static final String TAG_DISABLE_KEYGUARD_FEATURES = "disable-keyguard-features";
-        private static final String TAG_TEST_ONLY_ADMIN = "test-only-admin";
-        private static final String TAG_DISABLE_CAMERA = "disable-camera";
-        private static final String TAG_DISABLE_CALLER_ID = "disable-caller-id";
-        private static final String TAG_DISABLE_CONTACTS_SEARCH = "disable-contacts-search";
-        private static final String TAG_DISABLE_BLUETOOTH_CONTACT_SHARING
-                = "disable-bt-contacts-sharing";
-        private static final String TAG_DISABLE_SCREEN_CAPTURE = "disable-screen-capture";
-        private static final String TAG_DISABLE_ACCOUNT_MANAGEMENT = "disable-account-management";
-        private static final String TAG_REQUIRE_AUTO_TIME = "require_auto_time";
-        private static final String TAG_FORCE_EPHEMERAL_USERS = "force_ephemeral_users";
-        private static final String TAG_IS_NETWORK_LOGGING_ENABLED = "is_network_logging_enabled";
-        private static final String TAG_ACCOUNT_TYPE = "account-type";
-        private static final String TAG_PERMITTED_ACCESSIBILITY_SERVICES
-                = "permitted-accessiblity-services";
-        private static final String TAG_ENCRYPTION_REQUESTED = "encryption-requested";
-        private static final String TAG_MANAGE_TRUST_AGENT_FEATURES = "manage-trust-agent-features";
-        private static final String TAG_TRUST_AGENT_COMPONENT_OPTIONS = "trust-agent-component-options";
-        private static final String TAG_TRUST_AGENT_COMPONENT = "component";
-        private static final String TAG_PASSWORD_EXPIRATION_DATE = "password-expiration-date";
-        private static final String TAG_PASSWORD_EXPIRATION_TIMEOUT = "password-expiration-timeout";
-        private static final String TAG_GLOBAL_PROXY_EXCLUSION_LIST = "global-proxy-exclusion-list";
-        private static final String TAG_GLOBAL_PROXY_SPEC = "global-proxy-spec";
-        private static final String TAG_SPECIFIES_GLOBAL_PROXY = "specifies-global-proxy";
-        private static final String TAG_PERMITTED_IMES = "permitted-imes";
-        private static final String TAG_PERMITTED_NOTIFICATION_LISTENERS =
-                "permitted-notification-listeners";
-        private static final String TAG_MAX_FAILED_PASSWORD_WIPE = "max-failed-password-wipe";
-        private static final String TAG_MAX_TIME_TO_UNLOCK = "max-time-to-unlock";
-        private static final String TAG_STRONG_AUTH_UNLOCK_TIMEOUT = "strong-auth-unlock-timeout";
-        private static final String TAG_MIN_PASSWORD_NONLETTER = "min-password-nonletter";
-        private static final String TAG_MIN_PASSWORD_SYMBOLS = "min-password-symbols";
-        private static final String TAG_MIN_PASSWORD_NUMERIC = "min-password-numeric";
-        private static final String TAG_MIN_PASSWORD_LETTERS = "min-password-letters";
-        private static final String TAG_MIN_PASSWORD_LOWERCASE = "min-password-lowercase";
-        private static final String TAG_MIN_PASSWORD_UPPERCASE = "min-password-uppercase";
-        private static final String TAG_PASSWORD_HISTORY_LENGTH = "password-history-length";
-        private static final String TAG_MIN_PASSWORD_LENGTH = "min-password-length";
-        private static final String ATTR_VALUE = "value";
-        private static final String TAG_PASSWORD_QUALITY = "password-quality";
-        private static final String TAG_POLICIES = "policies";
-        private static final String TAG_CROSS_PROFILE_WIDGET_PROVIDERS =
-                "cross-profile-widget-providers";
-        private static final String TAG_PROVIDER = "provider";
-        private static final String TAG_PACKAGE_LIST_ITEM  = "item";
-        private static final String TAG_KEEP_UNINSTALLED_PACKAGES  = "keep-uninstalled-packages";
-        private static final String TAG_USER_RESTRICTIONS = "user-restrictions";
-        private static final String TAG_DEFAULT_ENABLED_USER_RESTRICTIONS =
-                "default-enabled-user-restrictions";
-        private static final String TAG_RESTRICTION = "restriction";
-        private static final String TAG_SHORT_SUPPORT_MESSAGE = "short-support-message";
-        private static final String TAG_LONG_SUPPORT_MESSAGE = "long-support-message";
-        private static final String TAG_PARENT_ADMIN = "parent-admin";
-        private static final String TAG_ORGANIZATION_COLOR = "organization-color";
-        private static final String TAG_ORGANIZATION_NAME = "organization-name";
-        private static final String ATTR_LAST_NETWORK_LOGGING_NOTIFICATION = "last-notification";
-        private static final String ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS = "num-notifications";
-        private static final String TAG_IS_LOGOUT_ENABLED = "is_logout_enabled";
-        private static final String TAG_START_USER_SESSION_MESSAGE = "start_user_session_message";
-        private static final String TAG_END_USER_SESSION_MESSAGE = "end_user_session_message";
-        private static final String TAG_METERED_DATA_DISABLED_PACKAGES =
-                "metered_data_disabled_packages";
-        private static final String TAG_CROSS_PROFILE_CALENDAR_PACKAGES =
-                "cross-profile-calendar-packages";
-        private static final String TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL =
-                "cross-profile-calendar-packages-null";
-        private static final String TAG_CROSS_PROFILE_PACKAGES = "cross-profile-packages";
-        private static final String TAG_FACTORY_RESET_PROTECTION_POLICY =
-                "factory_reset_protection_policy";
-        private static final String TAG_SUSPEND_PERSONAL_APPS = "suspend-personal-apps";
-        private static final String TAG_PROFILE_MAXIMUM_TIME_OFF = "profile-max-time-off";
-        private static final String TAG_PROFILE_OFF_DEADLINE = "profile-off-deadline";
-        private static final String TAG_ALWAYS_ON_VPN_PACKAGE = "vpn-package";
-        private static final String TAG_ALWAYS_ON_VPN_LOCKDOWN = "vpn-lockdown";
-        private static final String TAG_COMMON_CRITERIA_MODE = "common-criteria-mode";
-        DeviceAdminInfo info;
-
-
-        static final int DEF_PASSWORD_HISTORY_LENGTH = 0;
-        int passwordHistoryLength = DEF_PASSWORD_HISTORY_LENGTH;
-
-        @NonNull
-        PasswordPolicy mPasswordPolicy = new PasswordPolicy();
-
-        @Nullable
-        FactoryResetProtectionPolicy mFactoryResetProtectionPolicy = null;
-
-        static final long DEF_MAXIMUM_TIME_TO_UNLOCK = 0;
-        long maximumTimeToUnlock = DEF_MAXIMUM_TIME_TO_UNLOCK;
-
-        long strongAuthUnlockTimeout = 0; // admin doesn't participate by default
-
-        static final int DEF_MAXIMUM_FAILED_PASSWORDS_FOR_WIPE = 0;
-        int maximumFailedPasswordsForWipe = DEF_MAXIMUM_FAILED_PASSWORDS_FOR_WIPE;
-
-        static final long DEF_PASSWORD_EXPIRATION_TIMEOUT = 0;
-        long passwordExpirationTimeout = DEF_PASSWORD_EXPIRATION_TIMEOUT;
-
-        static final long DEF_PASSWORD_EXPIRATION_DATE = 0;
-        long passwordExpirationDate = DEF_PASSWORD_EXPIRATION_DATE;
-
-        static final int DEF_KEYGUARD_FEATURES_DISABLED = 0; // none
-
-        int disabledKeyguardFeatures = DEF_KEYGUARD_FEATURES_DISABLED;
-
-        boolean encryptionRequested = false;
-        boolean testOnlyAdmin = false;
-        boolean disableCamera = false;
-        boolean disableCallerId = false;
-        boolean disableContactsSearch = false;
-        boolean disableBluetoothContactSharing = true;
-        boolean disableScreenCapture = false; // Can only be set by a device/profile owner.
-        boolean requireAutoTime = false; // Can only be set by a device owner.
-        boolean forceEphemeralUsers = false; // Can only be set by a device owner.
-        boolean isNetworkLoggingEnabled = false; // Can only be set by a device owner.
-        boolean isLogoutEnabled = false; // Can only be set by a device owner.
-
-        // one notification after enabling + one more after reboots
-        static final int DEF_MAXIMUM_NETWORK_LOGGING_NOTIFICATIONS_SHOWN = 2;
-        int numNetworkLoggingNotifications = 0;
-        long lastNetworkLoggingNotificationTimeMs = 0; // Time in milliseconds since epoch
-
-        ActiveAdmin parentAdmin;
-        final boolean isParent;
-
-        static class TrustAgentInfo {
-            public PersistableBundle options;
-            TrustAgentInfo(PersistableBundle bundle) {
-                options = bundle;
-            }
-        }
-
-        // The list of packages which are not allowed to use metered data.
-        List<String> meteredDisabledPackages;
-
-        final Set<String> accountTypesWithManagementDisabled = new ArraySet<>();
-
-        // The list of permitted accessibility services package namesas set by a profile
-        // or device owner. Null means all accessibility services are allowed, empty means
-        // none except system services are allowed.
-        List<String> permittedAccessiblityServices;
-
-        // The list of permitted input methods package names as set by a profile or device owner.
-        // Null means all input methods are allowed, empty means none except system imes are
-        // allowed.
-        List<String> permittedInputMethods;
-
-        // The list of packages allowed to use a NotificationListenerService to receive events for
-        // notifications from this user. Null means that all packages are allowed. Empty list means
-        // that only packages from the system are allowed.
-        List<String> permittedNotificationListeners;
-
-        // List of package names to keep cached.
-        List<String> keepUninstalledPackages;
-
-        // TODO: review implementation decisions with frameworks team
-        boolean specifiesGlobalProxy = false;
-        String globalProxySpec = null;
-        String globalProxyExclusionList = null;
-
-        @NonNull ArrayMap<String, TrustAgentInfo> trustAgentInfos = new ArrayMap<>();
-
-        List<String> crossProfileWidgetProviders;
-
-        Bundle userRestrictions;
-
-        // User restrictions that have already been enabled by default for this admin (either when
-        // setting the device or profile owner, or during a system update if one of those "enabled
-        // by default" restrictions is newly added).
-        final Set<String> defaultEnabledRestrictionsAlreadySet = new ArraySet<>();
-
-        // Support text provided by the admin to display to the user.
-        CharSequence shortSupportMessage = null;
-        CharSequence longSupportMessage = null;
-
-        // Background color of confirm credentials screen. Default: teal.
-        static final int DEF_ORGANIZATION_COLOR = Color.parseColor("#00796B");
-        int organizationColor = DEF_ORGANIZATION_COLOR;
-
-        // Default title of confirm credentials screen
-        String organizationName = null;
-
-        // Message for user switcher
-        String startUserSessionMessage = null;
-        String endUserSessionMessage = null;
-
-        // The whitelist of packages that can access cross profile calendar APIs.
-        // This whitelist should be in default an empty list, which indicates that no package
-        // is whitelisted.
-        List<String> mCrossProfileCalendarPackages = Collections.emptyList();
-
-        // The whitelist of packages that the admin has enabled to be able to request consent from
-        // the user to communicate cross-profile. By default, no packages are whitelisted, which is
-        // represented as an empty list.
-        List<String> mCrossProfilePackages = Collections.emptyList();
-
-        // Whether the admin explicitly requires personal apps to be suspended
-        boolean mSuspendPersonalApps = false;
-        // Maximum time the profile owned by this admin can be off.
-        long mProfileMaximumTimeOffMillis = 0;
-        // Time by which the profile should be turned on according to System.currentTimeMillis().
-        long mProfileOffDeadline = 0;
-
-        public String mAlwaysOnVpnPackage;
-        public boolean mAlwaysOnVpnLockdown;
-        boolean mCommonCriteriaMode;
-
-        ActiveAdmin(DeviceAdminInfo _info, boolean parent) {
-            info = _info;
-            isParent = parent;
-        }
-
-        ActiveAdmin getParentActiveAdmin() {
-            Preconditions.checkState(!isParent);
-
-            if (parentAdmin == null) {
-                parentAdmin = new ActiveAdmin(info, /* parent */ true);
-            }
-            return parentAdmin;
-        }
-
-        boolean hasParentActiveAdmin() {
-            return parentAdmin != null;
-        }
-
-        int getUid() { return info.getActivityInfo().applicationInfo.uid; }
-
-        public UserHandle getUserHandle() {
-            return UserHandle.of(UserHandle.getUserId(info.getActivityInfo().applicationInfo.uid));
-        }
-
-        void writeToXml(XmlSerializer out)
-                throws IllegalArgumentException, IllegalStateException, IOException {
-            out.startTag(null, TAG_POLICIES);
-            info.writePoliciesToXml(out);
-            out.endTag(null, TAG_POLICIES);
-            if (mPasswordPolicy.quality != PASSWORD_QUALITY_UNSPECIFIED) {
-                writeAttributeValueToXml(
-                        out, TAG_PASSWORD_QUALITY, mPasswordPolicy.quality);
-                if (mPasswordPolicy.length != PasswordPolicy.DEF_MINIMUM_LENGTH) {
-                    writeAttributeValueToXml(
-                            out, TAG_MIN_PASSWORD_LENGTH, mPasswordPolicy.length);
-                }
-                if (mPasswordPolicy.upperCase != PasswordPolicy.DEF_MINIMUM_UPPER_CASE) {
-                    writeAttributeValueToXml(
-                            out, TAG_MIN_PASSWORD_UPPERCASE, mPasswordPolicy.upperCase);
-                }
-                if (mPasswordPolicy.lowerCase != PasswordPolicy.DEF_MINIMUM_LOWER_CASE) {
-                    writeAttributeValueToXml(
-                            out, TAG_MIN_PASSWORD_LOWERCASE, mPasswordPolicy.lowerCase);
-                }
-                if (mPasswordPolicy.letters != PasswordPolicy.DEF_MINIMUM_LETTERS) {
-                    writeAttributeValueToXml(
-                            out, TAG_MIN_PASSWORD_LETTERS, mPasswordPolicy.letters);
-                }
-                if (mPasswordPolicy.numeric != PasswordPolicy.DEF_MINIMUM_NUMERIC) {
-                    writeAttributeValueToXml(
-                            out, TAG_MIN_PASSWORD_NUMERIC, mPasswordPolicy.numeric);
-                }
-                if (mPasswordPolicy.symbols != PasswordPolicy.DEF_MINIMUM_SYMBOLS) {
-                    writeAttributeValueToXml(
-                            out, TAG_MIN_PASSWORD_SYMBOLS, mPasswordPolicy.symbols);
-                }
-                if (mPasswordPolicy.nonLetter > PasswordPolicy.DEF_MINIMUM_NON_LETTER) {
-                    writeAttributeValueToXml(
-                            out, TAG_MIN_PASSWORD_NONLETTER, mPasswordPolicy.nonLetter);
-                }
-            }
-            if (passwordHistoryLength != DEF_PASSWORD_HISTORY_LENGTH) {
-                writeAttributeValueToXml(
-                        out, TAG_PASSWORD_HISTORY_LENGTH, passwordHistoryLength);
-            }
-            if (maximumTimeToUnlock != DEF_MAXIMUM_TIME_TO_UNLOCK) {
-                writeAttributeValueToXml(
-                        out, TAG_MAX_TIME_TO_UNLOCK, maximumTimeToUnlock);
-            }
-            if (strongAuthUnlockTimeout != DevicePolicyManager.DEFAULT_STRONG_AUTH_TIMEOUT_MS) {
-                writeAttributeValueToXml(
-                        out, TAG_STRONG_AUTH_UNLOCK_TIMEOUT, strongAuthUnlockTimeout);
-            }
-            if (maximumFailedPasswordsForWipe != DEF_MAXIMUM_FAILED_PASSWORDS_FOR_WIPE) {
-                writeAttributeValueToXml(
-                        out, TAG_MAX_FAILED_PASSWORD_WIPE, maximumFailedPasswordsForWipe);
-            }
-            if (specifiesGlobalProxy) {
-                writeAttributeValueToXml(
-                        out, TAG_SPECIFIES_GLOBAL_PROXY, specifiesGlobalProxy);
-                if (globalProxySpec != null) {
-                    writeAttributeValueToXml(out, TAG_GLOBAL_PROXY_SPEC, globalProxySpec);
-                }
-                if (globalProxyExclusionList != null) {
-                    writeAttributeValueToXml(
-                            out, TAG_GLOBAL_PROXY_EXCLUSION_LIST, globalProxyExclusionList);
-                }
-            }
-            if (passwordExpirationTimeout != DEF_PASSWORD_EXPIRATION_TIMEOUT) {
-                writeAttributeValueToXml(
-                        out, TAG_PASSWORD_EXPIRATION_TIMEOUT, passwordExpirationTimeout);
-            }
-            if (passwordExpirationDate != DEF_PASSWORD_EXPIRATION_DATE) {
-                writeAttributeValueToXml(
-                        out, TAG_PASSWORD_EXPIRATION_DATE, passwordExpirationDate);
-            }
-            if (encryptionRequested) {
-                writeAttributeValueToXml(
-                        out, TAG_ENCRYPTION_REQUESTED, encryptionRequested);
-            }
-            if (testOnlyAdmin) {
-                writeAttributeValueToXml(
-                        out, TAG_TEST_ONLY_ADMIN, testOnlyAdmin);
-            }
-            if (disableCamera) {
-                writeAttributeValueToXml(
-                        out, TAG_DISABLE_CAMERA, disableCamera);
-            }
-            if (disableCallerId) {
-                writeAttributeValueToXml(
-                        out, TAG_DISABLE_CALLER_ID, disableCallerId);
-            }
-            if (disableContactsSearch) {
-                writeAttributeValueToXml(
-                        out, TAG_DISABLE_CONTACTS_SEARCH, disableContactsSearch);
-            }
-            if (!disableBluetoothContactSharing) {
-                writeAttributeValueToXml(
-                        out, TAG_DISABLE_BLUETOOTH_CONTACT_SHARING, disableBluetoothContactSharing);
-            }
-            if (disableScreenCapture) {
-                writeAttributeValueToXml(
-                        out, TAG_DISABLE_SCREEN_CAPTURE, disableScreenCapture);
-            }
-            if (requireAutoTime) {
-                writeAttributeValueToXml(
-                        out, TAG_REQUIRE_AUTO_TIME, requireAutoTime);
-            }
-            if (forceEphemeralUsers) {
-                writeAttributeValueToXml(
-                        out, TAG_FORCE_EPHEMERAL_USERS, forceEphemeralUsers);
-            }
-            if (isNetworkLoggingEnabled) {
-                out.startTag(null, TAG_IS_NETWORK_LOGGING_ENABLED);
-                out.attribute(null, ATTR_VALUE, Boolean.toString(isNetworkLoggingEnabled));
-                out.attribute(null, ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS,
-                        Integer.toString(numNetworkLoggingNotifications));
-                out.attribute(null, ATTR_LAST_NETWORK_LOGGING_NOTIFICATION,
-                        Long.toString(lastNetworkLoggingNotificationTimeMs));
-                out.endTag(null, TAG_IS_NETWORK_LOGGING_ENABLED);
-            }
-            if (disabledKeyguardFeatures != DEF_KEYGUARD_FEATURES_DISABLED) {
-                writeAttributeValueToXml(
-                        out, TAG_DISABLE_KEYGUARD_FEATURES, disabledKeyguardFeatures);
-            }
-            if (!accountTypesWithManagementDisabled.isEmpty()) {
-                writeAttributeValuesToXml(
-                        out, TAG_DISABLE_ACCOUNT_MANAGEMENT, TAG_ACCOUNT_TYPE,
-                        accountTypesWithManagementDisabled);
-            }
-            if (!trustAgentInfos.isEmpty()) {
-                Set<Entry<String, TrustAgentInfo>> set = trustAgentInfos.entrySet();
-                out.startTag(null, TAG_MANAGE_TRUST_AGENT_FEATURES);
-                for (Entry<String, TrustAgentInfo> entry : set) {
-                    TrustAgentInfo trustAgentInfo = entry.getValue();
-                    out.startTag(null, TAG_TRUST_AGENT_COMPONENT);
-                    out.attribute(null, ATTR_VALUE, entry.getKey());
-                    if (trustAgentInfo.options != null) {
-                        out.startTag(null, TAG_TRUST_AGENT_COMPONENT_OPTIONS);
-                        try {
-                            trustAgentInfo.options.saveToXml(out);
-                        } catch (XmlPullParserException e) {
-                            Log.e(LOG_TAG, "Failed to save TrustAgent options", e);
-                        }
-                        out.endTag(null, TAG_TRUST_AGENT_COMPONENT_OPTIONS);
-                    }
-                    out.endTag(null, TAG_TRUST_AGENT_COMPONENT);
-                }
-                out.endTag(null, TAG_MANAGE_TRUST_AGENT_FEATURES);
-            }
-            if (crossProfileWidgetProviders != null && !crossProfileWidgetProviders.isEmpty()) {
-                writeAttributeValuesToXml(
-                        out, TAG_CROSS_PROFILE_WIDGET_PROVIDERS, TAG_PROVIDER,
-                        crossProfileWidgetProviders);
-            }
-            writePackageListToXml(out, TAG_PERMITTED_ACCESSIBILITY_SERVICES,
-                    permittedAccessiblityServices);
-            writePackageListToXml(out, TAG_PERMITTED_IMES, permittedInputMethods);
-            writePackageListToXml(out, TAG_PERMITTED_NOTIFICATION_LISTENERS,
-                    permittedNotificationListeners);
-            writePackageListToXml(out, TAG_KEEP_UNINSTALLED_PACKAGES, keepUninstalledPackages);
-            writePackageListToXml(out, TAG_METERED_DATA_DISABLED_PACKAGES, meteredDisabledPackages);
-            if (hasUserRestrictions()) {
-                UserRestrictionsUtils.writeRestrictions(
-                        out, userRestrictions, TAG_USER_RESTRICTIONS);
-            }
-            if (!defaultEnabledRestrictionsAlreadySet.isEmpty()) {
-                writeAttributeValuesToXml(out, TAG_DEFAULT_ENABLED_USER_RESTRICTIONS,
-                        TAG_RESTRICTION,
-                        defaultEnabledRestrictionsAlreadySet);
-            }
-            if (!TextUtils.isEmpty(shortSupportMessage)) {
-                writeTextToXml(out, TAG_SHORT_SUPPORT_MESSAGE, shortSupportMessage.toString());
-            }
-            if (!TextUtils.isEmpty(longSupportMessage)) {
-                writeTextToXml(out, TAG_LONG_SUPPORT_MESSAGE, longSupportMessage.toString());
-            }
-            if (parentAdmin != null) {
-                out.startTag(null, TAG_PARENT_ADMIN);
-                parentAdmin.writeToXml(out);
-                out.endTag(null, TAG_PARENT_ADMIN);
-            }
-            if (organizationColor != DEF_ORGANIZATION_COLOR) {
-                writeAttributeValueToXml(out, TAG_ORGANIZATION_COLOR, organizationColor);
-            }
-            if (organizationName != null) {
-                writeTextToXml(out, TAG_ORGANIZATION_NAME, organizationName);
-            }
-            if (isLogoutEnabled) {
-                writeAttributeValueToXml(out, TAG_IS_LOGOUT_ENABLED, isLogoutEnabled);
-            }
-            if (startUserSessionMessage != null) {
-                writeTextToXml(out, TAG_START_USER_SESSION_MESSAGE, startUserSessionMessage);
-            }
-            if (endUserSessionMessage != null) {
-                writeTextToXml(out, TAG_END_USER_SESSION_MESSAGE, endUserSessionMessage);
-            }
-            if (mCrossProfileCalendarPackages == null) {
-                out.startTag(null, TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL);
-                out.endTag(null, TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL);
-            } else {
-                writePackageListToXml(out, TAG_CROSS_PROFILE_CALENDAR_PACKAGES,
-                        mCrossProfileCalendarPackages);
-            }
-            writePackageListToXml(out, TAG_CROSS_PROFILE_PACKAGES, mCrossProfilePackages);
-            if (mFactoryResetProtectionPolicy != null) {
-                out.startTag(null, TAG_FACTORY_RESET_PROTECTION_POLICY);
-                mFactoryResetProtectionPolicy.writeToXml(out);
-                out.endTag(null, TAG_FACTORY_RESET_PROTECTION_POLICY);
-            }
-            if (mSuspendPersonalApps) {
-                writeAttributeValueToXml(out, TAG_SUSPEND_PERSONAL_APPS, mSuspendPersonalApps);
-            }
-            if (mProfileMaximumTimeOffMillis != 0) {
-                writeAttributeValueToXml(out, TAG_PROFILE_MAXIMUM_TIME_OFF,
-                        mProfileMaximumTimeOffMillis);
-            }
-            if (mProfileMaximumTimeOffMillis != 0) {
-                writeAttributeValueToXml(out, TAG_PROFILE_OFF_DEADLINE, mProfileOffDeadline);
-            }
-            if (!TextUtils.isEmpty(mAlwaysOnVpnPackage)) {
-                writeAttributeValueToXml(out, TAG_ALWAYS_ON_VPN_PACKAGE, mAlwaysOnVpnPackage);
-            }
-            if (mAlwaysOnVpnLockdown) {
-                writeAttributeValueToXml(out, TAG_ALWAYS_ON_VPN_LOCKDOWN, mAlwaysOnVpnLockdown);
-            }
-            if (mCommonCriteriaMode) {
-                writeAttributeValueToXml(out, TAG_COMMON_CRITERIA_MODE, mCommonCriteriaMode);
-            }
-        }
-
-        void writeTextToXml(XmlSerializer out, String tag, String text) throws IOException {
-            out.startTag(null, tag);
-            out.text(text);
-            out.endTag(null, tag);
-        }
-
-        void writePackageListToXml(XmlSerializer out, String outerTag,
-                List<String> packageList)
-                throws IllegalArgumentException, IllegalStateException, IOException {
-            if (packageList == null) {
-                return;
-            }
-            writeAttributeValuesToXml(out, outerTag, TAG_PACKAGE_LIST_ITEM, packageList);
-        }
-
-        void writeAttributeValueToXml(XmlSerializer out, String tag, String value)
-                throws IOException {
-            out.startTag(null, tag);
-            out.attribute(null, ATTR_VALUE, value);
-            out.endTag(null, tag);
-        }
-
-        void writeAttributeValueToXml(XmlSerializer out, String tag, int value)
-                throws IOException {
-            out.startTag(null, tag);
-            out.attribute(null, ATTR_VALUE, Integer.toString(value));
-            out.endTag(null, tag);
-        }
-
-        void writeAttributeValueToXml(XmlSerializer out, String tag, long value)
-                throws IOException {
-            out.startTag(null, tag);
-            out.attribute(null, ATTR_VALUE, Long.toString(value));
-            out.endTag(null, tag);
-        }
-
-        void writeAttributeValueToXml(XmlSerializer out, String tag, boolean value)
-                throws IOException {
-            out.startTag(null, tag);
-            out.attribute(null, ATTR_VALUE, Boolean.toString(value));
-            out.endTag(null, tag);
-        }
-
-        void writeAttributeValuesToXml(XmlSerializer out, String outerTag, String innerTag,
-                @NonNull Collection<String> values) throws IOException {
-            out.startTag(null, outerTag);
-            for (String value : values) {
-                out.startTag(null, innerTag);
-                out.attribute(null, ATTR_VALUE, value);
-                out.endTag(null, innerTag);
-            }
-            out.endTag(null, outerTag);
-        }
-
-        void readFromXml(XmlPullParser parser, boolean shouldOverridePolicies)
-                throws XmlPullParserException, IOException {
-            int outerDepth = parser.getDepth();
-            int type;
-            while ((type=parser.next()) != END_DOCUMENT
-                   && (type != END_TAG || parser.getDepth() > outerDepth)) {
-                if (type == END_TAG || type == TEXT) {
-                    continue;
-                }
-                String tag = parser.getName();
-                if (TAG_POLICIES.equals(tag)) {
-                    if (shouldOverridePolicies) {
-                        Log.d(LOG_TAG, "Overriding device admin policies from XML.");
-                        info.readPoliciesFromXml(parser);
-                    }
-                } else if (TAG_PASSWORD_QUALITY.equals(tag)) {
-                    mPasswordPolicy.quality = Integer.parseInt(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_MIN_PASSWORD_LENGTH.equals(tag)) {
-                    mPasswordPolicy.length = Integer.parseInt(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_PASSWORD_HISTORY_LENGTH.equals(tag)) {
-                    passwordHistoryLength = Integer.parseInt(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_MIN_PASSWORD_UPPERCASE.equals(tag)) {
-                    mPasswordPolicy.upperCase = Integer.parseInt(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_MIN_PASSWORD_LOWERCASE.equals(tag)) {
-                    mPasswordPolicy.lowerCase = Integer.parseInt(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_MIN_PASSWORD_LETTERS.equals(tag)) {
-                    mPasswordPolicy.letters = Integer.parseInt(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_MIN_PASSWORD_NUMERIC.equals(tag)) {
-                    mPasswordPolicy.numeric = Integer.parseInt(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_MIN_PASSWORD_SYMBOLS.equals(tag)) {
-                    mPasswordPolicy.symbols = Integer.parseInt(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_MIN_PASSWORD_NONLETTER.equals(tag)) {
-                    mPasswordPolicy.nonLetter = Integer.parseInt(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                }else if (TAG_MAX_TIME_TO_UNLOCK.equals(tag)) {
-                    maximumTimeToUnlock = Long.parseLong(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_STRONG_AUTH_UNLOCK_TIMEOUT.equals(tag)) {
-                    strongAuthUnlockTimeout = Long.parseLong(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_MAX_FAILED_PASSWORD_WIPE.equals(tag)) {
-                    maximumFailedPasswordsForWipe = Integer.parseInt(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_SPECIFIES_GLOBAL_PROXY.equals(tag)) {
-                    specifiesGlobalProxy = Boolean.parseBoolean(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_GLOBAL_PROXY_SPEC.equals(tag)) {
-                    globalProxySpec =
-                        parser.getAttributeValue(null, ATTR_VALUE);
-                } else if (TAG_GLOBAL_PROXY_EXCLUSION_LIST.equals(tag)) {
-                    globalProxyExclusionList =
-                        parser.getAttributeValue(null, ATTR_VALUE);
-                } else if (TAG_PASSWORD_EXPIRATION_TIMEOUT.equals(tag)) {
-                    passwordExpirationTimeout = Long.parseLong(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_PASSWORD_EXPIRATION_DATE.equals(tag)) {
-                    passwordExpirationDate = Long.parseLong(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_ENCRYPTION_REQUESTED.equals(tag)) {
-                    encryptionRequested = Boolean.parseBoolean(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_TEST_ONLY_ADMIN.equals(tag)) {
-                    testOnlyAdmin = Boolean.parseBoolean(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_DISABLE_CAMERA.equals(tag)) {
-                    disableCamera = Boolean.parseBoolean(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_DISABLE_CALLER_ID.equals(tag)) {
-                    disableCallerId = Boolean.parseBoolean(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_DISABLE_CONTACTS_SEARCH.equals(tag)) {
-                    disableContactsSearch = Boolean.parseBoolean(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_DISABLE_BLUETOOTH_CONTACT_SHARING.equals(tag)) {
-                    disableBluetoothContactSharing = Boolean.parseBoolean(parser
-                            .getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_DISABLE_SCREEN_CAPTURE.equals(tag)) {
-                    disableScreenCapture = Boolean.parseBoolean(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_REQUIRE_AUTO_TIME.equals(tag)) {
-                    requireAutoTime = Boolean.parseBoolean(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_FORCE_EPHEMERAL_USERS.equals(tag)) {
-                    forceEphemeralUsers = Boolean.parseBoolean(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_IS_NETWORK_LOGGING_ENABLED.equals(tag)) {
-                    isNetworkLoggingEnabled = Boolean.parseBoolean(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                    lastNetworkLoggingNotificationTimeMs = Long.parseLong(
-                            parser.getAttributeValue(null, ATTR_LAST_NETWORK_LOGGING_NOTIFICATION));
-                    numNetworkLoggingNotifications = Integer.parseInt(
-                            parser.getAttributeValue(null, ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS));
-                } else if (TAG_DISABLE_KEYGUARD_FEATURES.equals(tag)) {
-                    disabledKeyguardFeatures = Integer.parseInt(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_DISABLE_ACCOUNT_MANAGEMENT.equals(tag)) {
-                    readAttributeValues(
-                            parser, TAG_ACCOUNT_TYPE, accountTypesWithManagementDisabled);
-                } else if (TAG_MANAGE_TRUST_AGENT_FEATURES.equals(tag)) {
-                    trustAgentInfos = getAllTrustAgentInfos(parser, tag);
-                } else if (TAG_CROSS_PROFILE_WIDGET_PROVIDERS.equals(tag)) {
-                    crossProfileWidgetProviders = new ArrayList<>();
-                    readAttributeValues(parser, TAG_PROVIDER, crossProfileWidgetProviders);
-                } else if (TAG_PERMITTED_ACCESSIBILITY_SERVICES.equals(tag)) {
-                    permittedAccessiblityServices = readPackageList(parser, tag);
-                } else if (TAG_PERMITTED_IMES.equals(tag)) {
-                    permittedInputMethods = readPackageList(parser, tag);
-                } else if (TAG_PERMITTED_NOTIFICATION_LISTENERS.equals(tag)) {
-                    permittedNotificationListeners = readPackageList(parser, tag);
-                } else if (TAG_KEEP_UNINSTALLED_PACKAGES.equals(tag)) {
-                    keepUninstalledPackages = readPackageList(parser, tag);
-                } else if (TAG_METERED_DATA_DISABLED_PACKAGES.equals(tag)) {
-                    meteredDisabledPackages = readPackageList(parser, tag);
-                } else if (TAG_USER_RESTRICTIONS.equals(tag)) {
-                    userRestrictions = UserRestrictionsUtils.readRestrictions(parser);
-                } else if (TAG_DEFAULT_ENABLED_USER_RESTRICTIONS.equals(tag)) {
-                    readAttributeValues(
-                            parser, TAG_RESTRICTION, defaultEnabledRestrictionsAlreadySet);
-                } else if (TAG_SHORT_SUPPORT_MESSAGE.equals(tag)) {
-                    type = parser.next();
-                    if (type == XmlPullParser.TEXT) {
-                        shortSupportMessage = parser.getText();
-                    } else {
-                        Log.w(LOG_TAG, "Missing text when loading short support message");
-                    }
-                } else if (TAG_LONG_SUPPORT_MESSAGE.equals(tag)) {
-                    type = parser.next();
-                    if (type == XmlPullParser.TEXT) {
-                        longSupportMessage = parser.getText();
-                    } else {
-                        Log.w(LOG_TAG, "Missing text when loading long support message");
-                    }
-                } else if (TAG_PARENT_ADMIN.equals(tag)) {
-                    Preconditions.checkState(!isParent);
-                    parentAdmin = new ActiveAdmin(info, /* parent */ true);
-                    parentAdmin.readFromXml(parser, shouldOverridePolicies);
-                } else if (TAG_ORGANIZATION_COLOR.equals(tag)) {
-                    organizationColor = Integer.parseInt(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_ORGANIZATION_NAME.equals(tag)) {
-                    type = parser.next();
-                    if (type == XmlPullParser.TEXT) {
-                        organizationName = parser.getText();
-                    } else {
-                        Log.w(LOG_TAG, "Missing text when loading organization name");
-                    }
-                } else if (TAG_IS_LOGOUT_ENABLED.equals(tag)) {
-                    isLogoutEnabled = Boolean.parseBoolean(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_START_USER_SESSION_MESSAGE.equals(tag)) {
-                    type = parser.next();
-                    if (type == XmlPullParser.TEXT) {
-                        startUserSessionMessage = parser.getText();
-                    } else {
-                        Log.w(LOG_TAG, "Missing text when loading start session message");
-                    }
-                } else if (TAG_END_USER_SESSION_MESSAGE.equals(tag)) {
-                    type = parser.next();
-                    if (type == XmlPullParser.TEXT) {
-                        endUserSessionMessage = parser.getText();
-                    } else {
-                        Log.w(LOG_TAG, "Missing text when loading end session message");
-                    }
-                } else if (TAG_CROSS_PROFILE_CALENDAR_PACKAGES.equals(tag)) {
-                    mCrossProfileCalendarPackages = readPackageList(parser, tag);
-                } else if (TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL.equals(tag)) {
-                    mCrossProfileCalendarPackages = null;
-                } else if (TAG_CROSS_PROFILE_PACKAGES.equals(tag)) {
-                    mCrossProfilePackages = readPackageList(parser, tag);
-                } else if (TAG_FACTORY_RESET_PROTECTION_POLICY.equals(tag)) {
-                    mFactoryResetProtectionPolicy = FactoryResetProtectionPolicy.readFromXml(
-                                parser);
-                } else if (TAG_SUSPEND_PERSONAL_APPS.equals(tag)) {
-                    mSuspendPersonalApps = Boolean.parseBoolean(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_PROFILE_MAXIMUM_TIME_OFF.equals(tag)) {
-                    mProfileMaximumTimeOffMillis =
-                            Long.parseLong(parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_PROFILE_OFF_DEADLINE.equals(tag)) {
-                    mProfileOffDeadline =
-                            Long.parseLong(parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_ALWAYS_ON_VPN_PACKAGE.equals(tag)) {
-                    mAlwaysOnVpnPackage = parser.getAttributeValue(null, ATTR_VALUE);
-                } else if (TAG_ALWAYS_ON_VPN_LOCKDOWN.equals(tag)) {
-                    mAlwaysOnVpnLockdown = Boolean.parseBoolean(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_COMMON_CRITERIA_MODE.equals(tag)) {
-                    mCommonCriteriaMode = Boolean.parseBoolean(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else {
-                    Slog.w(LOG_TAG, "Unknown admin tag: " + tag);
-                    XmlUtils.skipCurrentTag(parser);
-                }
-            }
-        }
-
-        private List<String> readPackageList(XmlPullParser parser,
-                String tag) throws XmlPullParserException, IOException {
-            List<String> result = new ArrayList<String>();
-            int outerDepth = parser.getDepth();
-            int outerType;
-            while ((outerType=parser.next()) != XmlPullParser.END_DOCUMENT
-                    && (outerType != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-                if (outerType == XmlPullParser.END_TAG || outerType == XmlPullParser.TEXT) {
-                    continue;
-                }
-                String outerTag = parser.getName();
-                if (TAG_PACKAGE_LIST_ITEM.equals(outerTag)) {
-                    String packageName = parser.getAttributeValue(null, ATTR_VALUE);
-                    if (packageName != null) {
-                        result.add(packageName);
-                    } else {
-                        Slog.w(LOG_TAG, "Package name missing under " + outerTag);
-                    }
-                } else {
-                    Slog.w(LOG_TAG, "Unknown tag under " + tag +  ": " + outerTag);
-                }
-            }
-            return result;
-        }
-
-        private void readAttributeValues(
-                XmlPullParser parser, String tag, Collection<String> result)
-                throws XmlPullParserException, IOException {
-            result.clear();
-            int outerDepthDAM = parser.getDepth();
-            int typeDAM;
-            while ((typeDAM=parser.next()) != END_DOCUMENT
-                    && (typeDAM != END_TAG || parser.getDepth() > outerDepthDAM)) {
-                if (typeDAM == END_TAG || typeDAM == TEXT) {
-                    continue;
-                }
-                String tagDAM = parser.getName();
-                if (tag.equals(tagDAM)) {
-                    result.add(parser.getAttributeValue(null, ATTR_VALUE));
-                } else {
-                    Slog.e(LOG_TAG, "Expected tag " + tag +  " but found " + tagDAM);
-                }
-            }
-        }
-
-        @NonNull
-        private ArrayMap<String, TrustAgentInfo> getAllTrustAgentInfos(
-                XmlPullParser parser, String tag) throws XmlPullParserException, IOException {
-            int outerDepthDAM = parser.getDepth();
-            int typeDAM;
-            final ArrayMap<String, TrustAgentInfo> result = new ArrayMap<>();
-            while ((typeDAM=parser.next()) != END_DOCUMENT
-                    && (typeDAM != END_TAG || parser.getDepth() > outerDepthDAM)) {
-                if (typeDAM == END_TAG || typeDAM == TEXT) {
-                    continue;
-                }
-                String tagDAM = parser.getName();
-                if (TAG_TRUST_AGENT_COMPONENT.equals(tagDAM)) {
-                    final String component = parser.getAttributeValue(null, ATTR_VALUE);
-                    final TrustAgentInfo trustAgentInfo = getTrustAgentInfo(parser, tag);
-                    result.put(component, trustAgentInfo);
-                } else {
-                    Slog.w(LOG_TAG, "Unknown tag under " + tag +  ": " + tagDAM);
-                }
-            }
-            return result;
-        }
-
-        private TrustAgentInfo getTrustAgentInfo(XmlPullParser parser, String tag)
-                throws XmlPullParserException, IOException  {
-            int outerDepthDAM = parser.getDepth();
-            int typeDAM;
-            TrustAgentInfo result = new TrustAgentInfo(null);
-            while ((typeDAM=parser.next()) != END_DOCUMENT
-                    && (typeDAM != END_TAG || parser.getDepth() > outerDepthDAM)) {
-                if (typeDAM == END_TAG || typeDAM == TEXT) {
-                    continue;
-                }
-                String tagDAM = parser.getName();
-                if (TAG_TRUST_AGENT_COMPONENT_OPTIONS.equals(tagDAM)) {
-                    result.options = PersistableBundle.restoreFromXml(parser);
-                } else {
-                    Slog.w(LOG_TAG, "Unknown tag under " + tag +  ": " + tagDAM);
-                }
-            }
-            return result;
-        }
-
-        boolean hasUserRestrictions() {
-            return userRestrictions != null && userRestrictions.size() > 0;
-        }
-
-        Bundle ensureUserRestrictions() {
-            if (userRestrictions == null) {
-                userRestrictions = new Bundle();
-            }
-            return userRestrictions;
-        }
-
-        public void transfer(DeviceAdminInfo deviceAdminInfo) {
-            if (hasParentActiveAdmin()) {
-                parentAdmin.info = deviceAdminInfo;
-            }
-            info = deviceAdminInfo;
-        }
-
-        Bundle addSyntheticRestrictions(Bundle restrictions) {
-            if (disableCamera) {
-                restrictions.putBoolean(UserManager.DISALLOW_CAMERA, true);
-            }
-            if (requireAutoTime) {
-                restrictions.putBoolean(UserManager.DISALLOW_CONFIG_DATE_TIME, true);
-            }
-            return restrictions;
-        }
-
-        static Bundle removeDeprecatedRestrictions(Bundle restrictions) {
-            for (String deprecatedRestriction: DEPRECATED_USER_RESTRICTIONS) {
-                restrictions.remove(deprecatedRestriction);
-            }
-            return restrictions;
-        }
-
-        static Bundle filterRestrictions(Bundle restrictions, Predicate<String> filter) {
-            Bundle result = new Bundle();
-            for (String key : restrictions.keySet()) {
-                if (!restrictions.getBoolean(key)) {
-                    continue;
-                }
-                if (filter.test(key)) {
-                    result.putBoolean(key, true);
-                }
-            }
-            return result;
-        }
-
-        Bundle getEffectiveRestrictions() {
-            return addSyntheticRestrictions(
-                    removeDeprecatedRestrictions(new Bundle(ensureUserRestrictions())));
-        }
-
-        Bundle getLocalUserRestrictions(int adminType) {
-            return filterRestrictions(getEffectiveRestrictions(),
-                    key -> UserRestrictionsUtils.isLocal(adminType, key));
-        }
-
-        Bundle getGlobalUserRestrictions(int adminType) {
-            return filterRestrictions(getEffectiveRestrictions(),
-                    key -> UserRestrictionsUtils.isGlobal(adminType, key));
-        }
-
-        void dump(IndentingPrintWriter pw) {
-            pw.print("uid="); pw.println(getUid());
-            pw.print("testOnlyAdmin=");
-            pw.println(testOnlyAdmin);
-            pw.println("policies:");
-            ArrayList<DeviceAdminInfo.PolicyInfo> pols = info.getUsedPolicies();
-            if (pols != null) {
-                pw.increaseIndent();
-                for (int i=0; i<pols.size(); i++) {
-                    pw.println(pols.get(i).tag);
-                }
-                pw.decreaseIndent();
-            }
-            pw.print("passwordQuality=0x");
-                    pw.println(Integer.toHexString(mPasswordPolicy.quality));
-            pw.print("minimumPasswordLength=");
-                    pw.println(mPasswordPolicy.length);
-            pw.print("passwordHistoryLength=");
-                    pw.println(passwordHistoryLength);
-            pw.print("minimumPasswordUpperCase=");
-                    pw.println(mPasswordPolicy.upperCase);
-            pw.print("minimumPasswordLowerCase=");
-                    pw.println(mPasswordPolicy.lowerCase);
-            pw.print("minimumPasswordLetters=");
-                    pw.println(mPasswordPolicy.letters);
-            pw.print("minimumPasswordNumeric=");
-                    pw.println(mPasswordPolicy.numeric);
-            pw.print("minimumPasswordSymbols=");
-                    pw.println(mPasswordPolicy.symbols);
-            pw.print("minimumPasswordNonLetter=");
-                    pw.println(mPasswordPolicy.nonLetter);
-            pw.print("maximumTimeToUnlock=");
-                    pw.println(maximumTimeToUnlock);
-            pw.print("strongAuthUnlockTimeout=");
-                    pw.println(strongAuthUnlockTimeout);
-            pw.print("maximumFailedPasswordsForWipe=");
-                    pw.println(maximumFailedPasswordsForWipe);
-            pw.print("specifiesGlobalProxy=");
-                    pw.println(specifiesGlobalProxy);
-            pw.print("passwordExpirationTimeout=");
-                    pw.println(passwordExpirationTimeout);
-            pw.print("passwordExpirationDate=");
-                    pw.println(passwordExpirationDate);
-            if (globalProxySpec != null) {
-                pw.print("globalProxySpec=");
-                        pw.println(globalProxySpec);
-            }
-            if (globalProxyExclusionList != null) {
-                pw.print("globalProxyEclusionList=");
-                        pw.println(globalProxyExclusionList);
-            }
-            pw.print("encryptionRequested=");
-                    pw.println(encryptionRequested);
-            pw.print("disableCamera=");
-                    pw.println(disableCamera);
-            pw.print("disableCallerId=");
-                    pw.println(disableCallerId);
-            pw.print("disableContactsSearch=");
-                    pw.println(disableContactsSearch);
-            pw.print("disableBluetoothContactSharing=");
-                    pw.println(disableBluetoothContactSharing);
-            pw.print("disableScreenCapture=");
-                    pw.println(disableScreenCapture);
-            pw.print("requireAutoTime=");
-                    pw.println(requireAutoTime);
-            pw.print("forceEphemeralUsers=");
-                    pw.println(forceEphemeralUsers);
-            pw.print("isNetworkLoggingEnabled=");
-                    pw.println(isNetworkLoggingEnabled);
-            pw.print("disabledKeyguardFeatures=");
-                    pw.println(disabledKeyguardFeatures);
-            pw.print("crossProfileWidgetProviders=");
-                    pw.println(crossProfileWidgetProviders);
-            if (permittedAccessiblityServices != null) {
-                pw.print("permittedAccessibilityServices=");
-                    pw.println(permittedAccessiblityServices);
-            }
-            if (permittedInputMethods != null) {
-                pw.print("permittedInputMethods=");
-                    pw.println(permittedInputMethods);
-            }
-            if (permittedNotificationListeners != null) {
-                pw.print("permittedNotificationListeners=");
-                pw.println(permittedNotificationListeners);
-            }
-            if (keepUninstalledPackages != null) {
-                pw.print("keepUninstalledPackages=");
-                    pw.println(keepUninstalledPackages);
-            }
-            pw.print("organizationColor=");
-                    pw.println(organizationColor);
-            if (organizationName != null) {
-                pw.print("organizationName=");
-                    pw.println(organizationName);
-            }
-            pw.println("userRestrictions:");
-            UserRestrictionsUtils.dumpRestrictions(pw, "  ", userRestrictions);
-            pw.print("defaultEnabledRestrictionsAlreadySet=");
-                    pw.println(defaultEnabledRestrictionsAlreadySet);
-            pw.print("isParent=");
-                    pw.println(isParent);
-            if (parentAdmin != null) {
-                pw.println("parentAdmin:");
-                pw.increaseIndent();
-                parentAdmin.dump(pw);
-                pw.decreaseIndent();
-            }
-            if (mCrossProfileCalendarPackages != null) {
-                pw.print("mCrossProfileCalendarPackages=");
-                pw.println(mCrossProfileCalendarPackages);
-            }
-            pw.print("mCrossProfilePackages=");
-                pw.println(mCrossProfilePackages);
-            pw.print("mSuspendPersonalApps=");
-                pw.println(mSuspendPersonalApps);
-            pw.print("mProfileMaximumTimeOffMillis=");
-                pw.println(mProfileMaximumTimeOffMillis);
-            pw.print("mProfileOffDeadline=");
-                pw.println(mProfileOffDeadline);
-            pw.print("mAlwaysOnVpnPackage=");
-            pw.println(mAlwaysOnVpnPackage);
-            pw.print("mAlwaysOnVpnLockdown=");
-            pw.println(mAlwaysOnVpnLockdown);
-            pw.print("mCommonCriteriaMode=");
-            pw.println(mCommonCriteriaMode);
-        }
-    }
-
     private void handlePackagesChanged(@Nullable String packageName, int userHandle) {
         boolean removedAdmin = false;
         if (VERBOSE_LOG) {
@@ -2073,7 +934,7 @@
                 }
             }
             if (removedAdmin) {
-                validatePasswordOwnerLocked(policy);
+                policy.validatePasswordOwner();
             }
 
             boolean removedDelegate = false;
@@ -2703,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.
      */
@@ -3514,13 +2409,8 @@
         }
     }
 
-
-    public DeviceAdminInfo findAdmin(final ComponentName adminName, final int userHandle,
+    private DeviceAdminInfo findAdmin(final ComponentName adminName, final int userHandle,
             boolean throwForMissingPermission) {
-        if (!mHasFeature) {
-            return null;
-        }
-        enforceFullCrossUsersPermission(userHandle);
         final ActivityInfo ai = mInjector.binderWithCleanCallingIdentity(() -> {
             try {
                 return mIPackageManager.getReceiverInfo(adminName,
@@ -3583,213 +2473,11 @@
     }
 
     private void saveSettingsLocked(int userHandle) {
-        DevicePolicyData policy = getUserData(userHandle);
-        JournaledFile journal = makeJournaledFile(userHandle);
-        FileOutputStream stream = null;
-        try {
-            stream = new FileOutputStream(journal.chooseForWrite(), false);
-            XmlSerializer out = new FastXmlSerializer();
-            out.setOutput(stream, StandardCharsets.UTF_8.name());
-            out.startDocument(null, true);
-
-            out.startTag(null, "policies");
-            if (policy.mRestrictionsProvider != null) {
-                out.attribute(null, ATTR_PERMISSION_PROVIDER,
-                        policy.mRestrictionsProvider.flattenToString());
-            }
-            if (policy.mUserSetupComplete) {
-                out.attribute(null, ATTR_SETUP_COMPLETE,
-                        Boolean.toString(true));
-            }
-            if (policy.mPaired) {
-                out.attribute(null, ATTR_DEVICE_PAIRED,
-                        Boolean.toString(true));
-            }
-            if (policy.mDeviceProvisioningConfigApplied) {
-                out.attribute(null, ATTR_DEVICE_PROVISIONING_CONFIG_APPLIED,
-                        Boolean.toString(true));
-            }
-            if (policy.mUserProvisioningState != DevicePolicyManager.STATE_USER_UNMANAGED) {
-                out.attribute(null, ATTR_PROVISIONING_STATE,
-                        Integer.toString(policy.mUserProvisioningState));
-            }
-            if (policy.mPermissionPolicy != DevicePolicyManager.PERMISSION_POLICY_PROMPT) {
-                out.attribute(null, ATTR_PERMISSION_POLICY,
-                        Integer.toString(policy.mPermissionPolicy));
-            }
-
-            // Serialize delegations.
-            for (int i = 0; i < policy.mDelegationMap.size(); ++i) {
-                final String delegatePackage = policy.mDelegationMap.keyAt(i);
-                final List<String> scopes = policy.mDelegationMap.valueAt(i);
-
-                // Every "delegation" tag serializes the information of one delegate-scope pair.
-                for (String scope : scopes) {
-                    out.startTag(null, "delegation");
-                    out.attribute(null, "delegatePackage", delegatePackage);
-                    out.attribute(null, "scope", scope);
-                    out.endTag(null, "delegation");
-                }
-            }
-
-            final int N = policy.mAdminList.size();
-            for (int i=0; i<N; i++) {
-                ActiveAdmin ap = policy.mAdminList.get(i);
-                if (ap != null) {
-                    out.startTag(null, "admin");
-                    out.attribute(null, "name", ap.info.getComponent().flattenToString());
-                    ap.writeToXml(out);
-                    out.endTag(null, "admin");
-                }
-            }
-
-            if (policy.mPasswordOwner >= 0) {
-                out.startTag(null, "password-owner");
-                out.attribute(null, "value", Integer.toString(policy.mPasswordOwner));
-                out.endTag(null, "password-owner");
-            }
-
-            if (policy.mFailedPasswordAttempts != 0) {
-                out.startTag(null, "failed-password-attempts");
-                out.attribute(null, "value", Integer.toString(policy.mFailedPasswordAttempts));
-                out.endTag(null, "failed-password-attempts");
-            }
-
-            // For FDE devices only, we save this flag so we can report on password sufficiency
-            // before the user enters their password for the first time after a reboot.  For
-            // security reasons, we don't want to store the full set of active password metrics.
-            if (!mInjector.storageManagerIsFileBasedEncryptionEnabled()) {
-                out.startTag(null, TAG_PASSWORD_VALIDITY);
-                out.attribute(null, ATTR_VALUE,
-                        Boolean.toString(policy.mPasswordValidAtLastCheckpoint));
-                out.endTag(null, TAG_PASSWORD_VALIDITY);
-            }
-
-            for (int i = 0; i < policy.mAcceptedCaCertificates.size(); i++) {
-                out.startTag(null, TAG_ACCEPTED_CA_CERTIFICATES);
-                out.attribute(null, ATTR_NAME, policy.mAcceptedCaCertificates.valueAt(i));
-                out.endTag(null, TAG_ACCEPTED_CA_CERTIFICATES);
-            }
-
-            for (int i=0; i<policy.mLockTaskPackages.size(); i++) {
-                String component = policy.mLockTaskPackages.get(i);
-                out.startTag(null, TAG_LOCK_TASK_COMPONENTS);
-                out.attribute(null, "name", component);
-                out.endTag(null, TAG_LOCK_TASK_COMPONENTS);
-            }
-
-            if (policy.mLockTaskFeatures != DevicePolicyManager.LOCK_TASK_FEATURE_NONE) {
-                out.startTag(null, TAG_LOCK_TASK_FEATURES);
-                out.attribute(null, ATTR_VALUE, Integer.toString(policy.mLockTaskFeatures));
-                out.endTag(null, TAG_LOCK_TASK_FEATURES);
-            }
-
-            if (policy.mSecondaryLockscreenEnabled) {
-                out.startTag(null, TAG_SECONDARY_LOCK_SCREEN);
-                out.attribute(null, ATTR_VALUE, Boolean.toString(true));
-                out.endTag(null, TAG_SECONDARY_LOCK_SCREEN);
-            }
-
-            if (policy.mStatusBarDisabled) {
-                out.startTag(null, TAG_STATUS_BAR);
-                out.attribute(null, ATTR_DISABLED, Boolean.toString(policy.mStatusBarDisabled));
-                out.endTag(null, TAG_STATUS_BAR);
-            }
-
-            if (policy.doNotAskCredentialsOnBoot) {
-                out.startTag(null, DO_NOT_ASK_CREDENTIALS_ON_BOOT_XML);
-                out.endTag(null, DO_NOT_ASK_CREDENTIALS_ON_BOOT_XML);
-            }
-
-            for (String id : policy.mAffiliationIds) {
-                out.startTag(null, TAG_AFFILIATION_ID);
-                out.attribute(null, ATTR_ID, id);
-                out.endTag(null, TAG_AFFILIATION_ID);
-            }
-
-            if (policy.mLastSecurityLogRetrievalTime >= 0) {
-                out.startTag(null, TAG_LAST_SECURITY_LOG_RETRIEVAL);
-                out.attribute(null, ATTR_VALUE,
-                        Long.toString(policy.mLastSecurityLogRetrievalTime));
-                out.endTag(null, TAG_LAST_SECURITY_LOG_RETRIEVAL);
-            }
-
-            if (policy.mLastBugReportRequestTime >= 0) {
-                out.startTag(null, TAG_LAST_BUG_REPORT_REQUEST);
-                out.attribute(null, ATTR_VALUE,
-                        Long.toString(policy.mLastBugReportRequestTime));
-                out.endTag(null, TAG_LAST_BUG_REPORT_REQUEST);
-            }
-
-            if (policy.mLastNetworkLogsRetrievalTime >= 0) {
-                out.startTag(null, TAG_LAST_NETWORK_LOG_RETRIEVAL);
-                out.attribute(null, ATTR_VALUE,
-                        Long.toString(policy.mLastNetworkLogsRetrievalTime));
-                out.endTag(null, TAG_LAST_NETWORK_LOG_RETRIEVAL);
-            }
-
-            if (policy.mAdminBroadcastPending) {
-                out.startTag(null, TAG_ADMIN_BROADCAST_PENDING);
-                out.attribute(null, ATTR_VALUE,
-                        Boolean.toString(policy.mAdminBroadcastPending));
-                out.endTag(null, TAG_ADMIN_BROADCAST_PENDING);
-            }
-
-            if (policy.mInitBundle != null) {
-                out.startTag(null, TAG_INITIALIZATION_BUNDLE);
-                policy.mInitBundle.saveToXml(out);
-                out.endTag(null, TAG_INITIALIZATION_BUNDLE);
-            }
-
-            if (policy.mPasswordTokenHandle != 0) {
-                out.startTag(null, TAG_PASSWORD_TOKEN_HANDLE);
-                out.attribute(null, ATTR_VALUE,
-                        Long.toString(policy.mPasswordTokenHandle));
-                out.endTag(null, TAG_PASSWORD_TOKEN_HANDLE);
-            }
-
-            if (policy.mCurrentInputMethodSet) {
-                out.startTag(null, TAG_CURRENT_INPUT_METHOD_SET);
-                out.endTag(null, TAG_CURRENT_INPUT_METHOD_SET);
-            }
-
-            for (final String cert : policy.mOwnerInstalledCaCerts) {
-                out.startTag(null, TAG_OWNER_INSTALLED_CA_CERT);
-                out.attribute(null, ATTR_ALIAS, cert);
-                out.endTag(null, TAG_OWNER_INSTALLED_CA_CERT);
-            }
-
-            for (int i = 0, size = policy.mUserControlDisabledPackages.size(); i < size; i++) {
-                String packageName = policy.mUserControlDisabledPackages.get(i);
-                out.startTag(null, TAG_PROTECTED_PACKAGES);
-                out.attribute(null, ATTR_NAME, packageName);
-                out.endTag(null, TAG_PROTECTED_PACKAGES);
-            }
-
-            if (policy.mAppsSuspended) {
-                out.startTag(null, TAG_APPS_SUSPENDED);
-                out.attribute(null, ATTR_VALUE, Boolean.toString(policy.mAppsSuspended));
-                out.endTag(null, TAG_APPS_SUSPENDED);
-            }
-
-            out.endTag(null, "policies");
-
-            out.endDocument();
-            stream.flush();
-            FileUtils.sync(stream);
-            stream.close();
-            journal.commit();
+        if (DevicePolicyData.store(
+                getUserData(userHandle),
+                makeJournaledFile(userHandle),
+                !mInjector.storageManagerIsFileBasedEncryptionEnabled())) {
             sendChangedNotification(userHandle);
-        } catch (XmlPullParserException | IOException e) {
-            Slog.w(LOG_TAG, "failed writing file", e);
-            try {
-                if (stream != null) {
-                    stream.close();
-                }
-            } catch (IOException ex) {
-                // Ignore
-            }
-            journal.rollback();
         }
     }
 
@@ -3801,225 +2489,19 @@
     }
 
     private void loadSettingsLocked(DevicePolicyData policy, int userHandle) {
-        JournaledFile journal = makeJournaledFile(userHandle);
-        FileInputStream stream = null;
-        File file = journal.chooseForRead();
-        boolean needsRewrite = false;
-        try {
-            stream = new FileInputStream(file);
-            XmlPullParser parser = Xml.newPullParser();
-            parser.setInput(stream, StandardCharsets.UTF_8.name());
-
-            int type;
-            while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
-                    && type != XmlPullParser.START_TAG) {
-            }
-            String tag = parser.getName();
-            if (!"policies".equals(tag)) {
-                throw new XmlPullParserException(
-                        "Settings do not start with policies tag: found " + tag);
-            }
-
-            // Extract the permission provider component name if available
-            String permissionProvider = parser.getAttributeValue(null, ATTR_PERMISSION_PROVIDER);
-            if (permissionProvider != null) {
-                policy.mRestrictionsProvider = ComponentName.unflattenFromString(permissionProvider);
-            }
-            String userSetupComplete = parser.getAttributeValue(null, ATTR_SETUP_COMPLETE);
-            if (userSetupComplete != null && Boolean.toString(true).equals(userSetupComplete)) {
-                policy.mUserSetupComplete = true;
-            }
-            String paired = parser.getAttributeValue(null, ATTR_DEVICE_PAIRED);
-            if (paired != null && Boolean.toString(true).equals(paired)) {
-                policy.mPaired = true;
-            }
-            String deviceProvisioningConfigApplied = parser.getAttributeValue(null,
-                    ATTR_DEVICE_PROVISIONING_CONFIG_APPLIED);
-            if (deviceProvisioningConfigApplied != null
-                    && Boolean.toString(true).equals(deviceProvisioningConfigApplied)) {
-                policy.mDeviceProvisioningConfigApplied = true;
-            }
-            String provisioningState = parser.getAttributeValue(null, ATTR_PROVISIONING_STATE);
-            if (!TextUtils.isEmpty(provisioningState)) {
-                policy.mUserProvisioningState = Integer.parseInt(provisioningState);
-            }
-            String permissionPolicy = parser.getAttributeValue(null, ATTR_PERMISSION_POLICY);
-            if (!TextUtils.isEmpty(permissionPolicy)) {
-                policy.mPermissionPolicy = Integer.parseInt(permissionPolicy);
-            }
-            // Check for delegation compatibility with pre-O.
-            // TODO(edmanp) remove in P.
-            {
-                final String certDelegate = parser.getAttributeValue(null,
-                        ATTR_DELEGATED_CERT_INSTALLER);
-                if (certDelegate != null) {
-                    List<String> scopes = policy.mDelegationMap.get(certDelegate);
-                    if (scopes == null) {
-                        scopes = new ArrayList<>();
-                        policy.mDelegationMap.put(certDelegate, scopes);
-                    }
-                    if (!scopes.contains(DELEGATION_CERT_INSTALL)) {
-                        scopes.add(DELEGATION_CERT_INSTALL);
-                        needsRewrite = true;
-                    }
-                }
-                final String appRestrictionsDelegate = parser.getAttributeValue(null,
-                        ATTR_APPLICATION_RESTRICTIONS_MANAGER);
-                if (appRestrictionsDelegate != null) {
-                    List<String> scopes = policy.mDelegationMap.get(appRestrictionsDelegate);
-                    if (scopes == null) {
-                        scopes = new ArrayList<>();
-                        policy.mDelegationMap.put(appRestrictionsDelegate, scopes);
-                    }
-                    if (!scopes.contains(DELEGATION_APP_RESTRICTIONS)) {
-                        scopes.add(DELEGATION_APP_RESTRICTIONS);
-                        needsRewrite = true;
-                    }
-                }
-            }
-
-            type = parser.next();
-            int outerDepth = parser.getDepth();
-            policy.mLockTaskPackages.clear();
-            policy.mAdminList.clear();
-            policy.mAdminMap.clear();
-            policy.mAffiliationIds.clear();
-            policy.mOwnerInstalledCaCerts.clear();
-            policy.mUserControlDisabledPackages.clear();
-            while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
-                   && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-                if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                    continue;
-                }
-                tag = parser.getName();
-                if ("admin".equals(tag)) {
-                    String name = parser.getAttributeValue(null, "name");
-                    try {
-                        DeviceAdminInfo dai = findAdmin(
-                                ComponentName.unflattenFromString(name), userHandle,
-                                /* throwForMissingPermission= */ false);
-                        if (VERBOSE_LOG
-                                && (UserHandle.getUserId(dai.getActivityInfo().applicationInfo.uid)
-                                != userHandle)) {
-                            Slog.w(LOG_TAG, "findAdmin returned an incorrect uid "
-                                    + dai.getActivityInfo().applicationInfo.uid + " for user "
-                                    + userHandle);
-                        }
-                        if (dai != null) {
-                            boolean shouldOverwritePolicies =
-                                    shouldOverwritePoliciesFromXml(dai.getComponent(), userHandle);
-                            ActiveAdmin ap = new ActiveAdmin(dai, /* parent */ false);
-                            ap.readFromXml(parser, shouldOverwritePolicies);
-                            policy.mAdminMap.put(ap.info.getComponent(), ap);
-                        }
-                    } catch (RuntimeException e) {
-                        Slog.w(LOG_TAG, "Failed loading admin " + name, e);
-                    }
-                } else if ("delegation".equals(tag)) {
-                    // Parse delegation info.
-                    final String delegatePackage = parser.getAttributeValue(null,
-                            "delegatePackage");
-                    final String scope = parser.getAttributeValue(null, "scope");
-
-                    // Get a reference to the scopes list for the delegatePackage.
-                    List<String> scopes = policy.mDelegationMap.get(delegatePackage);
-                    // Or make a new list if none was found.
-                    if (scopes == null) {
-                        scopes = new ArrayList<>();
-                        policy.mDelegationMap.put(delegatePackage, scopes);
-                    }
-                    // Add the new scope to the list of delegatePackage if it's not already there.
-                    if (!scopes.contains(scope)) {
-                        scopes.add(scope);
-                    }
-                } else if ("failed-password-attempts".equals(tag)) {
-                    policy.mFailedPasswordAttempts = Integer.parseInt(
-                            parser.getAttributeValue(null, "value"));
-                } else if ("password-owner".equals(tag)) {
-                    policy.mPasswordOwner = Integer.parseInt(
-                            parser.getAttributeValue(null, "value"));
-                } else if (TAG_ACCEPTED_CA_CERTIFICATES.equals(tag)) {
-                    policy.mAcceptedCaCertificates.add(parser.getAttributeValue(null, ATTR_NAME));
-                } else if (TAG_LOCK_TASK_COMPONENTS.equals(tag)) {
-                    policy.mLockTaskPackages.add(parser.getAttributeValue(null, "name"));
-                } else if (TAG_LOCK_TASK_FEATURES.equals(tag)) {
-                    policy.mLockTaskFeatures = Integer.parseInt(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_SECONDARY_LOCK_SCREEN.equals(tag)) {
-                    policy.mSecondaryLockscreenEnabled = Boolean.parseBoolean(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_STATUS_BAR.equals(tag)) {
-                    policy.mStatusBarDisabled = Boolean.parseBoolean(
-                            parser.getAttributeValue(null, ATTR_DISABLED));
-                } else if (DO_NOT_ASK_CREDENTIALS_ON_BOOT_XML.equals(tag)) {
-                    policy.doNotAskCredentialsOnBoot = true;
-                } else if (TAG_AFFILIATION_ID.equals(tag)) {
-                    policy.mAffiliationIds.add(parser.getAttributeValue(null, ATTR_ID));
-                } else if (TAG_LAST_SECURITY_LOG_RETRIEVAL.equals(tag)) {
-                    policy.mLastSecurityLogRetrievalTime = Long.parseLong(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_LAST_BUG_REPORT_REQUEST.equals(tag)) {
-                    policy.mLastBugReportRequestTime = Long.parseLong(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_LAST_NETWORK_LOG_RETRIEVAL.equals(tag)) {
-                    policy.mLastNetworkLogsRetrievalTime = Long.parseLong(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_ADMIN_BROADCAST_PENDING.equals(tag)) {
-                    String pending = parser.getAttributeValue(null, ATTR_VALUE);
-                    policy.mAdminBroadcastPending = Boolean.toString(true).equals(pending);
-                } else if (TAG_INITIALIZATION_BUNDLE.equals(tag)) {
-                    policy.mInitBundle = PersistableBundle.restoreFromXml(parser);
-                } else if ("active-password".equals(tag)) {
-                    // Remove password metrics from saved settings, as we no longer wish to store
-                    // these on disk
-                    needsRewrite = true;
-                } else if (TAG_PASSWORD_VALIDITY.equals(tag)) {
-                    if (!mInjector.storageManagerIsFileBasedEncryptionEnabled()) {
-                        // This flag is only used for FDE devices
-                        policy.mPasswordValidAtLastCheckpoint = Boolean.parseBoolean(
-                                parser.getAttributeValue(null, ATTR_VALUE));
-                    }
-                } else if (TAG_PASSWORD_TOKEN_HANDLE.equals(tag)) {
-                    policy.mPasswordTokenHandle = Long.parseLong(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_CURRENT_INPUT_METHOD_SET.equals(tag)) {
-                    policy.mCurrentInputMethodSet = true;
-                } else if (TAG_OWNER_INSTALLED_CA_CERT.equals(tag)) {
-                    policy.mOwnerInstalledCaCerts.add(parser.getAttributeValue(null, ATTR_ALIAS));
-                } else if (TAG_PROTECTED_PACKAGES.equals(tag)) {
-                    policy.mUserControlDisabledPackages.add(
-                            parser.getAttributeValue(null, ATTR_NAME));
-                } else if (TAG_APPS_SUSPENDED.equals(tag)) {
-                    policy.mAppsSuspended =
-                            Boolean.parseBoolean(parser.getAttributeValue(null, ATTR_VALUE));
-                } else {
-                    Slog.w(LOG_TAG, "Unknown tag: " + tag);
-                    XmlUtils.skipCurrentTag(parser);
-                }
-            }
-        } catch (FileNotFoundException e) {
-            // Don't be noisy, this is normal if we haven't defined any policies.
-        } catch (NullPointerException | NumberFormatException | XmlPullParserException | IOException
-                | IndexOutOfBoundsException e) {
-            Slog.w(LOG_TAG, "failed parsing " + file, e);
-        }
-        try {
-            if (stream != null) {
-                stream.close();
-            }
-        } catch (IOException e) {
-            // Ignore
-        }
-
-        // Generate a list of admins from the admin map
-        policy.mAdminList.addAll(policy.mAdminMap.values());
+        boolean needsRewrite = DevicePolicyData.load(policy,
+                !mInjector.storageManagerIsFileBasedEncryptionEnabled(),
+                makeJournaledFile(userHandle),
+                component -> findAdmin(
+                        component, userHandle, /* throwForMissingPermission= */ false),
+                getOwnerComponent(userHandle));
 
         // Might need to upgrade the file by rewriting it
         if (needsRewrite) {
             saveSettingsLocked(userHandle);
         }
 
-        validatePasswordOwnerLocked(policy);
+        policy.validatePasswordOwner();
         updateMaximumTimeToLockLocked(userHandle);
         updateLockTaskPackagesLocked(policy.mLockTaskPackages, userHandle);
         updateLockTaskFeaturesLocked(policy.mLockTaskFeatures, userHandle);
@@ -4029,14 +2511,6 @@
         }
     }
 
-    private boolean shouldOverwritePoliciesFromXml(
-            ComponentName deviceAdminComponent, int userHandle) {
-        // http://b/123415062: If DA, overwrite with the stored policies that were agreed by the
-        // user to prevent apps from sneaking additional policies into updates.
-        return !isProfileOwner(deviceAdminComponent, userHandle)
-                && !isDeviceOwner(deviceAdminComponent, userHandle);
-    }
-
     private void updateLockTaskPackagesLocked(List<String> packages, int userId) {
         long ident = mInjector.binderClearCallingIdentity();
         try {
@@ -4098,23 +2572,6 @@
                 + Integer.toHexString(quality));
     }
 
-    void validatePasswordOwnerLocked(DevicePolicyData policy) {
-        if (policy.mPasswordOwner >= 0) {
-            boolean haveOwner = false;
-            for (int i = policy.mAdminList.size() - 1; i >= 0; i--) {
-                if (policy.mAdminList.get(i).getUid() == policy.mPasswordOwner) {
-                    haveOwner = true;
-                    break;
-                }
-            }
-            if (!haveOwner) {
-                Slog.w(LOG_TAG, "Previous password owner " + policy.mPasswordOwner
-                        + " no longer active; disabling");
-                policy.mPasswordOwner = -1;
-            }
-        }
-    }
-
     @VisibleForTesting
     @Override
     void systemReady(int phase) {
@@ -5842,8 +4299,8 @@
     private void setDoNotAskCredentialsOnBoot() {
         synchronized (getLockObject()) {
             DevicePolicyData policyData = getUserData(UserHandle.USER_SYSTEM);
-            if (!policyData.doNotAskCredentialsOnBoot) {
-                policyData.doNotAskCredentialsOnBoot = true;
+            if (!policyData.mDoNotAskCredentialsOnBoot) {
+                policyData.mDoNotAskCredentialsOnBoot = true;
                 saveSettingsLocked(UserHandle.USER_SYSTEM);
             }
         }
@@ -5855,7 +4312,7 @@
                 android.Manifest.permission.QUERY_DO_NOT_ASK_CREDENTIALS_ON_BOOT, null);
         synchronized (getLockObject()) {
             DevicePolicyData policyData = getUserData(UserHandle.USER_SYSTEM);
-            return policyData.doNotAskCredentialsOnBoot;
+            return policyData.mDoNotAskCredentialsOnBoot;
         }
     }
 
@@ -6133,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);
@@ -6738,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).
@@ -7743,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));
     }
@@ -8164,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(
@@ -8171,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;
             }
@@ -8191,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()) {
@@ -8220,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)) {
@@ -8748,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()
@@ -10025,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,
@@ -10033,7 +8495,7 @@
             mInjector.binderWithCleanCallingIdentity(() -> enforcePackageIsSystemPackage(
                     packageName, getProfileParentId(mInjector.userHandleGetCallingUserId())));
         } else {
-            enforceDeviceOwner(admin);
+            Preconditions.checkCallAuthorization(isDeviceOwner(identity));
         }
 
         mInjector.binderWithCleanCallingIdentity(() ->
@@ -10795,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;
@@ -10828,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;
@@ -10852,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)) {
@@ -10884,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)) {
@@ -10952,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
@@ -11914,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)
@@ -11922,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)) {
@@ -12004,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());
             }
         });
 
@@ -13544,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) {
@@ -14363,7 +12830,7 @@
                     DeviceAdminInfo.USES_POLICY_SETS_GLOBAL_PROXY);
             policy.mAdminList.remove(admin);
             policy.mAdminMap.remove(adminReceiver);
-            validatePasswordOwnerLocked(policy);
+            policy.validatePasswordOwner();
             if (doProxyCleanup) {
                 resetGlobalProxyLocked(policy);
             }
@@ -15059,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());
         }
     }
 
@@ -15236,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()
@@ -15261,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()
@@ -15286,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;
         }
     }
@@ -15300,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;
         }
     }
@@ -15343,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) {
@@ -15363,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;
@@ -15385,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);
     }
 
@@ -15406,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();
     }
 
@@ -15427,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);
     }
 
@@ -15445,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(
@@ -15528,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:
@@ -15566,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;
@@ -15590,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);
     }
 
@@ -15938,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)
@@ -15964,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/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index a5f0d04..f7082a9 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -23,7 +23,6 @@
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <binder/AppOpsManager.h>
-#include <binder/Nullable.h>
 #include <binder/Status.h>
 #include <sys/stat.h>
 #include <uuid/uuid.h>
@@ -1404,7 +1403,7 @@
     }
 
     FileSystemControlParcel fsControlParcel;
-    fsControlParcel.incremental = aidl::make_nullable<IncrementalFileSystemControlParcel>();
+    fsControlParcel.incremental = std::make_optional<IncrementalFileSystemControlParcel>();
     fsControlParcel.incremental->cmd.reset(dup(ifs.control.cmd()));
     fsControlParcel.incremental->pendingReads.reset(dup(ifs.control.pendingReads()));
     fsControlParcel.incremental->log.reset(dup(ifs.control.logs()));
diff --git a/services/systemcaptions/java/com/android/server/systemcaptions/SystemCaptionsManagerService.java b/services/systemcaptions/java/com/android/server/systemcaptions/SystemCaptionsManagerService.java
index 27a116c..1586a33 100644
--- a/services/systemcaptions/java/com/android/server/systemcaptions/SystemCaptionsManagerService.java
+++ b/services/systemcaptions/java/com/android/server/systemcaptions/SystemCaptionsManagerService.java
@@ -34,7 +34,8 @@
                         context,
                         com.android.internal.R.string.config_defaultSystemCaptionsManagerService),
                 /*disallowProperty=*/ null,
-                /*packageUpdatePolicy=*/ PACKAGE_UPDATE_POLICY_REFRESH_EAGER);
+                /*packageUpdatePolicy=*/ PACKAGE_UPDATE_POLICY_REFRESH_EAGER
+                    | PACKAGE_RESTART_POLICY_REFRESH_EAGER);
     }
 
     @Override
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/LocationProviderManagerTest.java
index 1cb004a..fdcadf3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/LocationProviderManagerTest.java
@@ -25,6 +25,7 @@
 import static android.location.Criteria.ACCURACY_FINE;
 import static android.location.Criteria.POWER_HIGH;
 import static android.location.LocationManager.PASSIVE_PROVIDER;
+import static android.os.PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF;
 
 import static androidx.test.ext.truth.location.LocationSubject.assertThat;
 
@@ -907,6 +908,21 @@
         assertThat(mProvider.getRequest().interval).isEqualTo(5);
     }
 
+    @Test
+    public void testProviderRequest_BatterySaver_ScreenOnOff() {
+        mInjector.getLocationPowerSaveModeHelper().setLocationPowerSaveMode(
+                LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF);
+
+        ILocationListener listener = createMockLocationListener();
+        LocationRequest request = LocationRequest.createFromDeprecatedProvider(NAME, 5, 0, false);
+        mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
+
+        assertThat(mProvider.getRequest().reportLocation).isTrue();
+
+        mInjector.getScreenInteractiveHelper().setScreenInteractive(false);
+        assertThat(mProvider.getRequest().reportLocation).isFalse();
+    }
+
     private ILocationListener createMockLocationListener() {
         return spy(new ILocationListener.Stub() {
             @Override
diff --git a/services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/restrict-background-lists-whitelist-format.xml b/services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/restrict-background-lists-allowlist-format.xml
similarity index 100%
rename from services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/restrict-background-lists-whitelist-format.xml
rename to services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/restrict-background-lists-allowlist-format.xml
diff --git a/services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/uidA-whitelisted-restrict-background-off.xml b/services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/uidA-allowlisted-restrict-background-off.xml
similarity index 100%
rename from services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/uidA-whitelisted-restrict-background-off.xml
rename to services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/uidA-allowlisted-restrict-background-off.xml
diff --git a/services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/uidA-whitelisted-restrict-background-on.xml b/services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/uidA-allowlisted-restrict-background-on.xml
similarity index 100%
rename from services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/uidA-whitelisted-restrict-background-on.xml
rename to services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/uidA-allowlisted-restrict-background-on.xml
diff --git a/services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/uidA-blacklisted-restrict-background-off.xml b/services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/uidA-denylisted-restrict-background-off.xml
similarity index 100%
rename from services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/uidA-blacklisted-restrict-background-off.xml
rename to services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/uidA-denylisted-restrict-background-off.xml
diff --git a/services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/uidA-blacklisted-restrict-background-on.xml b/services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/uidA-denylisted-restrict-background-on.xml
similarity index 100%
rename from services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/uidA-blacklisted-restrict-background-on.xml
rename to services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/uidA-denylisted-restrict-background-on.xml
diff --git a/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java b/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java
index 7d6d90c..b7a36f2 100644
--- a/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java
@@ -21,7 +21,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.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
@@ -224,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
@@ -310,7 +324,7 @@
         verify(mNativeWrapperMock).vibratorPerformEffect(
                 eq((long) VibrationEffect.EFFECT_CLICK),
                 eq((long) VibrationEffect.EFFECT_STRENGTH_STRONG),
-                any(VibratorService.Vibration.class), eq(false));
+                any(VibratorService.Vibration.class));
     }
 
     @Test
@@ -387,21 +401,26 @@
     }
 
     @Test
-    public void vibrate_withOneShotAndNativeCallbackNotTriggered_finishesVibrationViaFallback() {
+    public void vibrate_withPrebakedAndNativeCallbackTriggered_finishesVibration() {
+        when(mNativeWrapperMock.vibratorGetSupportedEffects())
+                .thenReturn(new int[]{VibrationEffect.EFFECT_CLICK});
+        doAnswer(invocation -> {
+            ((VibratorService.Vibration) invocation.getArgument(2)).onComplete();
+            return 10_000L; // 10s
+        }).when(mNativeWrapperMock).vibratorPerformEffect(
+                anyLong(), anyLong(), any(VibratorService.Vibration.class));
         VibratorService service = createService();
         Mockito.clearInvocations(mNativeWrapperMock);
 
-        vibrate(service, VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE));
+        vibrate(service, VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK));
 
-        verify(mNativeWrapperMock).vibratorOff();
-        verify(mNativeWrapperMock).vibratorOn(eq(100L), any(VibratorService.Vibration.class));
-        Mockito.clearInvocations(mNativeWrapperMock);
-
-        // Run the scheduled callback to finish one-shot vibration.
-        mTestLooper.moveTimeForward(200);
-        mTestLooper.dispatchAll();
-
-        verify(mNativeWrapperMock).vibratorOff();
+        InOrder inOrderVerifier = inOrder(mNativeWrapperMock);
+        inOrderVerifier.verify(mNativeWrapperMock).vibratorOff();
+        inOrderVerifier.verify(mNativeWrapperMock).vibratorPerformEffect(
+                eq((long) VibrationEffect.EFFECT_CLICK),
+                eq((long) VibrationEffect.EFFECT_STRENGTH_STRONG),
+                any(VibratorService.Vibration.class));
+        inOrderVerifier.verify(mNativeWrapperMock).vibratorOff();
     }
 
     @Test
@@ -447,30 +466,6 @@
     }
 
     @Test
-    public void vibrate_withComposedAndNativeCallbackNotTriggered_finishesVibrationViaFallback() {
-        mockVibratorCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
-        VibratorService service = createService();
-        Mockito.clearInvocations(mNativeWrapperMock);
-
-        VibrationEffect effect = VibrationEffect.startComposition()
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 10)
-                .compose();
-        vibrate(service, effect);
-
-        verify(mNativeWrapperMock).vibratorOff();
-        verify(mNativeWrapperMock).vibratorPerformComposedEffect(
-                any(VibrationEffect.Composition.PrimitiveEffect[].class),
-                any(VibratorService.Vibration.class));
-        Mockito.clearInvocations(mNativeWrapperMock);
-
-        // Run the scheduled callback to finish one-shot vibration.
-        mTestLooper.moveTimeForward(10000); // 10s
-        mTestLooper.dispatchAll();
-
-        verify(mNativeWrapperMock).vibratorOff();
-    }
-
-    @Test
     public void vibrate_whenBinderDies_cancelsVibration() {
         mockVibratorCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
         doAnswer(invocation -> {
@@ -574,15 +569,15 @@
 
         verify(mNativeWrapperMock).vibratorPerformEffect(
                 eq((long) VibrationEffect.EFFECT_CLICK),
-                eq((long) VibrationEffect.EFFECT_STRENGTH_STRONG), any(), anyBoolean());
+                eq((long) VibrationEffect.EFFECT_STRENGTH_STRONG), any());
         verify(mNativeWrapperMock).vibratorPerformEffect(
                 eq((long) VibrationEffect.EFFECT_TICK),
-                eq((long) VibrationEffect.EFFECT_STRENGTH_MEDIUM), any(), anyBoolean());
+                eq((long) VibrationEffect.EFFECT_STRENGTH_MEDIUM), any());
         verify(mNativeWrapperMock).vibratorPerformEffect(
                 eq((long) VibrationEffect.EFFECT_DOUBLE_CLICK),
-                eq((long) VibrationEffect.EFFECT_STRENGTH_LIGHT), any(), anyBoolean());
+                eq((long) VibrationEffect.EFFECT_STRENGTH_LIGHT), any());
         verify(mNativeWrapperMock, never()).vibratorPerformEffect(
-                eq((long) VibrationEffect.EFFECT_HEAVY_CLICK), anyLong(), any(), anyBoolean());
+                eq((long) VibrationEffect.EFFECT_HEAVY_CLICK), anyLong(), any());
     }
 
     @Test
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/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 9a465a91..7f6723e 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}.
@@ -1604,7 +1605,7 @@
         dpm.setApplicationRestrictionsManagingPackage(admin1, RESTRICTIONS_DELEGATE);
 
         // DPMS correctly stores and retrieves the delegates
-        DevicePolicyManagerService.DevicePolicyData policy = dpms.mUserData.get(userHandle);
+        DevicePolicyData policy = dpms.mUserData.get(userHandle);
         assertEquals(2, policy.mDelegationMap.size());
         MoreAsserts.assertContentsInAnyOrder(policy.mDelegationMap.get(CERT_DELEGATE),
             DELEGATION_CERT_INSTALL);
@@ -1846,11 +1847,11 @@
         reset(getServices().userManagerInternal);
     }
 
-    private DevicePolicyManagerService.ActiveAdmin getDeviceOwner() {
+    private ActiveAdmin getDeviceOwner() {
         ComponentName component = dpms.mOwners.getDeviceOwnerComponent();
-        DevicePolicyManagerService.DevicePolicyData policy =
+        DevicePolicyData policy =
                 dpms.getUserData(dpms.mOwners.getDeviceOwnerUserId());
-        for (DevicePolicyManagerService.ActiveAdmin admin : policy.mAdminList) {
+        for (ActiveAdmin admin : policy.mAdminList) {
             if (component.equals(admin.info.getComponent())) {
                 return admin;
             }
@@ -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);
@@ -3745,8 +3746,7 @@
         setUserSetupCompleteForUser(false, userId);
 
         // GIVEN userComplete is true in DPM
-        DevicePolicyManagerService.DevicePolicyData userData =
-                new DevicePolicyManagerService.DevicePolicyData(userId);
+        DevicePolicyData userData = new DevicePolicyData(userId);
         userData.mUserSetupComplete = true;
         dpms.mUserData.put(UserHandle.USER_SYSTEM, userData);
 
@@ -3770,8 +3770,7 @@
         setUserSetupCompleteForUser(false, userId);
 
         // GIVEN userComplete is true in DPM
-        DevicePolicyManagerService.DevicePolicyData userData =
-                new DevicePolicyManagerService.DevicePolicyData(userId);
+        DevicePolicyData userData = new DevicePolicyData(userId);
         userData.mUserSetupComplete = true;
         dpms.mUserData.put(UserHandle.USER_SYSTEM, userData);
 
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/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
index 12b144f..1f66c7c 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
@@ -129,7 +129,7 @@
         mGateKeeperService.clearAuthToken(TURNED_OFF_PROFILE_USER_ID);
         // verify credential
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                firstUnifiedPassword, 0, PRIMARY_USER_ID)
+                firstUnifiedPassword, PRIMARY_USER_ID, 0 /* flags */)
                 .getResponseCode());
 
         // Verify that we have a new auth token for the profile
@@ -186,13 +186,13 @@
         mGateKeeperService.clearAuthToken(MANAGED_PROFILE_USER_ID);
         // verify primary credential
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                primaryPassword, 0, PRIMARY_USER_ID)
+                primaryPassword, PRIMARY_USER_ID, 0 /* flags */)
                 .getResponseCode());
         assertNull(mGateKeeperService.getAuthToken(MANAGED_PROFILE_USER_ID));
 
         // verify profile credential
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                profilePassword, 0, MANAGED_PROFILE_USER_ID)
+                profilePassword, MANAGED_PROFILE_USER_ID, 0 /* flags */)
                 .getResponseCode());
         assertNotNull(mGateKeeperService.getAuthToken(MANAGED_PROFILE_USER_ID));
         assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
@@ -203,7 +203,7 @@
                 newPassword("pwd"), primaryPassword, PRIMARY_USER_ID));
         mStorageManager.setIgnoreBadUnlock(false);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                profilePassword, 0, MANAGED_PROFILE_USER_ID)
+                profilePassword, MANAGED_PROFILE_USER_ID, 0 /* flags */)
                 .getResponseCode());
         assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
     }
@@ -389,7 +389,7 @@
         initializeStorageWithCredential(PRIMARY_USER_ID, password, 1234);
         reset(mRecoverableKeyStoreManager);
 
-        mService.verifyCredential(password, 1, PRIMARY_USER_ID);
+        mService.verifyCredential(password, PRIMARY_USER_ID, 0 /* flags */);
 
         verify(mRecoverableKeyStoreManager)
                 .lockScreenSecretAvailable(
@@ -406,7 +406,7 @@
                 MANAGED_PROFILE_USER_ID));
         reset(mRecoverableKeyStoreManager);
 
-        mService.verifyCredential(pattern, 1, MANAGED_PROFILE_USER_ID);
+        mService.verifyCredential(pattern, MANAGED_PROFILE_USER_ID, 0 /* flags */);
 
         verify(mRecoverableKeyStoreManager)
                 .lockScreenSecretAvailable(
@@ -421,7 +421,7 @@
         mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
         reset(mRecoverableKeyStoreManager);
 
-        mService.verifyCredential(pattern, 1, PRIMARY_USER_ID);
+        mService.verifyCredential(pattern, PRIMARY_USER_ID, 0 /* flags */);
 
         // Parent sends its credentials for both the parent and profile.
         verify(mRecoverableKeyStoreManager)
@@ -484,9 +484,8 @@
 
     private void assertVerifyCredentials(int userId, LockscreenCredential credential, long sid)
             throws RemoteException{
-        final long challenge = 54321;
-        VerifyCredentialResponse response = mService.verifyCredential(credential,
-                challenge, userId);
+        VerifyCredentialResponse response = mService.verifyCredential(credential, userId,
+                0 /* flags */);
 
         assertEquals(GateKeeperResponse.RESPONSE_OK, response.getResponseCode());
         if (sid != -1) assertEquals(sid, mGateKeeperService.getSecureUserId(userId));
@@ -508,7 +507,7 @@
             badCredential = LockscreenCredential.createPin("0");
         }
         assertEquals(GateKeeperResponse.RESPONSE_ERROR, mService.verifyCredential(
-                badCredential, challenge, userId).getResponseCode());
+                badCredential, userId, 0 /* flags */).getResponseCode());
     }
 
     private void initializeStorageWithCredential(int userId, LockscreenCredential credential,
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java
index 4b3f7b5..9c0239b 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java
@@ -52,7 +52,8 @@
 
         assertEquals(CREDENTIAL_TYPE_PIN, mService.getCredentialType(USER_FRP));
         assertEquals(VerifyCredentialResponse.RESPONSE_OK,
-                mService.verifyCredential(newPin("1234"), 0, USER_FRP).getResponseCode());
+                mService.verifyCredential(newPin("1234"), USER_FRP, 0 /* flags */)
+                        .getResponseCode());
     }
 
     @Test
@@ -61,7 +62,8 @@
 
         assertEquals(CREDENTIAL_TYPE_PATTERN, mService.getCredentialType(USER_FRP));
         assertEquals(VerifyCredentialResponse.RESPONSE_OK,
-                mService.verifyCredential(newPattern("4321"), 0, USER_FRP).getResponseCode());
+                mService.verifyCredential(newPattern("4321"), USER_FRP, 0 /* flags */)
+                        .getResponseCode());
     }
 
     @Test
@@ -70,7 +72,8 @@
 
         assertEquals(CREDENTIAL_TYPE_PASSWORD, mService.getCredentialType(USER_FRP));
         assertEquals(VerifyCredentialResponse.RESPONSE_OK,
-                mService.verifyCredential(newPassword("4321"), 0, USER_FRP).getResponseCode());
+                mService.verifyCredential(newPassword("4321"), USER_FRP, 0 /* flags */)
+                        .getResponseCode());
     }
 
     @Test
@@ -80,7 +83,8 @@
 
         assertEquals(CREDENTIAL_TYPE_PATTERN, mService.getCredentialType(USER_FRP));
         assertEquals(VerifyCredentialResponse.RESPONSE_OK,
-                mService.verifyCredential(newPattern("5678"), 0, USER_FRP).getResponseCode());
+                mService.verifyCredential(newPattern("5678"), USER_FRP, 0 /* flags */)
+                        .getResponseCode());
     }
 
     @Test
@@ -98,7 +102,8 @@
 
         mSettings.setDeviceProvisioned(true);
         assertEquals(VerifyCredentialResponse.RESPONSE_ERROR,
-                mService.verifyCredential(newPin("1234"), 0, USER_FRP).getResponseCode());
+                mService.verifyCredential(newPin("1234"), USER_FRP, 0 /* flags */)
+                        .getResponseCode());
     }
 
     @Test
@@ -113,7 +118,8 @@
 
         assertEquals(CREDENTIAL_TYPE_PIN, mService.getCredentialType(USER_FRP));
         assertEquals(VerifyCredentialResponse.RESPONSE_OK,
-                mService.verifyCredential(newPin("1234"), 0, USER_FRP).getResponseCode());
+                mService.verifyCredential(newPin("1234"), USER_FRP, 0 /* flags */)
+                        .getResponseCode());
 
     }
 
@@ -129,6 +135,7 @@
 
         assertEquals(CREDENTIAL_TYPE_PASSWORD, mService.getCredentialType(USER_FRP));
         assertEquals(VerifyCredentialResponse.RESPONSE_OK,
-                mService.verifyCredential(newPin("1234"), 0, USER_FRP).getResponseCode());
+                mService.verifyCredential(newPin("1234"), USER_FRP, 0 /* flags */)
+                        .getResponseCode());
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index ba85199..2bd0e55 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -119,8 +119,7 @@
         long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
         mService.setLockCredential(newPassword, password, PRIMARY_USER_ID);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                newPassword, 0, PRIMARY_USER_ID)
-                        .getResponseCode());
+                newPassword, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
         assertEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
     }
 
@@ -131,12 +130,10 @@
 
         initializeCredentialUnderSP(password, PRIMARY_USER_ID);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                password, 0, PRIMARY_USER_ID)
-                        .getResponseCode());
+                password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
 
         assertEquals(VerifyCredentialResponse.RESPONSE_ERROR, mService.verifyCredential(
-                badPassword, 0, PRIMARY_USER_ID)
-                        .getResponseCode());
+                badPassword, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
     }
 
     @Test
@@ -153,8 +150,7 @@
         // set a new password
         mService.setLockCredential(badPassword, nonePassword(), PRIMARY_USER_ID);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                badPassword, 0, PRIMARY_USER_ID)
-                        .getResponseCode());
+                badPassword, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
         assertNotEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
     }
 
@@ -166,8 +162,7 @@
         initializeCredentialUnderSP(password, PRIMARY_USER_ID);
         mService.setLockCredential(badPassword, password, PRIMARY_USER_ID);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                badPassword, 0, PRIMARY_USER_ID)
-                        .getResponseCode());
+                badPassword, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
 
         // Check the same secret was passed each time
         ArgumentCaptor<ArrayList<Byte>> secret = ArgumentCaptor.forClass(ArrayList.class);
@@ -183,8 +178,7 @@
         initializeCredentialUnderSP(password, PRIMARY_USER_ID);
         reset(mAuthSecretService);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                password, 0, PRIMARY_USER_ID)
-                        .getResponseCode());
+                password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
         verify(mAuthSecretService).primaryUserCredential(any(ArrayList.class));
     }
 
@@ -194,8 +188,7 @@
 
         initializeCredentialUnderSP(password, SECONDARY_USER_ID);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                password, 0, SECONDARY_USER_ID)
-                        .getResponseCode());
+                password, SECONDARY_USER_ID, 0 /* flags */).getResponseCode());
         verify(mAuthSecretService, never()).primaryUserCredential(any(ArrayList.class));
     }
 
@@ -246,7 +239,8 @@
         assertFalse(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
         assertTrue(mService.hasPendingEscrowToken(PRIMARY_USER_ID));
 
-        mService.verifyCredential(password, 0, PRIMARY_USER_ID).getResponseCode();
+        mService.verifyCredential(password, PRIMARY_USER_ID, 0 /* flags */)
+                .getResponseCode();
         assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
         assertFalse(mService.hasPendingEscrowToken(PRIMARY_USER_ID));
 
@@ -259,8 +253,7 @@
         verify(mDevicePolicyManager).reportPasswordChanged(PRIMARY_USER_ID);
 
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                pattern, 0, PRIMARY_USER_ID)
-                    .getResponseCode());
+                pattern, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
         assertArrayEquals(storageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
     }
 
@@ -275,7 +268,8 @@
         long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
         assertFalse(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
 
-        mService.verifyCredential(password, 0, PRIMARY_USER_ID).getResponseCode();
+        mService.verifyCredential(password, PRIMARY_USER_ID, 0 /* flags */)
+                .getResponseCode();
         assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
 
         mLocalService.setLockCredentialWithToken(nonePassword(), handle, token, PRIMARY_USER_ID);
@@ -284,8 +278,7 @@
                 PRIMARY_USER_ID);
 
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                pattern, 0, PRIMARY_USER_ID)
-                        .getResponseCode());
+                pattern, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
         assertArrayEquals(storageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
     }
 
@@ -301,7 +294,8 @@
         long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
         assertFalse(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
 
-        mService.verifyCredential(password, 0, PRIMARY_USER_ID).getResponseCode();
+        mService.verifyCredential(password, PRIMARY_USER_ID, 0 /* flags */)
+                .getResponseCode();
         assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
 
         mService.setLockCredential(pattern, password, PRIMARY_USER_ID);
@@ -309,8 +303,7 @@
         mLocalService.setLockCredentialWithToken(newPassword, handle, token, PRIMARY_USER_ID);
 
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                newPassword, 0, PRIMARY_USER_ID)
-                    .getResponseCode());
+                newPassword, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
         assertArrayEquals(storageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
     }
 
@@ -357,8 +350,7 @@
         assertFalse(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
         // Activate token (password gets migrated to SP at the same time)
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                password, 0, PRIMARY_USER_ID)
-                    .getResponseCode());
+                password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
         // Verify token is activated
         assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
     }
@@ -488,8 +480,7 @@
 
         initializeCredentialUnderSP(password, PRIMARY_USER_ID);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                password, 0, PRIMARY_USER_ID)
-                        .getResponseCode());
+                password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
         verify(mAuthSecretService, never()).primaryUserCredential(any(ArrayList.class));
     }
 
@@ -503,7 +494,8 @@
         reset(mDevicePolicyManager);
 
         long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
-        mService.verifyCredential(password, 0, PRIMARY_USER_ID).getResponseCode();
+        mService.verifyCredential(password, PRIMARY_USER_ID, 0 /* flags */)
+                .getResponseCode();
         assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
 
         mService.onCleanupUser(PRIMARY_USER_ID);
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index 128177b..58b71d4 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -462,7 +462,7 @@
 
     @Test
     public void testTurnRestrictBackgroundOn() throws Exception {
-        assertRestrictBackgroundOff(); // Sanity check.
+        assertRestrictBackgroundOff();
         final FutureIntent futureIntent = newRestrictBackgroundChangedFuture();
         setRestrictBackground(true);
         assertRestrictBackgroundChangedReceived(futureIntent, null);
@@ -471,7 +471,7 @@
     @Test
     @NetPolicyXml("restrict-background-on.xml")
     public void testTurnRestrictBackgroundOff() throws Exception {
-        assertRestrictBackgroundOn(); // Sanity check.
+        assertRestrictBackgroundOn();
         assertRestrictBackgroundChangedReceived(mFutureIntent, null);
         final FutureIntent futureIntent = newRestrictBackgroundChangedFuture();
         setRestrictBackground(false);
@@ -479,28 +479,27 @@
     }
 
     /**
-     * Adds whitelist when restrict background is on - app should receive an intent.
+     * Adds allowlist when restrict background is on - app should receive an intent.
      */
     @Test
     @NetPolicyXml("restrict-background-on.xml")
-    public void testAddRestrictBackgroundWhitelist_restrictBackgroundOn() throws Exception {
-        assertRestrictBackgroundOn(); // Sanity check.
+    public void testAddRestrictBackgroundAllowlist_restrictBackgroundOn() throws Exception {
+        assertRestrictBackgroundOn();
         assertRestrictBackgroundChangedReceived(mFutureIntent, null);
-        addRestrictBackgroundWhitelist(true);
+        addRestrictBackgroundAllowlist(true);
     }
 
     /**
-     * Adds whitelist when restrict background is off - app should not receive an intent.
+     * Adds allowlist when restrict background is off - app should not receive an intent.
      */
     @Test
-    public void testAddRestrictBackgroundWhitelist_restrictBackgroundOff() throws Exception {
-        assertRestrictBackgroundOff(); // Sanity check.
-        addRestrictBackgroundWhitelist(false);
+    public void testAddRestrictBackgroundAllowlist_restrictBackgroundOff() throws Exception {
+        assertRestrictBackgroundOff();
+        addRestrictBackgroundAllowlist(false);
     }
 
-    private void addRestrictBackgroundWhitelist(boolean expectIntent) throws Exception {
-        // Sanity checks.
-        assertWhitelistUids();
+    private void addRestrictBackgroundAllowlist(boolean expectIntent) throws Exception {
+        assertAllowlistUids();
         assertUidPolicy(UID_A, POLICY_NONE);
 
         final FutureIntent futureIntent = newRestrictBackgroundChangedFuture();
@@ -508,7 +507,7 @@
 
         mService.setUidPolicy(UID_A, POLICY_ALLOW_METERED_BACKGROUND);
 
-        assertWhitelistUids(UID_A);
+        assertAllowlistUids(UID_A);
         assertUidPolicy(UID_A, POLICY_ALLOW_METERED_BACKGROUND);
         mPolicyListener.waitAndVerify()
                 .onUidPoliciesChanged(APP_ID_A, POLICY_ALLOW_METERED_BACKGROUND);
@@ -520,24 +519,24 @@
     }
 
     /**
-     * Removes whitelist when restrict background is on - app should receive an intent.
+     * Removes allowlist when restrict background is on - app should receive an intent.
      */
     @Test
-    @NetPolicyXml("uidA-whitelisted-restrict-background-on.xml")
-    public void testRemoveRestrictBackgroundWhitelist_restrictBackgroundOn() throws Exception {
-        assertRestrictBackgroundOn(); // Sanity check.
+    @NetPolicyXml("uidA-allowlisted-restrict-background-on.xml")
+    public void testRemoveRestrictBackgroundAllowlist_restrictBackgroundOn() throws Exception {
+        assertRestrictBackgroundOn();
         assertRestrictBackgroundChangedReceived(mFutureIntent, null);
-        removeRestrictBackgroundWhitelist(true);
+        removeRestrictBackgroundAllowlist(true);
     }
 
     /**
-     * Removes whitelist when restrict background is off - app should not receive an intent.
+     * Removes allowlist when restrict background is off - app should not receive an intent.
      */
     @Test
-    @NetPolicyXml("uidA-whitelisted-restrict-background-off.xml")
-    public void testRemoveRestrictBackgroundWhitelist_restrictBackgroundOff() throws Exception {
-        assertRestrictBackgroundOff(); // Sanity check.
-        removeRestrictBackgroundWhitelist(false);
+    @NetPolicyXml("uidA-allowlisted-restrict-background-off.xml")
+    public void testRemoveRestrictBackgroundAllowlist_restrictBackgroundOff() throws Exception {
+        assertRestrictBackgroundOff();
+        removeRestrictBackgroundAllowlist(false);
     }
 
     @Test
@@ -688,9 +687,8 @@
         assertFalse(mService.getRestrictBackground());
     }
 
-    private void removeRestrictBackgroundWhitelist(boolean expectIntent) throws Exception {
-        // Sanity checks.
-        assertWhitelistUids(UID_A);
+    private void removeRestrictBackgroundAllowlist(boolean expectIntent) throws Exception {
+        assertAllowlistUids(UID_A);
         assertUidPolicy(UID_A, POLICY_ALLOW_METERED_BACKGROUND);
 
         final FutureIntent futureIntent = newRestrictBackgroundChangedFuture();
@@ -698,7 +696,7 @@
 
         mService.setUidPolicy(UID_A, POLICY_NONE);
 
-        assertWhitelistUids();
+        assertAllowlistUids();
         assertUidPolicy(UID_A, POLICY_NONE);
         mPolicyListener.waitAndVerify().onUidPoliciesChanged(APP_ID_A, POLICY_NONE);
         if (expectIntent) {
@@ -709,27 +707,27 @@
     }
 
     /**
-     * Adds blacklist when restrict background is on - app should not receive an intent.
+     * Adds denylist when restrict background is on - app should not receive an intent.
      */
     @Test
     @NetPolicyXml("restrict-background-on.xml")
-    public void testAddRestrictBackgroundBlacklist_restrictBackgroundOn() throws Exception {
-        assertRestrictBackgroundOn(); // Sanity check.
+    public void testAddRestrictBackgroundDenylist_restrictBackgroundOn() throws Exception {
+        assertRestrictBackgroundOn();
         assertRestrictBackgroundChangedReceived(mFutureIntent, null);
-        addRestrictBackgroundBlacklist(false);
+        addRestrictBackgroundDenylist(false);
     }
 
     /**
-     * Adds blacklist when restrict background is off - app should receive an intent.
+     * Adds denylist when restrict background is off - app should receive an intent.
      */
     @Test
-    public void testAddRestrictBackgroundBlacklist_restrictBackgroundOff() throws Exception {
-        assertRestrictBackgroundOff(); // Sanity check.
-        addRestrictBackgroundBlacklist(true);
+    public void testAddRestrictBackgroundDenylist_restrictBackgroundOff() throws Exception {
+        assertRestrictBackgroundOff();
+        addRestrictBackgroundDenylist(true);
     }
 
-    private void addRestrictBackgroundBlacklist(boolean expectIntent) throws Exception {
-        assertUidPolicy(UID_A, POLICY_NONE); // Sanity check.
+    private void addRestrictBackgroundDenylist(boolean expectIntent) throws Exception {
+        assertUidPolicy(UID_A, POLICY_NONE);
         final FutureIntent futureIntent = newRestrictBackgroundChangedFuture();
         mPolicyListener.expect().onUidPoliciesChanged(anyInt(), anyInt());
 
@@ -746,28 +744,28 @@
     }
 
     /**
-     * Removes blacklist when restrict background is on - app should not receive an intent.
+     * Removes denylist when restrict background is on - app should not receive an intent.
      */
     @Test
-    @NetPolicyXml("uidA-blacklisted-restrict-background-on.xml")
-    public void testRemoveRestrictBackgroundBlacklist_restrictBackgroundOn() throws Exception {
-        assertRestrictBackgroundOn(); // Sanity check.
+    @NetPolicyXml("uidA-denylisted-restrict-background-on.xml")
+    public void testRemoveRestrictBackgroundDenylist_restrictBackgroundOn() throws Exception {
+        assertRestrictBackgroundOn();
         assertRestrictBackgroundChangedReceived(mFutureIntent, null);
-        removeRestrictBackgroundBlacklist(false);
+        removeRestrictBackgroundDenylist(false);
     }
 
     /**
-     * Removes blacklist when restrict background is off - app should receive an intent.
+     * Removes denylist when restrict background is off - app should receive an intent.
      */
     @Test
-    @NetPolicyXml("uidA-blacklisted-restrict-background-off.xml")
-    public void testRemoveRestrictBackgroundBlacklist_restrictBackgroundOff() throws Exception {
-        assertRestrictBackgroundOff(); // Sanity check.
-        removeRestrictBackgroundBlacklist(true);
+    @NetPolicyXml("uidA-denylisted-restrict-background-off.xml")
+    public void testRemoveRestrictBackgroundDenylist_restrictBackgroundOff() throws Exception {
+        assertRestrictBackgroundOff();
+        removeRestrictBackgroundDenylist(true);
     }
 
-    private void removeRestrictBackgroundBlacklist(boolean expectIntent) throws Exception {
-        assertUidPolicy(UID_A, POLICY_REJECT_METERED_BACKGROUND); // Sanity check.
+    private void removeRestrictBackgroundDenylist(boolean expectIntent) throws Exception {
+        assertUidPolicy(UID_A, POLICY_REJECT_METERED_BACKGROUND);
         final FutureIntent futureIntent = newRestrictBackgroundChangedFuture();
         mPolicyListener.expect().onUidPoliciesChanged(anyInt(), anyInt());
 
@@ -784,9 +782,8 @@
     }
 
     @Test
-    @NetPolicyXml("uidA-blacklisted-restrict-background-on.xml")
-    public void testBlacklistedAppIsNotNotifiedWhenRestrictBackgroundIsOn() throws Exception {
-        // Sanity checks.
+    @NetPolicyXml("uidA-denylisted-restrict-background-on.xml")
+    public void testDenylistedAppIsNotNotifiedWhenRestrictBackgroundIsOn() throws Exception {
         assertRestrictBackgroundOn();
         assertRestrictBackgroundChangedReceived(mFutureIntent, null);
         assertUidPolicy(UID_A, POLICY_REJECT_METERED_BACKGROUND);
@@ -797,12 +794,11 @@
     }
 
     @Test
-    @NetPolicyXml("uidA-whitelisted-restrict-background-on.xml")
-    public void testWhitelistedAppIsNotNotifiedWhenRestrictBackgroundIsOn() throws Exception {
-        // Sanity checks.
+    @NetPolicyXml("uidA-allowlisted-restrict-background-on.xml")
+    public void testAllowlistedAppIsNotNotifiedWhenRestrictBackgroundIsOn() throws Exception {
         assertRestrictBackgroundOn();
         assertRestrictBackgroundChangedReceived(mFutureIntent, null);
-        assertWhitelistUids(UID_A);
+        assertAllowlistUids(UID_A);
 
         final FutureIntent futureIntent = newRestrictBackgroundChangedFuture();
         setRestrictBackground(true);
@@ -810,12 +806,11 @@
     }
 
     @Test
-    @NetPolicyXml("uidA-whitelisted-restrict-background-on.xml")
-    public void testWhitelistedAppIsNotifiedWhenBlacklisted() throws Exception {
-        // Sanity checks.
+    @NetPolicyXml("uidA-allowlisted-restrict-background-on.xml")
+    public void testAllowlistedAppIsNotifiedWhenDenylisted() throws Exception {
         assertRestrictBackgroundOn();
         assertRestrictBackgroundChangedReceived(mFutureIntent, null);
-        assertWhitelistUids(UID_A);
+        assertAllowlistUids(UID_A);
 
         final FutureIntent futureIntent = newRestrictBackgroundChangedFuture();
         mService.setUidPolicy(UID_A, POLICY_REJECT_METERED_BACKGROUND);
@@ -823,8 +818,8 @@
     }
 
     @Test
-    @NetPolicyXml("restrict-background-lists-whitelist-format.xml")
-    public void testRestrictBackgroundLists_whitelistFormat() throws Exception {
+    @NetPolicyXml("restrict-background-lists-allowlist-format.xml")
+    public void testRestrictBackgroundLists_allowlistFormat() throws Exception {
         restrictBackgroundListsTest();
     }
 
@@ -835,33 +830,33 @@
     }
 
     private void restrictBackgroundListsTest() throws Exception {
-        // UIds that are whitelisted.
-        assertWhitelistUids(UID_A, UID_B, UID_C);
+        // UIds that are allowlisted.
+        assertAllowlistUids(UID_A, UID_B, UID_C);
         assertUidPolicy(UID_A, POLICY_ALLOW_METERED_BACKGROUND);
         assertUidPolicy(UID_B, POLICY_ALLOW_METERED_BACKGROUND);
         assertUidPolicy(UID_C, POLICY_ALLOW_METERED_BACKGROUND);
 
-        // UIDs that are blacklisted.
+        // UIDs that are denylisted.
         assertUidPolicy(UID_D, POLICY_NONE);
         assertUidPolicy(UID_E, POLICY_REJECT_METERED_BACKGROUND);
 
         // UIDS that have legacy policies.
         assertUidPolicy(UID_F, 2); // POLICY_ALLOW_BACKGROUND_BATTERY_SAVE
 
-        // Remove whitelist.
+        // Remove allowlist.
         mService.setUidPolicy(UID_A, POLICY_NONE);
         assertUidPolicy(UID_A, POLICY_NONE);
-        assertWhitelistUids(UID_B, UID_C);
+        assertAllowlistUids(UID_B, UID_C);
 
-        // Add whitelist when blacklisted.
+        // Add allowlist when denylisted.
         mService.setUidPolicy(UID_E, POLICY_ALLOW_METERED_BACKGROUND);
         assertUidPolicy(UID_E, POLICY_ALLOW_METERED_BACKGROUND);
-        assertWhitelistUids(UID_B, UID_C, UID_E);
+        assertAllowlistUids(UID_B, UID_C, UID_E);
 
-        // Add blacklist when whitelisted.
+        // Add denylist when allowlisted.
         mService.setUidPolicy(UID_B, POLICY_REJECT_METERED_BACKGROUND);
         assertUidPolicy(UID_B, POLICY_REJECT_METERED_BACKGROUND);
-        assertWhitelistUids(UID_C, UID_E);
+        assertAllowlistUids(UID_C, UID_E);
     }
 
     /**
@@ -870,9 +865,9 @@
     @Test
     @NetPolicyXml("restrict-background-lists-mixed-format.xml")
     public void testRestrictBackgroundLists_mixedFormat() throws Exception {
-        assertWhitelistUids(UID_A, UID_C, UID_D);
+        assertAllowlistUids(UID_A, UID_C, UID_D);
         assertUidPolicy(UID_A, POLICY_ALLOW_METERED_BACKGROUND);
-        assertUidPolicy(UID_B, POLICY_REJECT_METERED_BACKGROUND); // Blacklist prevails.
+        assertUidPolicy(UID_B, POLICY_REJECT_METERED_BACKGROUND); // Denylist prevails.
         assertUidPolicy(UID_C, (POLICY_ALLOW_METERED_BACKGROUND | 2));
         assertUidPolicy(UID_D, POLICY_ALLOW_METERED_BACKGROUND);
     }
@@ -2045,7 +2040,7 @@
         }
     }
 
-    private void assertWhitelistUids(int... uids) {
+    private void assertAllowlistUids(int... uids) {
         assertContainsInAnyOrder(mService.getUidsWithPolicy(POLICY_ALLOW_METERED_BACKGROUND), uids);
     }
 
@@ -2133,7 +2128,6 @@
 
     private void setRestrictBackground(boolean flag) throws Exception {
         mService.setRestrictBackground(flag);
-        // Sanity check.
         assertEquals("restrictBackground not set", flag, mService.getRestrictBackground());
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
index 80f145b..35d6f47 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
@@ -48,7 +48,7 @@
             public void sendPackageBroadcast(final String action, final String pkg,
                     final Bundle extras, final int flags, final String targetPkg,
                     final IIntentReceiver finishedReceiver, final int[] userIds,
-                    int[] instantUserIds, SparseArray<int[]> broadcastWhitelist) {
+                    int[] instantUserIds, SparseArray<int[]> broadcastAllowList) {
             }
 
             public void sendPackageAddedForNewUsers(String packageName,
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingEquivalenceTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingEquivalenceTest.kt
index 9f9ec31..f96ebda 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingEquivalenceTest.kt
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingEquivalenceTest.kt
@@ -23,6 +23,8 @@
 
 import org.junit.Rule
 import org.junit.Test
+import org.junit.rules.Timeout
+import java.util.concurrent.TimeUnit
 
 /**
  * Collects APKs from the device and verifies that the new parsing behavior outputs
@@ -32,6 +34,9 @@
 class AndroidPackageParsingEquivalenceTest : AndroidPackageParsingTestBase() {
 
     @get:Rule
+    val timeout = Timeout(4, TimeUnit.MINUTES)
+
+    @get:Rule
     val expect = Expect.create()
 
     @Test
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/timezonedetector/FakeTimeZoneDetectorStrategy.java b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
index dcf3190..e5e9311 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
@@ -27,6 +27,9 @@
 import android.app.timezonedetector.TimeZoneConfiguration;
 import android.util.IndentingPrintWriter;
 
+import java.util.ArrayList;
+import java.util.List;
+
 class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
 
     private StrategyListener mListener;
@@ -41,6 +44,7 @@
     private TelephonyTimeZoneSuggestion mLastTelephonySuggestion;
     private boolean mHandleAutoTimeZoneConfigChangedCalled;
     private boolean mDumpCalled;
+    private final List<Dumpable> mDumpables = new ArrayList<>();
 
     @Override
     public void setStrategyListener(@NonNull StrategyListener listener) {
@@ -105,7 +109,7 @@
 
     @Override
     public void addDumpable(Dumpable dumpable) {
-        // Stubbed
+        mDumpables.add(dumpable);
     }
 
     @Override
@@ -149,4 +153,8 @@
     void verifyDumpCalled() {
         assertTrue(mDumpCalled);
     }
+
+    void verifyHasDumpable(Dumpable expected) {
+        assertTrue(mDumpables.contains(expected));
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java
index 0e2c227..e9d57e5 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java
@@ -75,6 +75,16 @@
         mFakeTimeZoneDetectorStrategy.verifySuggestGeolocationTimeZoneCalled(timeZoneSuggestion);
     }
 
+    @Test
+    public void testAddDumpable() throws Exception {
+        Dumpable stubbedDumpable = mock(Dumpable.class);
+
+        mTimeZoneDetectorInternal.addDumpable(stubbedDumpable);
+        mTestHandler.assertTotalMessagesEnqueued(0);
+
+        mFakeTimeZoneDetectorStrategy.verifyHasDumpable(stubbedDumpable);
+    }
+
     private static GeolocationTimeZoneSuggestion createGeolocationTimeZoneSuggestion() {
         return new GeolocationTimeZoneSuggestion(ARBITRARY_ZONE_IDS);
     }
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
index 8034cac..3a1ec4f 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
@@ -52,6 +52,7 @@
 
 import java.io.PrintWriter;
 import java.io.StringWriter;
+import java.util.Arrays;
 
 @RunWith(AndroidJUnit4.class)
 public class TimeZoneDetectorServiceTest {
@@ -253,6 +254,39 @@
     }
 
     @Test(expected = SecurityException.class)
+    public void testSuggestGeolocationTimeZone_withoutPermission() {
+        doThrow(new SecurityException("Mock"))
+                .when(mMockContext).enforceCallingOrSelfPermission(anyString(), any());
+        GeolocationTimeZoneSuggestion timeZoneSuggestion = createGeolocationTimeZoneSuggestion();
+
+        try {
+            mTimeZoneDetectorService.suggestGeolocationTimeZone(timeZoneSuggestion);
+            fail();
+        } finally {
+            verify(mMockContext).enforceCallingOrSelfPermission(
+                    eq(android.Manifest.permission.SET_TIME_ZONE),
+                    anyString());
+        }
+    }
+
+    @Test
+    public void testSuggestGeolocationTimeZone() throws Exception {
+        doNothing().when(mMockContext).enforceCallingOrSelfPermission(anyString(), any());
+
+        GeolocationTimeZoneSuggestion timeZoneSuggestion = createGeolocationTimeZoneSuggestion();
+
+        mTimeZoneDetectorService.suggestGeolocationTimeZone(timeZoneSuggestion);
+        mTestHandler.assertTotalMessagesEnqueued(1);
+
+        verify(mMockContext).enforceCallingOrSelfPermission(
+                eq(android.Manifest.permission.SET_TIME_ZONE),
+                anyString());
+
+        mTestHandler.waitForMessagesToBeProcessed();
+        mFakeTimeZoneDetectorStrategy.verifySuggestGeolocationTimeZoneCalled(timeZoneSuggestion);
+    }
+
+    @Test(expected = SecurityException.class)
     public void testSuggestManualTimeZone_withoutPermission() {
         doThrow(new SecurityException("Mock"))
                 .when(mMockContext).enforceCallingOrSelfPermission(anyString(), any());
@@ -346,7 +380,7 @@
     }
 
     @Test
-    public void testAutoTimeZoneDetectionChanged() throws Exception {
+    public void testHandleAutoTimeZoneConfigChanged() throws Exception {
         mTimeZoneDetectorService.handleAutoTimeZoneConfigChanged();
         mTestHandler.assertTotalMessagesEnqueued(1);
         mTestHandler.waitForMessagesToBeProcessed();
@@ -370,10 +404,15 @@
     private static TimeZoneCapabilities createTimeZoneCapabilities() {
         return new TimeZoneCapabilities.Builder(ARBITRARY_USER_ID)
                 .setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
+                .setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
                 .setSuggestManualTimeZone(CAPABILITY_POSSESSED)
                 .build();
     }
 
+    private static GeolocationTimeZoneSuggestion createGeolocationTimeZoneSuggestion() {
+        return new GeolocationTimeZoneSuggestion(Arrays.asList("TestZoneId"));
+    }
+
     private static ManualTimeZoneSuggestion createManualTimeZoneSuggestion() {
         return new ManualTimeZoneSuggestion("TestZoneId");
     }
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
index 6855445..a6caa42 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
@@ -41,6 +41,7 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
+import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
@@ -56,10 +57,10 @@
 import org.junit.Test;
 
 import java.io.StringWriter;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.LinkedList;
+import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.atomic.AtomicBoolean;
 
@@ -93,16 +94,26 @@
                     TELEPHONY_SCORE_HIGHEST),
     };
 
-    private static final TimeZoneConfiguration CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED =
+    private static final TimeZoneConfiguration CONFIG_AUTO_ENABLED =
             new TimeZoneConfiguration.Builder()
                     .setAutoDetectionEnabled(true)
                     .build();
 
-    private static final TimeZoneConfiguration CONFIG_AUTO_TIME_ZONE_DETECTION_DISABLED =
+    private static final TimeZoneConfiguration CONFIG_AUTO_DISABLED =
             new TimeZoneConfiguration.Builder()
                     .setAutoDetectionEnabled(false)
                     .build();
 
+    private static final TimeZoneConfiguration CONFIG_GEO_DETECTION_DISABLED =
+            new TimeZoneConfiguration.Builder()
+                    .setGeoDetectionEnabled(false)
+                    .build();
+
+    private static final TimeZoneConfiguration CONFIG_GEO_DETECTION_ENABLED =
+            new TimeZoneConfiguration.Builder()
+                    .setGeoDetectionEnabled(true)
+                    .build();
+
     private TimeZoneDetectorStrategyImpl mTimeZoneDetectorStrategy;
     private FakeCallback mFakeCallback;
     private MockStrategyListener mMockStrategyListener;
@@ -120,7 +131,7 @@
     public void testGetCapabilities() {
         new Script()
                 .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED);
+                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
         TimeZoneCapabilities expectedCapabilities = mFakeCallback.getCapabilities(USER_ID);
         assertEquals(expectedCapabilities, mTimeZoneDetectorStrategy.getCapabilities(USER_ID));
     }
@@ -129,7 +140,7 @@
     public void testGetConfiguration() {
         new Script()
                 .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED);
+                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
         TimeZoneConfiguration expectedConfiguration = mFakeCallback.getConfiguration(USER_ID);
         assertTrue(expectedConfiguration.isComplete());
         assertEquals(expectedConfiguration, mTimeZoneDetectorStrategy.getConfiguration(USER_ID));
@@ -140,20 +151,22 @@
         Script script = new Script();
 
         script.initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED);
+                CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
         {
             // Check the fake test infra is doing what is expected.
             TimeZoneCapabilities capabilities = mFakeCallback.getCapabilities(USER_ID);
             assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureAutoDetectionEnabled());
+            assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureGeoDetectionEnabled());
             assertEquals(CAPABILITY_NOT_APPLICABLE, capabilities.getSuggestManualTimeZone());
         }
 
         script.initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                CONFIG_AUTO_TIME_ZONE_DETECTION_DISABLED);
+                CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED));
         {
             // Check the fake test infra is doing what is expected.
             TimeZoneCapabilities capabilities = mFakeCallback.getCapabilities(USER_ID);
             assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureAutoDetectionEnabled());
+            assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureGeoDetectionEnabled());
             assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZone());
         }
     }
@@ -163,20 +176,22 @@
         Script script = new Script();
 
         script.initializeUser(USER_ID, UserCase.RESTRICTED,
-                CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED);
+                CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
         {
             // Check the fake test infra is doing what is expected.
             TimeZoneCapabilities capabilities = mFakeCallback.getCapabilities(USER_ID);
             assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureAutoDetectionEnabled());
+            assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureGeoDetectionEnabled());
             assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getSuggestManualTimeZone());
         }
 
         script.initializeUser(USER_ID, UserCase.RESTRICTED,
-                CONFIG_AUTO_TIME_ZONE_DETECTION_DISABLED);
+                CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED));
         {
             // Check the fake test infra is doing what is expected.
             TimeZoneCapabilities capabilities = mFakeCallback.getCapabilities(USER_ID);
             assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureAutoDetectionEnabled());
+            assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureGeoDetectionEnabled());
             assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getSuggestManualTimeZone());
         }
     }
@@ -186,20 +201,22 @@
         Script script = new Script();
 
         script.initializeUser(USER_ID, UserCase.AUTO_DETECT_NOT_SUPPORTED,
-                CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED);
+                CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
         {
             // Check the fake test infra is doing what is expected.
             TimeZoneCapabilities capabilities = mFakeCallback.getCapabilities(USER_ID);
             assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureAutoDetectionEnabled());
+            assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureGeoDetectionEnabled());
             assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZone());
         }
 
         script.initializeUser(USER_ID, UserCase.AUTO_DETECT_NOT_SUPPORTED,
-                CONFIG_AUTO_TIME_ZONE_DETECTION_DISABLED);
+                CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED));
         {
             // Check the fake test infra is doing what is expected.
             TimeZoneCapabilities capabilities = mFakeCallback.getCapabilities(USER_ID);
             assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureAutoDetectionEnabled());
+            assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureGeoDetectionEnabled());
             assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZone());
         }
     }
@@ -208,42 +225,61 @@
     public void testUpdateConfiguration_unrestricted() {
         Script script = new Script()
                 .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED);
+                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
 
         // Set the configuration with auto detection enabled.
-        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED);
+        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */);
 
         // Nothing should have happened: it was initialized in this state.
         script.verifyConfigurationNotChanged();
 
         // Update the configuration with auto detection disabled.
-        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_TIME_ZONE_DETECTION_DISABLED);
+        script.simulateUpdateConfiguration(
+                USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */);
 
         // The settings should have been changed and the StrategyListener onChange() called.
-        script.verifyConfigurationChangedAndReset(
-                USER_ID, CONFIG_AUTO_TIME_ZONE_DETECTION_DISABLED);
+        script.verifyConfigurationChangedAndReset(USER_ID,
+                CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED));
 
         // Update the configuration with auto detection enabled.
-        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED);
+        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */);
 
         // The settings should have been changed and the StrategyListener onChange() called.
-        script.verifyConfigurationChangedAndReset(USER_ID, CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED);
+        script.verifyConfigurationChangedAndReset(USER_ID,
+                CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
+
+        // Update the configuration to enable geolocation time zone detection.
+        script.simulateUpdateConfiguration(
+                USER_ID, CONFIG_GEO_DETECTION_ENABLED,  true /* expectedResult */);
+
+        // The settings should have been changed and the StrategyListener onChange() called.
+        script.verifyConfigurationChangedAndReset(USER_ID,
+                CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_ENABLED));
     }
 
     @Test
     public void testUpdateConfiguration_restricted() {
         Script script = new Script()
                 .initializeUser(USER_ID, UserCase.RESTRICTED,
-                        CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED);
+                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
 
         // Try to update the configuration with auto detection disabled.
-        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_TIME_ZONE_DETECTION_DISABLED);
+        script.simulateUpdateConfiguration(
+                USER_ID, CONFIG_AUTO_DISABLED, false /* expectedResult */);
 
         // The settings should not have been changed: user shouldn't have the capabilities.
         script.verifyConfigurationNotChanged();
 
         // Update the configuration with auto detection enabled.
-        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED);
+        script.simulateUpdateConfiguration(
+                USER_ID, CONFIG_AUTO_ENABLED,  false /* expectedResult */);
+
+        // The settings should not have been changed: user shouldn't have the capabilities.
+        script.verifyConfigurationNotChanged();
+
+        // Update the configuration to enable geolocation time zone detection.
+        script.simulateUpdateConfiguration(
+                USER_ID, CONFIG_GEO_DETECTION_ENABLED,  false /* expectedResult */);
 
         // The settings should not have been changed: user shouldn't have the capabilities.
         script.verifyConfigurationNotChanged();
@@ -253,16 +289,18 @@
     public void testUpdateConfiguration_autoDetectNotSupported() {
         Script script = new Script()
                 .initializeUser(USER_ID, UserCase.AUTO_DETECT_NOT_SUPPORTED,
-                        CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED);
+                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
 
         // Try to update the configuration with auto detection disabled.
-        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_TIME_ZONE_DETECTION_DISABLED);
+        script.simulateUpdateConfiguration(
+                USER_ID, CONFIG_AUTO_DISABLED, false /* expectedResult */);
 
         // The settings should not have been changed: user shouldn't have the capabilities.
         script.verifyConfigurationNotChanged();
 
         // Update the configuration with auto detection enabled.
-        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED);
+        script.simulateUpdateConfiguration(
+                USER_ID, CONFIG_AUTO_ENABLED, false /* expectedResult */);
 
         // The settings should not have been changed: user shouldn't have the capabilities.
         script.verifyConfigurationNotChanged();
@@ -276,7 +314,7 @@
                 createEmptySlotIndex2Suggestion();
         Script script = new Script()
                 .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED)
+                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED))
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
         script.simulateTelephonyTimeZoneSuggestion(slotIndex1TimeZoneSuggestion)
@@ -308,6 +346,10 @@
                 mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
     }
 
+    /**
+     * Telephony suggestions have quality metadata. Ordinarily, low scoring suggestions are not
+     * used, but this is not true if the device's time zone setting is uninitialized.
+     */
     @Test
     public void testTelephonySuggestionsWhenTimeZoneUninitialized() {
         assertTrue(TELEPHONY_SCORE_LOW < TELEPHONY_SCORE_USAGE_THRESHOLD);
@@ -319,7 +361,7 @@
 
         Script script = new Script()
                 .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED);
+                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
 
         // A low quality suggestions will not be taken: The device time zone setting is left
         // uninitialized.
@@ -376,16 +418,16 @@
 
     /**
      * Confirms that toggling the auto time zone detection setting has the expected behavior when
-     * the strategy is "opinionated".
+     * the strategy is "opinionated" when using telephony auto detection.
      */
     @Test
-    public void testTogglingAutoTimeZoneDetection() {
+    public void testTogglingAutoDetection_autoTelephony() {
         Script script = new Script();
 
         for (TelephonyTestCase testCase : TELEPHONY_TEST_CASES) {
             // Start with the device in a known state.
             script.initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                    CONFIG_AUTO_TIME_ZONE_DETECTION_DISABLED)
+                    CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED))
                     .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
             TelephonyTimeZoneSuggestion suggestion =
@@ -405,7 +447,8 @@
                     mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
 
             // Toggling the time zone setting on should cause the device setting to be set.
-            script.simulateAutoTimeZoneDetectionEnabled(USER_ID, true);
+            script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED,
+                    true /* expectedResult */);
 
             // When time zone detection is already enabled the suggestion (if it scores highly
             // enough) should be set immediately.
@@ -422,7 +465,8 @@
                     mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
 
             // Toggling the time zone setting should off should do nothing.
-            script.simulateAutoTimeZoneDetectionEnabled(USER_ID, false)
+            script.simulateUpdateConfiguration(
+                    USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
                     .verifyTimeZoneNotChanged();
 
             // Assert internal service state.
@@ -437,7 +481,7 @@
     public void testTelephonySuggestionsSingleSlotId() {
         Script script = new Script()
                 .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED)
+                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED))
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
         for (TelephonyTestCase testCase : TELEPHONY_TEST_CASES) {
@@ -451,8 +495,7 @@
          */
 
         // Each test case will have the same or lower score than the last.
-        ArrayList<TelephonyTestCase> descendingCasesByScore =
-                new ArrayList<>(Arrays.asList(TELEPHONY_TEST_CASES));
+        List<TelephonyTestCase> descendingCasesByScore = list(TELEPHONY_TEST_CASES);
         Collections.reverse(descendingCasesByScore);
 
         for (TelephonyTestCase testCase : descendingCasesByScore) {
@@ -504,7 +547,7 @@
 
         Script script = new Script()
                 .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED)
+                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED))
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
                 // Initialize the latest suggestions as empty so we don't need to worry about nulls
                 // below for the first loop.
@@ -583,15 +626,15 @@
     }
 
     /**
-     * The {@link TimeZoneDetectorStrategyImpl.Callback} is left to detect whether changing
-     * the time zone is actually necessary. This test proves that the service doesn't assume it
-     * knows the current setting.
+     * The {@link TimeZoneDetectorStrategyImpl.Callback} is left to detect whether changing the time
+     * zone is actually necessary. This test proves that the strategy doesn't assume it knows the
+     * current settings.
      */
     @Test
-    public void testTelephonySuggestionTimeZoneDetectorStrategyDoesNotAssumeCurrentSetting() {
+    public void testTelephonySuggestionStrategyDoesNotAssumeCurrentSetting_autoTelephony() {
         Script script = new Script()
                 .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED);
+                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
 
         TelephonyTestCase testCase = newTelephonyTestCase(
                 MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE, TELEPHONY_SCORE_HIGH);
@@ -609,26 +652,40 @@
 
         // Toggling time zone detection should set the device time zone only if the current setting
         // value is different from the most recent telephony suggestion.
-        script.simulateAutoTimeZoneDetectionEnabled(USER_ID, false)
+        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
                 .verifyTimeZoneNotChanged()
-                .simulateAutoTimeZoneDetectionEnabled(USER_ID, true)
+                .simulateUpdateConfiguration(
+                        USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
                 .verifyTimeZoneNotChanged();
 
         // Simulate a user turning auto detection off, a new suggestion being made while auto
         // detection is off, and the user turning it on again.
-        script.simulateAutoTimeZoneDetectionEnabled(USER_ID, false)
+        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
                 .simulateTelephonyTimeZoneSuggestion(newYorkSuggestion)
                 .verifyTimeZoneNotChanged();
         // Latest suggestion should be used.
-        script.simulateAutoTimeZoneDetectionEnabled(USER_ID, true)
+        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
                 .verifyTimeZoneChangedAndReset(newYorkSuggestion);
     }
 
     @Test
-    public void testManualSuggestion_unrestricted_simulateAutoTimeZoneEnabled() {
+    public void testManualSuggestion_autoDetectionEnabled_autoTelephony() {
+        checkManualSuggestion_autoDetectionEnabled(false /* geoDetectionEnabled */);
+    }
+
+    @Test
+    public void testManualSuggestion_autoDetectionEnabled_autoGeo() {
+        checkManualSuggestion_autoDetectionEnabled(true /* geoDetectionEnabled */);
+    }
+
+    private void checkManualSuggestion_autoDetectionEnabled(boolean geoDetectionEnabled) {
+        TimeZoneConfiguration geoTzEnabledConfig =
+                new TimeZoneConfiguration.Builder()
+                        .setGeoDetectionEnabled(geoDetectionEnabled)
+                        .build();
         Script script = new Script()
                 .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED)
+                        CONFIG_AUTO_ENABLED.with(geoTzEnabledConfig))
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
         // Auto time zone detection is enabled so the manual suggestion should be ignored.
@@ -641,7 +698,7 @@
     public void testManualSuggestion_restricted_simulateAutoTimeZoneEnabled() {
         Script script = new Script()
                 .initializeUser(USER_ID, UserCase.RESTRICTED,
-                        CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED)
+                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED))
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
         // Auto time zone detection is enabled so the manual suggestion should be ignored.
@@ -654,7 +711,7 @@
     public void testManualSuggestion_autoDetectNotSupported_simulateAutoTimeZoneEnabled() {
         Script script = new Script()
                 .initializeUser(USER_ID, UserCase.AUTO_DETECT_NOT_SUPPORTED,
-                        CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED)
+                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED))
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
         // Auto time zone detection is enabled so the manual suggestion should be ignored.
@@ -668,7 +725,7 @@
     public void testManualSuggestion_unrestricted_autoTimeZoneDetectionDisabled() {
         Script script = new Script()
                 .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_TIME_ZONE_DETECTION_DISABLED)
+                        CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED))
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
         // Auto time zone detection is disabled so the manual suggestion should be used.
@@ -682,7 +739,7 @@
     public void testManualSuggestion_restricted_autoTimeZoneDetectionDisabled() {
         Script script = new Script()
                 .initializeUser(USER_ID, UserCase.RESTRICTED,
-                        CONFIG_AUTO_TIME_ZONE_DETECTION_DISABLED)
+                        CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED))
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
         // Restricted users do not have the capability.
@@ -696,7 +753,7 @@
     public void testManualSuggestion_autoDetectNotSupported_autoTimeZoneDetectionDisabled() {
         Script script = new Script()
                 .initializeUser(USER_ID, UserCase.AUTO_DETECT_NOT_SUPPORTED,
-                        CONFIG_AUTO_TIME_ZONE_DETECTION_DISABLED)
+                        CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED))
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
         // Unrestricted users have the capability.
@@ -707,10 +764,261 @@
     }
 
     @Test
+    public void testGeoSuggestion_uncertain() {
+        Script script = new Script()
+                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
+                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_ENABLED))
+                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
+
+        GeolocationTimeZoneSuggestion uncertainSuggestion = createUncertainGeoLocationSuggestion();
+
+        script.simulateGeolocationTimeZoneSuggestion(uncertainSuggestion)
+                .verifyTimeZoneNotChanged();
+
+        // Assert internal service state.
+        assertEquals(uncertainSuggestion,
+                mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+    }
+
+    @Test
+    public void testGeoSuggestion_noZones() {
+        Script script = new Script()
+                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
+                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_ENABLED))
+                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
+
+        GeolocationTimeZoneSuggestion noZonesSuggestion = createGeoLocationSuggestion(list());
+
+        script.simulateGeolocationTimeZoneSuggestion(noZonesSuggestion)
+                .verifyTimeZoneNotChanged();
+
+        // Assert internal service state.
+        assertEquals(noZonesSuggestion, mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+    }
+
+    @Test
+    public void testGeoSuggestion_oneZone() {
+        GeolocationTimeZoneSuggestion suggestion =
+                createGeoLocationSuggestion(list("Europe/London"));
+
+        Script script = new Script()
+                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
+                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_ENABLED))
+                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
+
+        script.simulateGeolocationTimeZoneSuggestion(suggestion)
+                .verifyTimeZoneChangedAndReset("Europe/London");
+
+        // Assert internal service state.
+        assertEquals(suggestion, mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+    }
+
+    /**
+     * In the current implementation, the first zone ID is always used unless the device is set to
+     * one of the other options. This is "stickiness" - the device favors the zone it is currently
+     * set to until that unambiguously can't be correct.
+     */
+    @Test
+    public void testGeoSuggestion_multiZone() {
+        GeolocationTimeZoneSuggestion londonOnlySuggestion =
+                createGeoLocationSuggestion(list("Europe/London"));
+        GeolocationTimeZoneSuggestion londonOrParisSuggestion =
+                createGeoLocationSuggestion(list("Europe/Paris", "Europe/London"));
+        GeolocationTimeZoneSuggestion parisOnlySuggestion =
+                createGeoLocationSuggestion(list("Europe/Paris"));
+
+        Script script = new Script()
+                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
+                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_ENABLED))
+                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
+
+        script.simulateGeolocationTimeZoneSuggestion(londonOnlySuggestion)
+                .verifyTimeZoneChangedAndReset("Europe/London");
+        assertEquals(londonOnlySuggestion,
+                mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+
+        // Confirm bias towards the current device zone when there's multiple zones to choose from.
+        script.simulateGeolocationTimeZoneSuggestion(londonOrParisSuggestion)
+                .verifyTimeZoneNotChanged();
+        assertEquals(londonOrParisSuggestion,
+                mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+
+        script.simulateGeolocationTimeZoneSuggestion(parisOnlySuggestion)
+                .verifyTimeZoneChangedAndReset("Europe/Paris");
+        assertEquals(parisOnlySuggestion,
+                mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+
+        // Now the suggestion that previously left the device on Europe/London will leave the device
+        // on Europe/Paris.
+        script.simulateGeolocationTimeZoneSuggestion(londonOrParisSuggestion)
+                .verifyTimeZoneNotChanged();
+        assertEquals(londonOrParisSuggestion,
+                mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+    }
+
+    /**
+     * Confirms that toggling the auto time zone detection enabled setting has the expected behavior
+     * when the strategy is "opinionated" and "un-opinionated" when in geolocation detection is
+     * enabled.
+     */
+    @Test
+    public void testTogglingAutoDetectionEnabled_autoGeo() {
+        GeolocationTimeZoneSuggestion geolocationSuggestion =
+                createGeoLocationSuggestion(list("Europe/London"));
+        GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion =
+                createUncertainGeoLocationSuggestion();
+        ManualTimeZoneSuggestion manualSuggestion = createManualSuggestion("Europe/Paris");
+
+        Script script = new Script()
+                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
+                        CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_ENABLED))
+                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
+
+        script.simulateGeolocationTimeZoneSuggestion(geolocationSuggestion);
+
+        // When time zone detection is not enabled, the time zone suggestion will not be set.
+        script.verifyTimeZoneNotChanged();
+
+        // Assert internal service state.
+        assertEquals(geolocationSuggestion,
+                mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+
+        // Toggling the time zone setting on should cause the device setting to be set.
+        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
+                .verifyTimeZoneChangedAndReset("Europe/London");
+
+        // Toggling the time zone setting should off should do nothing because the device is now
+        // set to that time zone.
+        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
+                .verifyTimeZoneNotChanged()
+                .simulateUpdateConfiguration(
+                        USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
+                .verifyTimeZoneNotChanged();
+
+        // Now toggle auto time zone setting, and confirm it is opinionated.
+        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
+                .simulateManualTimeZoneSuggestion(
+                        USER_ID, manualSuggestion, true /* expectedResult */)
+                .verifyTimeZoneChangedAndReset(manualSuggestion)
+                .simulateUpdateConfiguration(
+                        USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
+                .verifyTimeZoneChangedAndReset("Europe/London");
+
+        // Now withdraw the geolocation suggestion, and assert the strategy is no longer
+        // opinionated.
+        /* expectedResult */
+        script.simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion)
+                .verifyTimeZoneNotChanged()
+                .simulateUpdateConfiguration(
+                        USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
+                .verifyTimeZoneNotChanged()
+                .simulateManualTimeZoneSuggestion(
+                        USER_ID, manualSuggestion, true /* expectedResult */)
+                .verifyTimeZoneChangedAndReset(manualSuggestion)
+                .simulateUpdateConfiguration(
+                        USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
+                .verifyTimeZoneNotChanged();
+
+        // Assert internal service state.
+        assertEquals(uncertainGeolocationSuggestion,
+                mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+    }
+
+    /**
+     * Confirms that changing the geolocation time zone detection enabled setting has the expected
+     * behavior, i.e. immediately recompute the detected time zone using different signals.
+     */
+    @Test
+    public void testChangingGeoDetectionEnabled() {
+        GeolocationTimeZoneSuggestion geolocationSuggestion =
+                createGeoLocationSuggestion(list("Europe/London"));
+        TelephonyTimeZoneSuggestion telephonySuggestion = createTelephonySuggestion(
+                SLOT_INDEX1, MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE,
+                "Europe/Paris");
+
+        Script script = new Script()
+                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
+                        CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED))
+                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
+
+        // Add suggestions. Nothing should happen as time zone detection is disabled.
+        script.simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
+                .verifyTimeZoneNotChanged();
+        script.simulateTelephonyTimeZoneSuggestion(telephonySuggestion)
+                .verifyTimeZoneNotChanged();
+
+        // Assert internal service state.
+        assertEquals(geolocationSuggestion,
+                mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+        assertEquals(telephonySuggestion,
+                mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1).suggestion);
+
+        // Toggling the time zone detection enabled setting on should cause the device setting to be
+        // set from the telephony signal, as we've started with geolocation time zone detection
+        // disabled.
+        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
+                .verifyTimeZoneChangedAndReset(telephonySuggestion);
+
+        // Changing the detection to enable geo detection should cause the device tz setting to
+        // change to the geo suggestion.
+        script.simulateUpdateConfiguration(
+                USER_ID, CONFIG_GEO_DETECTION_ENABLED, true /* expectedResult */)
+                .verifyTimeZoneChangedAndReset(geolocationSuggestion.getZoneIds().get(0));
+
+        // Changing the detection to disable geo detection should cause the device tz setting to
+        // change to the telephony suggestion.
+        script.simulateUpdateConfiguration(
+                USER_ID, CONFIG_GEO_DETECTION_DISABLED, true /* expectedResult */)
+                .verifyTimeZoneChangedAndReset(telephonySuggestion);
+    }
+
+    /**
+     * The {@link TimeZoneDetectorStrategyImpl.Callback} is left to detect whether changing the time
+     * zone is actually necessary. This test proves that the strategy doesn't assume it knows the
+     * current setting.
+     */
+    @Test
+    public void testTimeZoneDetectorStrategyDoesNotAssumeCurrentSetting_autoGeo() {
+        GeolocationTimeZoneSuggestion losAngelesSuggestion =
+                createGeoLocationSuggestion(list("America/Los_Angeles"));
+        GeolocationTimeZoneSuggestion newYorkSuggestion =
+                createGeoLocationSuggestion(list("America/New_York"));
+
+        Script script = new Script()
+                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
+                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_ENABLED));
+
+        // Initialization.
+        script.simulateGeolocationTimeZoneSuggestion(losAngelesSuggestion)
+                .verifyTimeZoneChangedAndReset("America/Los_Angeles");
+        // Suggest it again - it should not be set because it is already set.
+        script.simulateGeolocationTimeZoneSuggestion(losAngelesSuggestion)
+                .verifyTimeZoneNotChanged();
+
+        // Toggling time zone detection should set the device time zone only if the current setting
+        // value is different from the most recent telephony suggestion.
+        /* expectedResult */
+        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
+                .verifyTimeZoneNotChanged()
+                .simulateUpdateConfiguration(
+                        USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
+                .verifyTimeZoneNotChanged();
+
+        // Simulate a user turning auto detection off, a new suggestion being made while auto
+        // detection is off, and the user turning it on again.
+        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
+                .simulateGeolocationTimeZoneSuggestion(newYorkSuggestion)
+                .verifyTimeZoneNotChanged();
+        // Latest suggestion should be used.
+        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
+                .verifyTimeZoneChangedAndReset("America/New_York");
+    }
+
+    @Test
     public void testAddDumpable() {
         new Script()
                 .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_TIME_ZONE_DETECTION_DISABLED)
+                        CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED))
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
         AtomicBoolean dumpCalled = new AtomicBoolean(false);
@@ -733,6 +1041,15 @@
         return new ManualTimeZoneSuggestion(zoneId);
     }
 
+    private static TelephonyTimeZoneSuggestion createTelephonySuggestion(
+            int slotIndex, @MatchType int matchType, @Quality int quality, String zoneId) {
+        return new TelephonyTimeZoneSuggestion.Builder(slotIndex)
+                .setMatchType(matchType)
+                .setQuality(quality)
+                .setZoneId(zoneId)
+                .build();
+    }
+
     private static TelephonyTimeZoneSuggestion createEmptySlotIndex1Suggestion() {
         return new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX1).build();
     }
@@ -741,6 +1058,17 @@
         return new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX2).build();
     }
 
+    private static GeolocationTimeZoneSuggestion createUncertainGeoLocationSuggestion() {
+        return createGeoLocationSuggestion(null);
+    }
+
+    private static GeolocationTimeZoneSuggestion createGeoLocationSuggestion(
+            @Nullable List<String> zoneIds) {
+        GeolocationTimeZoneSuggestion suggestion = new GeolocationTimeZoneSuggestion(zoneIds);
+        suggestion.addDebugInfo("Test suggestion");
+        return suggestion;
+    }
+
     static class FakeCallback implements TimeZoneDetectorStrategyImpl.Callback {
 
         private TimeZoneCapabilities mCapabilities;
@@ -757,7 +1085,8 @@
                 TimeZoneConfiguration configuration) {
             assertEquals(userId, capabilities.getUserId());
             mCapabilities = capabilities;
-            assertTrue(configuration.isComplete());
+            assertTrue("Configuration must be complete when initializing, config=" + configuration,
+                    configuration.isComplete());
             mConfiguration.init(new UserConfiguration(userId, configuration));
         }
 
@@ -790,11 +1119,8 @@
             mConfiguration.set(new UserConfiguration(userId, newConfig));
 
             if (!newConfig.equals(oldConfig)) {
-                if (oldConfig.isAutoDetectionEnabled() != newConfig.isAutoDetectionEnabled()) {
-                    // Simulate what happens when the auto detection enabled configuration is
-                    // changed.
-                    mStrategy.handleAutoTimeZoneConfigChanged();
-                }
+                // Simulate what happens when the auto detection configuration is changed.
+                mStrategy.handleAutoTimeZoneConfigChanged();
             }
         }
 
@@ -804,6 +1130,11 @@
         }
 
         @Override
+        public boolean isGeoDetectionEnabled() {
+            return mConfiguration.getLatest().configuration.isGeoDetectionEnabled();
+        }
+
+        @Override
         public boolean isDeviceTimeZoneInitialized() {
             return mTimeZoneId.getLatest() != null;
         }
@@ -942,32 +1273,33 @@
      * supplied configuration.
      */
     private static TimeZoneCapabilities createCapabilities(
-            int userId, UserCase userRole, TimeZoneConfiguration configuration) {
-        switch (userRole) {
+            int userId, UserCase userCase, TimeZoneConfiguration configuration) {
+        switch (userCase) {
             case UNRESTRICTED: {
                 int suggestManualTimeZoneCapability = configuration.isAutoDetectionEnabled()
                         ? CAPABILITY_NOT_APPLICABLE : CAPABILITY_POSSESSED;
                 return new TimeZoneCapabilities.Builder(userId)
                         .setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
+                        .setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
                         .setSuggestManualTimeZone(suggestManualTimeZoneCapability)
                         .build();
             }
             case RESTRICTED: {
                 return new TimeZoneCapabilities.Builder(userId)
                         .setConfigureAutoDetectionEnabled(CAPABILITY_NOT_ALLOWED)
+                        .setConfigureGeoDetectionEnabled(CAPABILITY_NOT_ALLOWED)
                         .setSuggestManualTimeZone(CAPABILITY_NOT_ALLOWED)
                         .build();
-
             }
             case AUTO_DETECT_NOT_SUPPORTED: {
                 return new TimeZoneCapabilities.Builder(userId)
                         .setConfigureAutoDetectionEnabled(CAPABILITY_NOT_SUPPORTED)
+                        .setConfigureGeoDetectionEnabled(CAPABILITY_NOT_SUPPORTED)
                         .setSuggestManualTimeZone(CAPABILITY_POSSESSED)
                         .build();
-
             }
             default:
-                throw new AssertionError(userRole + " not recognized");
+                throw new AssertionError(userCase + " not recognized");
         }
     }
 
@@ -978,8 +1310,8 @@
     private class Script {
 
         Script initializeUser(
-                @UserIdInt int userId, UserCase userRole, TimeZoneConfiguration configuration) {
-            TimeZoneCapabilities capabilities = createCapabilities(userId, userRole, configuration);
+                @UserIdInt int userId, UserCase userCase, TimeZoneConfiguration configuration) {
+            TimeZoneCapabilities capabilities = createCapabilities(userId, userCase, configuration);
             mFakeCallback.initializeUser(userId, capabilities, configuration);
             return this;
         }
@@ -989,27 +1321,24 @@
             return this;
         }
 
-        Script simulateAutoTimeZoneDetectionEnabled(@UserIdInt int userId, boolean enabled) {
-            TimeZoneConfiguration configuration = new TimeZoneConfiguration.Builder()
-                    .setAutoDetectionEnabled(enabled)
-                    .build();
-            return simulateUpdateConfiguration(userId, configuration);
-        }
-
         /**
-         * Simulates the time zone detection strategy receiving an updated configuration.
+         * Simulates the time zone detection strategy receiving an updated configuration and checks
+         * the return value.
          */
         Script simulateUpdateConfiguration(
-                @UserIdInt int userId, TimeZoneConfiguration configuration) {
-            mTimeZoneDetectorStrategy.updateConfiguration(userId, configuration);
+                @UserIdInt int userId, TimeZoneConfiguration configuration,
+                boolean expectedResult) {
+            assertEquals(expectedResult,
+                    mTimeZoneDetectorStrategy.updateConfiguration(userId, configuration));
             return this;
         }
 
         /**
-         * Simulates the time zone detection strategy receiving a telephony-originated suggestion.
+         * Simulates the time zone detection strategy receiving a geolocation-originated
+         * suggestion.
          */
-        Script simulateTelephonyTimeZoneSuggestion(TelephonyTimeZoneSuggestion timeZoneSuggestion) {
-            mTimeZoneDetectorStrategy.suggestTelephonyTimeZone(timeZoneSuggestion);
+        Script simulateGeolocationTimeZoneSuggestion(GeolocationTimeZoneSuggestion suggestion) {
+            mTimeZoneDetectorStrategy.suggestGeolocationTimeZone(suggestion);
             return this;
         }
 
@@ -1024,13 +1353,26 @@
             return this;
         }
 
+        /**
+         * Simulates the time zone detection strategy receiving a telephony-originated suggestion.
+         */
+        Script simulateTelephonyTimeZoneSuggestion(TelephonyTimeZoneSuggestion timeZoneSuggestion) {
+            mTimeZoneDetectorStrategy.suggestTelephonyTimeZone(timeZoneSuggestion);
+            return this;
+        }
+
+        /**
+         * Confirms that the device's time zone has not been set by previous actions since the test
+         * state was last reset.
+         */
         Script verifyTimeZoneNotChanged() {
             mFakeCallback.assertTimeZoneNotChanged();
             return this;
         }
 
-        Script verifyTimeZoneChangedAndReset(TelephonyTimeZoneSuggestion suggestion) {
-            mFakeCallback.assertTimeZoneChangedTo(suggestion.getZoneId());
+        /** Verifies the device's time zone has been set and clears change tracking history. */
+        Script verifyTimeZoneChangedAndReset(String zoneId) {
+            mFakeCallback.assertTimeZoneChangedTo(zoneId);
             mFakeCallback.commitAllChanges();
             return this;
         }
@@ -1041,6 +1383,12 @@
             return this;
         }
 
+        Script verifyTimeZoneChangedAndReset(TelephonyTimeZoneSuggestion suggestion) {
+            mFakeCallback.assertTimeZoneChangedTo(suggestion.getZoneId());
+            mFakeCallback.commitAllChanges();
+            return this;
+        }
+
         /**
          * Verifies that the configuration has been changed to the expected value.
          */
@@ -1120,4 +1468,8 @@
             mOnConfigurationChangedCalled = false;
         }
     }
+
+    private static <T> List<T> list(T... values) {
+        return Arrays.asList(values);
+    }
 }
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/uiservicestests/src/com/android/server/slice/PackageMatchingCacheTest.java b/services/tests/uiservicestests/src/com/android/server/slice/PackageMatchingCacheTest.java
deleted file mode 100644
index f6c854e..0000000
--- a/services/tests/uiservicestests/src/com/android/server/slice/PackageMatchingCacheTest.java
+++ /dev/null
@@ -1,76 +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.server.slice;
-
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper.RunWithLooper;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.server.UiServiceTestCase;
-import com.android.server.slice.SliceManagerService.PackageMatchingCache;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.function.Supplier;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
-public class PackageMatchingCacheTest extends UiServiceTestCase {
-
-    private final Supplier<String> supplier = mock(Supplier.class);
-    private final PackageMatchingCache cache = new PackageMatchingCache(supplier);
-
-    @Test
-    public void testNulls() {
-        // Doesn't get for a null input
-        cache.matches(null);
-        verify(supplier, never()).get();
-
-        // Gets once valid input in sent.
-        cache.matches("");
-        verify(supplier).get();
-    }
-
-    @Test
-    public void testCaching() {
-        when(supplier.get()).thenReturn("ret.pkg");
-
-        assertTrue(cache.matches("ret.pkg"));
-        assertTrue(cache.matches("ret.pkg"));
-        assertTrue(cache.matches("ret.pkg"));
-
-        verify(supplier, times(1)).get();
-    }
-
-    @Test
-    public void testGetOnFailure() {
-        when(supplier.get()).thenReturn("ret.pkg");
-        assertTrue(cache.matches("ret.pkg"));
-
-        when(supplier.get()).thenReturn("other.pkg");
-        assertTrue(cache.matches("other.pkg"));
-        verify(supplier, times(2)).get();
-    }
-}
diff --git a/services/tests/uiservicestests/src/com/android/server/slice/SliceManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/slice/SliceManagerServiceTest.java
index a443695..cf1c36c 100644
--- a/services/tests/uiservicestests/src/com/android/server/slice/SliceManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/slice/SliceManagerServiceTest.java
@@ -90,8 +90,6 @@
 
     @Test
     public void testAddPinCreatesPinned() throws RemoteException {
-        doReturn("pkg").when(mService).getDefaultHome(anyInt());
-
         mService.pinSlice("pkg", TEST_URI, EMPTY_SPECS, mToken);
         mService.pinSlice("pkg", TEST_URI, EMPTY_SPECS, mToken);
         verify(mService, times(1)).createPinnedSlice(eq(maybeAddUserId(TEST_URI, 0)), anyString());
@@ -99,8 +97,6 @@
 
     @Test
     public void testRemovePinDestroysPinned() throws RemoteException {
-        doReturn("pkg").when(mService).getDefaultHome(anyInt());
-
         mService.pinSlice("pkg", TEST_URI, EMPTY_SPECS, mToken);
 
         when(mCreatedSliceState.unpin(eq("pkg"), eq(mToken))).thenReturn(false);
diff --git a/services/tests/wmtests/Android.bp b/services/tests/wmtests/Android.bp
index b3d75d3..4ca5b9e 100644
--- a/services/tests/wmtests/Android.bp
+++ b/services/tests/wmtests/Android.bp
@@ -2,15 +2,37 @@
 // Build WmTests package
 //########################################################################
 
+// Include all test java files.
+filegroup {
+    name: "wmtests-sources",
+    srcs: [
+        "src/**/*.java",
+    ],
+}
+
+genrule {
+    name: "wmtests.protologsrc",
+    srcs: [
+        ":protolog-groups",
+        ":wmtests-sources",
+    ],
+    tools: ["protologtool"],
+    cmd: "$(location protologtool) transform-protolog-calls " +
+      "--protolog-class com.android.internal.protolog.common.ProtoLog " +
+      "--protolog-impl-class com.android.internal.protolog.ProtoLogImpl " +
+      "--protolog-cache-class 'com.android.server.wm.ProtoLogCache' " +
+      "--loggroups-class com.android.internal.protolog.ProtoLogGroup " +
+      "--loggroups-jar $(location :protolog-groups) " +
+      "--output-srcjar $(out) " +
+      "$(locations :wmtests-sources)",
+    out: ["wmtests.protolog.srcjar"],
+}
+
 android_test {
     name: "WmTests",
 
     // We only want this apk build for tests.
-
-    // Include all test java files.
-    srcs: [
-        "src/**/*.java",
-    ],
+    srcs: [":wmtests.protologsrc"],
 
     static_libs: [
         "frameworks-base-testutils",
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 6061f13..f860e17 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -1506,7 +1506,7 @@
         final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                 WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);
         params.width = params.height = WindowManager.LayoutParams.MATCH_PARENT;
-        final WindowTestUtils.TestWindowState w = new WindowTestUtils.TestWindowState(
+        final TestWindowState w = new TestWindowState(
                 mAtm.mWindowManager, mock(Session.class), new TestIWindow(), params, mActivity);
         mActivity.addWindow(w);
 
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..1e95aba 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java
@@ -192,6 +192,8 @@
                 virtualDisplay.getDisplay().getDisplayId(), unresizableActivity.info);
 
         assertThat(allowed).isTrue();
+
+        virtualDisplay.release();
     }
 
     @Test
@@ -206,6 +208,8 @@
                 virtualDisplay.getDisplay().getDisplayId(), unresizableActivity.info);
 
         assertThat(allowed).isFalse();
+
+        virtualDisplay.release();
     }
 
     private VirtualDisplay createVirtualDisplay(boolean trusted) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index 2e988af..5676100 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -225,5 +225,27 @@
 
         mockSession.finishMocking();
     }
+
+    @Test
+    public void testResumeNextActivityOnCrashedAppDied() {
+        mSupervisor.beginDeferResume();
+        final ActivityRecord homeActivity = new ActivityBuilder(mAtm)
+                .setTask(mRootWindowContainer.getDefaultTaskDisplayArea().getOrCreateRootHomeTask())
+                .build();
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        mSupervisor.endDeferResume();
+        // Assume the activity is finishing and hidden because it was crashed.
+        activity.finishing = true;
+        activity.mVisibleRequested = false;
+        activity.setVisible(false);
+        activity.getRootTask().mPausingActivity = activity;
+        homeActivity.setState(Task.ActivityState.PAUSED, "test");
+
+        // Even the visibility states are invisible, the next activity should be resumed because
+        // the crashed activity was pausing.
+        mAtm.mInternal.handleAppDied(activity.app, false /* restarting */,
+                null /* finishInstrumentationCallback */);
+        assertEquals(Task.ActivityState.RESUMED, homeActivity.getState());
+    }
 }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index 7adcead..1b21920 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -133,10 +133,10 @@
         // [DisplayContent] -+- [TaskStack1] - [Task1] - [ActivityRecord1] (opening, visible)
         //                   +- [TaskStack2] - [Task2] - [ActivityRecord2] (closing, invisible)
         final Task stack1 = createTaskStackOnDisplay(mDisplayContent);
-        final ActivityRecord activity1 = WindowTestUtils.createTestActivityRecord(stack1);
+        final ActivityRecord activity1 = createTestActivityRecord(stack1);
 
         final Task stack2 = createTaskStackOnDisplay(mDisplayContent);
-        final ActivityRecord activity2 = WindowTestUtils.createTestActivityRecord(stack2);
+        final ActivityRecord activity2 = createTestActivityRecord(stack2);
         activity2.setVisible(false);
         activity2.mVisibleRequested = false;
 
@@ -162,13 +162,13 @@
         // [DisplayContent] -+- [TaskStack1] - [Task1] - [ActivityRecord1] (closing, invisible)
         //                   +- [TaskStack2] - [Task2] - [ActivityRecord2] (opening, visible)
         final Task stack1 = createTaskStackOnDisplay(mDisplayContent);
-        final ActivityRecord activity1 = WindowTestUtils.createTestActivityRecord(stack1);
+        final ActivityRecord activity1 = createTestActivityRecord(stack1);
         activity1.setVisible(true);
         activity1.mVisibleRequested = true;
         activity1.mRequestForceTransition = true;
 
         final Task stack2 = createTaskStackOnDisplay(mDisplayContent);
-        final ActivityRecord activity2 = WindowTestUtils.createTestActivityRecord(stack2);
+        final ActivityRecord activity2 = createTestActivityRecord(stack2);
         activity2.setVisible(false);
         activity2.mVisibleRequested = false;
         activity2.mRequestForceTransition = true;
@@ -193,10 +193,10 @@
     @Test
     public void testGetAnimationTargets_exitingBeforeTransition() {
         // Create another non-empty task so the animation target won't promote to task display area.
-        WindowTestUtils.createTestActivityRecord(
+        createTestActivityRecord(
                 mDisplayContent.getDefaultTaskDisplayArea().getOrCreateRootHomeTask());
         final Task stack = createTaskStackOnDisplay(mDisplayContent);
-        final ActivityRecord activity = WindowTestUtils.createTestActivityRecord(stack);
+        final ActivityRecord activity = createTestActivityRecord(stack);
         activity.setVisible(false);
         activity.mIsExiting = true;
 
@@ -218,20 +218,20 @@
         //                   +- [TaskStack2] - [Task2] - [ActivityRecord2] (closing, invisible)
         //                                                      +- [AppWindow2] (being-replaced)
         final Task stack1 = createTaskStackOnDisplay(mDisplayContent);
-        final ActivityRecord activity1 = WindowTestUtils.createTestActivityRecord(stack1);
+        final ActivityRecord activity1 = createTestActivityRecord(stack1);
         final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
                 TYPE_BASE_APPLICATION);
         attrs.setTitle("AppWindow1");
-        final WindowTestUtils.TestWindowState appWindow1 = createWindowState(attrs, activity1);
+        final TestWindowState appWindow1 = createWindowState(attrs, activity1);
         appWindow1.mWillReplaceWindow = true;
         activity1.addWindow(appWindow1);
 
         final Task stack2 = createTaskStackOnDisplay(mDisplayContent);
-        final ActivityRecord activity2 = WindowTestUtils.createTestActivityRecord(stack2);
+        final ActivityRecord activity2 = createTestActivityRecord(stack2);
         activity2.setVisible(false);
         activity2.mVisibleRequested = false;
         attrs.setTitle("AppWindow2");
-        final WindowTestUtils.TestWindowState appWindow2 = createWindowState(attrs, activity2);
+        final TestWindowState appWindow2 = createWindowState(attrs, activity2);
         appWindow2.mWillReplaceWindow = true;
         activity2.addWindow(appWindow2);
 
@@ -262,21 +262,17 @@
         //                                              +- [ActivityRecord4] (invisible)
         final Task stack1 = createTaskStackOnDisplay(mDisplayContent);
         final Task task1 = createTaskInStack(stack1, 0 /* userId */);
-        final ActivityRecord activity1 = WindowTestUtils.createActivityRecordInTask(
-                mDisplayContent, task1);
+        final ActivityRecord activity1 = createActivityRecordInTask(mDisplayContent, task1);
         activity1.setVisible(false);
         activity1.mVisibleRequested = true;
-        final ActivityRecord activity2 = WindowTestUtils.createActivityRecordInTask(
-                mDisplayContent, task1);
+        final ActivityRecord activity2 = createActivityRecordInTask(mDisplayContent, task1);
         activity2.setVisible(false);
         activity2.mVisibleRequested = false;
 
         final Task stack2 = createTaskStackOnDisplay(mDisplayContent);
         final Task task2 = createTaskInStack(stack2, 0 /* userId */);
-        final ActivityRecord activity3 = WindowTestUtils.createActivityRecordInTask(
-                mDisplayContent, task2);
-        final ActivityRecord activity4 = WindowTestUtils.createActivityRecordInTask(
-                mDisplayContent, task2);
+        final ActivityRecord activity3 = createActivityRecordInTask(mDisplayContent, task2);
+        final ActivityRecord activity4 = createActivityRecordInTask(mDisplayContent, task2);
         activity4.setVisible(false);
         activity4.mVisibleRequested = false;
 
@@ -303,12 +299,10 @@
         //                                          +- [ActivityRecord2] (closing, visible)
         final Task stack = createTaskStackOnDisplay(mDisplayContent);
         final Task task = createTaskInStack(stack, 0 /* userId */);
-        final ActivityRecord activity1 = WindowTestUtils.createActivityRecordInTask(
-                mDisplayContent, task);
+        final ActivityRecord activity1 = createActivityRecordInTask(mDisplayContent, task);
         activity1.setVisible(false);
         activity1.mVisibleRequested = true;
-        final ActivityRecord activity2 = WindowTestUtils.createActivityRecordInTask(
-                mDisplayContent, task);
+        final ActivityRecord activity2 = createActivityRecordInTask(mDisplayContent, task);
 
         final ArraySet<ActivityRecord> opening = new ArraySet<>();
         opening.add(activity1);
@@ -337,22 +331,18 @@
 
         final Task stack1 = createTaskStackOnDisplay(mDisplayContent);
         final Task task1 = createTaskInStack(stack1, 0 /* userId */);
-        final ActivityRecord activity1 = WindowTestUtils.createActivityRecordInTask(
-                mDisplayContent, task1);
+        final ActivityRecord activity1 = createActivityRecordInTask(mDisplayContent, task1);
         activity1.setVisible(false);
         activity1.mVisibleRequested = true;
         activity1.setOccludesParent(false);
 
-        final ActivityRecord activity2 = WindowTestUtils.createActivityRecordInTask(
-                mDisplayContent, task1);
+        final ActivityRecord activity2 = createActivityRecordInTask(mDisplayContent, task1);
 
         final Task stack2 = createTaskStackOnDisplay(mDisplayContent);
         final Task task2 = createTaskInStack(stack2, 0 /* userId */);
-        final ActivityRecord activity3 = WindowTestUtils.createActivityRecordInTask(
-                mDisplayContent, task2);
+        final ActivityRecord activity3 = createActivityRecordInTask(mDisplayContent, task2);
         activity3.setOccludesParent(false);
-        final ActivityRecord activity4 = WindowTestUtils.createActivityRecordInTask(
-                mDisplayContent, task2);
+        final ActivityRecord activity4 = createActivityRecordInTask(mDisplayContent, task2);
 
         final ArraySet<ActivityRecord> opening = new ArraySet<>();
         opening.add(activity1);
@@ -381,24 +371,20 @@
 
         final Task stack1 = createTaskStackOnDisplay(mDisplayContent);
         final Task task1 = createTaskInStack(stack1, 0 /* userId */);
-        final ActivityRecord activity1 = WindowTestUtils.createActivityRecordInTask(
-                mDisplayContent, task1);
+        final ActivityRecord activity1 = createActivityRecordInTask(mDisplayContent, task1);
         activity1.setVisible(false);
         activity1.mVisibleRequested = true;
         activity1.setOccludesParent(false);
 
-        final ActivityRecord activity2 = WindowTestUtils.createActivityRecordInTask(
-                mDisplayContent, task1);
+        final ActivityRecord activity2 = createActivityRecordInTask(mDisplayContent, task1);
         activity2.setVisible(false);
         activity2.mVisibleRequested = true;
 
         final Task stack2 = createTaskStackOnDisplay(mDisplayContent);
         final Task task2 = createTaskInStack(stack2, 0 /* userId */);
-        final ActivityRecord activity3 = WindowTestUtils.createActivityRecordInTask(
-                mDisplayContent, task2);
+        final ActivityRecord activity3 = createActivityRecordInTask(mDisplayContent, task2);
         activity3.setOccludesParent(false);
-        final ActivityRecord activity4 = WindowTestUtils.createActivityRecordInTask(
-                mDisplayContent, task2);
+        final ActivityRecord activity4 = createActivityRecordInTask(mDisplayContent, task2);
 
         final ArraySet<ActivityRecord> opening = new ArraySet<>();
         opening.add(activity1);
@@ -425,13 +411,11 @@
         //                                 +- [Task2] - [ActivityRecord2] (closing, visible)
         final Task stack = createTaskStackOnDisplay(mDisplayContent);
         final Task task1 = createTaskInStack(stack, 0 /* userId */);
-        final ActivityRecord activity1 = WindowTestUtils.createActivityRecordInTask(
-                mDisplayContent, task1);
+        final ActivityRecord activity1 = createActivityRecordInTask(mDisplayContent, task1);
         activity1.setVisible(false);
         activity1.mVisibleRequested = true;
         final Task task2 = createTaskInStack(stack, 0 /* userId */);
-        final ActivityRecord activity2 = WindowTestUtils.createActivityRecordInTask(
-                mDisplayContent, task2);
+        final ActivityRecord activity2 = createActivityRecordInTask(mDisplayContent, task2);
 
         final ArraySet<ActivityRecord> opening = new ArraySet<>();
         opening.add(activity1);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index 17914e7..ee030af 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -151,8 +151,7 @@
 
         final Task stack1 = createTaskStackOnDisplay(dc1);
         final Task task1 = createTaskInStack(stack1, 0 /* userId */);
-        final ActivityRecord activity1 =
-                WindowTestUtils.createTestActivityRecord(dc1);
+        final ActivityRecord activity1 = createTestActivityRecord(dc1);
         task1.addChild(activity1, 0);
 
         // Simulate same app is during opening / closing transition set stage.
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
index 888935e..085b8de 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
@@ -92,7 +92,7 @@
     public void setUp() throws Exception {
         mStack = createTaskStackOnDisplay(mDisplayContent);
         mTask = createTaskInStack(mStack, 0 /* userId */);
-        mActivity = WindowTestUtils.createTestActivityRecord(mDisplayContent);
+        mActivity = createTestActivityRecord(mDisplayContent);
 
         mTask.addChild(mActivity, 0);
     }
@@ -165,7 +165,7 @@
         final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
                 TYPE_BASE_APPLICATION);
         attrs.setTitle("AppWindow");
-        final WindowTestUtils.TestWindowState appWindow = createWindowState(attrs, mActivity);
+        final TestWindowState appWindow = createWindowState(attrs, mActivity);
         mActivity.addWindow(appWindow);
 
         // Set initial orientation and update.
@@ -198,7 +198,7 @@
         final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
                 TYPE_BASE_APPLICATION);
         attrs.setTitle("RotationByPolicy");
-        final WindowTestUtils.TestWindowState appWindow = createWindowState(attrs, mActivity);
+        final TestWindowState appWindow = createWindowState(attrs, mActivity);
         mActivity.addWindow(appWindow);
 
         // Set initial orientation and update.
@@ -244,7 +244,7 @@
                 TYPE_BASE_APPLICATION);
         attrs.flags |= FLAG_SHOW_WHEN_LOCKED | FLAG_DISMISS_KEYGUARD;
         attrs.setTitle("AppWindow");
-        final WindowTestUtils.TestWindowState appWindow = createWindowState(attrs, mActivity);
+        final TestWindowState appWindow = createWindowState(attrs, mActivity);
 
         // Add window with show when locked flag
         mActivity.addWindow(appWindow);
@@ -307,7 +307,7 @@
         assertEquals(Configuration.ORIENTATION_PORTRAIT, displayConfig.orientation);
         assertEquals(Configuration.ORIENTATION_PORTRAIT, activityConfig.orientation);
 
-        final ActivityRecord topActivity = WindowTestUtils.createTestActivityRecord(mStack);
+        final ActivityRecord topActivity = createTestActivityRecord(mStack);
         topActivity.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
 
         assertEquals(Configuration.ORIENTATION_LANDSCAPE, displayConfig.orientation);
@@ -490,8 +490,7 @@
     }
 
     private ActivityRecord createTestActivityRecordForGivenTask(Task task) {
-        final ActivityRecord activity =
-                WindowTestUtils.createTestActivityRecord(mDisplayContent);
+        final ActivityRecord activity = createTestActivityRecord(mDisplayContent);
         task.addChild(activity, 0);
         waitUntilHandlersIdle();
         return activity;
@@ -562,7 +561,7 @@
     public void testHasStartingWindow() {
         final WindowManager.LayoutParams attrs =
                 new WindowManager.LayoutParams(TYPE_APPLICATION_STARTING);
-        final WindowTestUtils.TestWindowState startingWindow = createWindowState(attrs, mActivity);
+        final TestWindowState startingWindow = createWindowState(attrs, mActivity);
         mActivity.startingDisplayed = true;
         mActivity.addWindow(startingWindow);
         assertTrue("Starting window should be present", mActivity.hasStartingWindow());
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 0cc6159..d54b4a0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -326,7 +326,7 @@
         assertEquals(dc, stack.getDisplayContent());
 
         final Task task = createTaskInStack(stack, 0 /* userId */);
-        final ActivityRecord activity = WindowTestUtils.createTestActivityRecord(dc);
+        final ActivityRecord activity = createTestActivityRecord(dc);
         task.addChild(activity, 0);
         assertEquals(dc, task.getDisplayContent());
         assertEquals(dc, activity.getDisplayContent());
@@ -397,16 +397,14 @@
         // Add stack with activity.
         final Task stack0 = createTaskStackOnDisplay(dc0);
         final Task task0 = createTaskInStack(stack0, 0 /* userId */);
-        final ActivityRecord activity =
-                WindowTestUtils.createTestActivityRecord(dc0);
+        final ActivityRecord activity = createTestActivityRecord(dc0);
         task0.addChild(activity, 0);
         dc0.configureDisplayPolicy();
         assertNotNull(dc0.mTapDetector);
 
         final Task stack1 = createTaskStackOnDisplay(dc1);
         final Task task1 = createTaskInStack(stack1, 0 /* userId */);
-        final ActivityRecord activity1 =
-                WindowTestUtils.createTestActivityRecord(dc0);
+        final ActivityRecord activity1 = createTestActivityRecord(dc0);
         task1.addChild(activity1, 0);
         dc1.configureDisplayPolicy();
         assertNotNull(dc1.mTapDetector);
@@ -1296,7 +1294,7 @@
         final ActivityRecord pinnedActivity = createActivityRecord(displayContent,
                 WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
         final Task pinnedTask = pinnedActivity.getRootTask();
-        final ActivityRecord homeActivity = WindowTestUtils.createTestActivityRecord(
+        final ActivityRecord homeActivity = createTestActivityRecord(
                 displayContent.getDefaultTaskDisplayArea().getOrCreateRootHomeTask());
         if (displayConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
             homeActivity.setOrientation(SCREEN_ORIENTATION_PORTRAIT);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index 4ea5b97..0675c6d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -48,12 +48,14 @@
 import static android.view.WindowManagerPolicyConstants.ALT_BAR_RIGHT;
 import static android.view.WindowManagerPolicyConstants.ALT_BAR_TOP;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 
 import static org.hamcrest.Matchers.is;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertThat;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
@@ -874,6 +876,19 @@
     }
 
     @Test
+    public void testFixedRotationInsetsSourceFrame() {
+        mDisplayPolicy.beginLayoutLw(mFrames, mDisplayContent.getConfiguration().uiMode);
+        doReturn((mDisplayContent.getRotation() + 1) % 4).when(mDisplayContent)
+                .rotationForActivityInDifferentOrientation(eq(mWindow.mActivityRecord));
+        final Rect frame = mWindow.getInsetsState().getSource(ITYPE_STATUS_BAR).getFrame();
+        mDisplayContent.rotateInDifferentOrientationIfNeeded(mWindow.mActivityRecord);
+        final Rect rotatedFrame = mWindow.getInsetsState().getSource(ITYPE_STATUS_BAR).getFrame();
+
+        assertEquals(DISPLAY_WIDTH, frame.width());
+        assertEquals(DISPLAY_HEIGHT, rotatedFrame.width());
+    }
+
+    @Test
     public void testScreenDecorWindows() {
         final WindowState decorWindow = spy(
                 createWindow(null, TYPE_APPLICATION_OVERLAY, "decorWindow"));
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index e18d93d..4536997 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -95,8 +95,7 @@
      * Creates a window state which can be used as a drop target.
      */
     private WindowState createDropTargetWindow(String name, int ownerId) {
-        final ActivityRecord activity = WindowTestUtils.createTestActivityRecord(
-                mDisplayContent);
+        final ActivityRecord activity = createTestActivityRecord(mDisplayContent);
         final Task stack = createTaskStackOnDisplay(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, mDisplayContent);
         final Task task = createTaskInStack(stack, ownerId);
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index 5e83e66..085230d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -28,6 +28,9 @@
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -40,6 +43,7 @@
 
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
+import android.util.IntArray;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
 import android.view.test.InsetsModeSession;
@@ -328,6 +332,27 @@
         assertNull(getController().getControlsForDispatch(app));
     }
 
+    @Test
+    public void testTransientVisibilityOfFixedRotationState() {
+        final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
+        final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+        final InsetsSourceProvider provider = getController().getSourceProvider(ITYPE_STATUS_BAR);
+        provider.setWindow(statusBar, null, null);
+
+        final InsetsState rotatedState = new InsetsState(app.getInsetsState(),
+                true /* copySources */);
+        spyOn(app.mToken);
+        doReturn(rotatedState).when(app.mToken).getFixedRotationTransformInsetsState();
+        assertTrue(rotatedState.getSource(ITYPE_STATUS_BAR).isVisible());
+
+        provider.getSource().setVisible(false);
+        mDisplayContent.getInsetsPolicy().showTransient(
+                IntArray.wrap(new int[] { ITYPE_STATUS_BAR }));
+
+        assertTrue(mDisplayContent.getInsetsPolicy().isTransient(ITYPE_STATUS_BAR));
+        assertFalse(app.getInsetsState().getSource(ITYPE_STATUS_BAR).isVisible());
+    }
+
     private InsetsStateController getController() {
         return mDisplayContent.getInsetsStateController();
     }
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 d1510cf..578a43c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java
@@ -25,9 +25,12 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.server.protolog.ProtoLogImpl;
+import com.android.internal.protolog.ProtoLogGroup;
+import com.android.internal.protolog.ProtoLogImpl;
+import com.android.internal.protolog.common.ProtoLog;
 
 import org.junit.After;
+import org.junit.Ignore;
 import org.junit.Test;
 
 /**
@@ -41,20 +44,25 @@
         ProtoLogImpl.setSingleInstance(null);
     }
 
+    @Ignore("b/163095037")
     @Test
     public void testProtoLogToolIntegration() {
         ProtoLogImpl mockedProtoLog = mock(ProtoLogImpl.class);
-        runWith(mockedProtoLog, () -> {
-            ProtoLogGroup.testProtoLog();
-        });
+        runWith(mockedProtoLog, this::testProtoLog);
         verify(mockedProtoLog).log(eq(ProtoLogImpl.LogLevel.ERROR), eq(ProtoLogGroup.TEST_GROUP),
                 anyInt(), eq(0b0010101001010111),
-                eq(ProtoLogGroup.TEST_GROUP.isLogToLogcat()
+                eq(com.android.internal.protolog.ProtoLogGroup.TEST_GROUP.isLogToLogcat()
                         ? "Test completed successfully: %b %d %o %x %e %g %f %% %s"
                         : null),
                 eq(new Object[]{true, 1L, 2L, 3L, 0.4, 0.5, 0.6, "ok"}));
     }
 
+    private void testProtoLog() {
+        ProtoLog.e(ProtoLogGroup.TEST_GROUP,
+                "Test completed successfully: %b %d %o %x %e %g %f %% %s.",
+                true, 1, 2, 3, 0.4, 0.5, 0.6, "ok");
+    }
+
     /**
      * Starts protolog for the duration of {@code runnable}, with a ProtoLogImpl instance installed.
      */
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 0e1d4dc..982e469 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -568,7 +568,9 @@
     private static WindowState addWindowToActivity(ActivityRecord activity) {
         final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
         params.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-        final WindowTestUtils.TestWindowState w = new WindowTestUtils.TestWindowState(
+        params.setFitInsetsSides(0);
+        params.setFitInsetsTypes(0);
+        final TestWindowState w = new TestWindowState(
                 activity.mWmService, mock(Session.class), new TestIWindow(), params, activity);
         WindowTestsBase.makeWindowVisible(w);
         w.mWinAnimator.mDrawState = WindowStateAnimator.HAS_DRAWN;
@@ -581,7 +583,7 @@
         doReturn(true).when(displayPolicy).hasStatusBar();
         displayPolicy.onConfigurationChanged();
 
-        final WindowTestUtils.TestWindowToken token = WindowTestUtils.createTestWindowToken(
+        final TestWindowToken token = createTestWindowToken(
                 WindowManager.LayoutParams.TYPE_STATUS_BAR, displayContent);
         final WindowManager.LayoutParams attrs =
                 new WindowManager.LayoutParams(WindowManager.LayoutParams.TYPE_STATUS_BAR);
@@ -589,7 +591,7 @@
         attrs.layoutInDisplayCutoutMode =
                 WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
         attrs.setFitInsetsTypes(0 /* types */);
-        final WindowTestUtils.TestWindowState statusBar = new WindowTestUtils.TestWindowState(
+        final TestWindowState statusBar = new TestWindowState(
                 displayContent.mWmService, mock(Session.class), new TestIWindow(), attrs, token);
         token.addWindow(statusBar);
         statusBar.setRequestedSize(displayContent.mBaseDisplayWidth,
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
index 3492556..260f1e9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
@@ -76,8 +76,7 @@
         // Stack should contain visible app window to be considered visible.
         final Task pinnedTask = createTaskInStack(mPinnedStack, 0 /* userId */);
         assertFalse(mPinnedStack.isVisible());
-        final ActivityRecord pinnedApp =
-                WindowTestUtils.createTestActivityRecord(mDisplayContent);
+        final ActivityRecord pinnedApp = createTestActivityRecord(mDisplayContent);
         pinnedTask.addChild(pinnedApp, 0 /* addPos */);
         assertTrue(mPinnedStack.isVisible());
     }
@@ -92,7 +91,7 @@
         final Task stack = createTaskStackOnDisplay(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, mDisplayContent);
         final Task task = createTaskInStack(stack, 0 /* userId */);
-        final ActivityRecord activity = WindowTestUtils.createTestActivityRecord(mDisplayContent);
+        final ActivityRecord activity = createTestActivityRecord(mDisplayContent);
         task.addChild(activity, 0 /* addPos */);
         final TaskDisplayArea taskDisplayArea = activity.getDisplayArea();
         activity.mNeedsAnimationBoundsLayer = true;
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java
index 205b842..7cf30c0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java
@@ -85,14 +85,12 @@
     public void testClosingAppDifferentStackOrientation() {
         final Task stack = createTaskStackOnDisplay(mDisplayContent);
         final Task task1 = createTaskInStack(stack, 0 /* userId */);
-        ActivityRecord activity1 =
-                WindowTestUtils.createTestActivityRecord(mDisplayContent);
+        ActivityRecord activity1 = createTestActivityRecord(mDisplayContent);
         task1.addChild(activity1, 0);
         activity1.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
 
         final Task task2 = createTaskInStack(stack, 1 /* userId */);
-        ActivityRecord activity2=
-                WindowTestUtils.createTestActivityRecord(mDisplayContent);
+        ActivityRecord activity2 = createTestActivityRecord(mDisplayContent);
         task2.addChild(activity2, 0);
         activity2.setOrientation(SCREEN_ORIENTATION_PORTRAIT);
 
@@ -105,14 +103,12 @@
     public void testMoveTaskToBackDifferentStackOrientation() {
         final Task stack = createTaskStackOnDisplay(mDisplayContent);
         final Task task1 = createTaskInStack(stack, 0 /* userId */);
-        ActivityRecord activity1 =
-                WindowTestUtils.createTestActivityRecord(mDisplayContent);
+        ActivityRecord activity1 = createTestActivityRecord(mDisplayContent);
         task1.addChild(activity1, 0);
         activity1.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
 
         final Task task2 = createTaskInStack(stack, 1 /* userId */);
-        ActivityRecord activity2 =
-                WindowTestUtils.createTestActivityRecord(mDisplayContent);
+        ActivityRecord activity2 = createTestActivityRecord(mDisplayContent);
         task2.addChild(activity2, 0);
         activity2.setOrientation(SCREEN_ORIENTATION_PORTRAIT);
         assertEquals(SCREEN_ORIENTATION_PORTRAIT, stack.getOrientation());
@@ -221,7 +217,7 @@
     public void testActivityAndTaskGetsProperType() {
         final Task stack = createTaskStackOnDisplay(mDisplayContent);
         final Task task1 = createTaskInStack(stack, 0 /* userId */);
-        ActivityRecord activity1 = WindowTestUtils.createTestActivityRecord(mDisplayContent);
+        ActivityRecord activity1 = createTestActivityRecord(mDisplayContent);
 
         // First activity should become standard
         task1.addChild(activity1, 0);
@@ -229,7 +225,7 @@
         assertEquals(WindowConfiguration.ACTIVITY_TYPE_STANDARD, task1.getActivityType());
 
         // Second activity should also become standard
-        ActivityRecord activity2 = WindowTestUtils.createTestActivityRecord(mDisplayContent);
+        ActivityRecord activity2 = createTestActivityRecord(mDisplayContent);
         task1.addChild(activity2, WindowContainer.POSITION_TOP);
         assertEquals(WindowConfiguration.ACTIVITY_TYPE_STANDARD, activity2.getActivityType());
         assertEquals(WindowConfiguration.ACTIVITY_TYPE_STANDARD, task1.getActivityType());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 92b6e6e..ace0400 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -56,8 +56,7 @@
     public void testRemoveContainer() {
         final Task stackController1 = createTaskStackOnDisplay(mDisplayContent);
         final Task task = createTaskInStack(stackController1, 0 /* userId */);
-        final ActivityRecord activity =
-                WindowTestUtils.createActivityRecordInTask(mDisplayContent, task);
+        final ActivityRecord activity = createActivityRecordInTask(mDisplayContent, task);
 
         task.removeIfPossible();
         // Assert that the container was removed.
@@ -70,8 +69,7 @@
     public void testRemoveContainer_deferRemoval() {
         final Task stackController1 = createTaskStackOnDisplay(mDisplayContent);
         final Task task = createTaskInStack(stackController1, 0 /* userId */);
-        final ActivityRecord activity =
-                WindowTestUtils.createActivityRecordInTask(mDisplayContent, task);
+        final ActivityRecord activity = createActivityRecordInTask(mDisplayContent, task);
 
         doReturn(true).when(task).shouldDeferRemoval();
 
@@ -153,10 +151,8 @@
     public void testIsInStack() {
         final Task task1 = createTaskStackOnDisplay(mDisplayContent);
         final Task task2 = createTaskStackOnDisplay(mDisplayContent);
-        final ActivityRecord activity1 =
-                WindowTestUtils.createActivityRecordInTask(mDisplayContent, task1);
-        final ActivityRecord activity2 =
-                WindowTestUtils.createActivityRecordInTask(mDisplayContent, task2);
+        final ActivityRecord activity1 = createActivityRecordInTask(mDisplayContent, task1);
+        final ActivityRecord activity2 = createActivityRecordInTask(mDisplayContent, task2);
         assertEquals(activity1, task1.isInTask(activity1));
         assertNull(task1.isInTask(activity2));
     }
@@ -165,12 +161,9 @@
     public void testRemoveChildForOverlayTask() {
         final Task task = createTaskStackOnDisplay(mDisplayContent);
         final int taskId = task.mTaskId;
-        final ActivityRecord activity1 =
-                WindowTestUtils.createActivityRecordInTask(mDisplayContent, task);
-        final ActivityRecord activity2 =
-                WindowTestUtils.createActivityRecordInTask(mDisplayContent, task);
-        final ActivityRecord activity3 =
-                WindowTestUtils.createActivityRecordInTask(mDisplayContent, task);
+        final ActivityRecord activity1 = createActivityRecordInTask(mDisplayContent, task);
+        final ActivityRecord activity2 = createActivityRecordInTask(mDisplayContent, task);
+        final ActivityRecord activity3 = createActivityRecordInTask(mDisplayContent, task);
         activity1.setTaskOverlay(true);
         activity2.setTaskOverlay(true);
         activity3.setTaskOverlay(true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
index 75ed928..6ed7622 100644
--- a/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
@@ -44,7 +44,7 @@
 
     @Test
     public void testFlow() {
-        final ActivityRecord activity = WindowTestUtils.createTestActivityRecord(mDisplayContent);
+        final ActivityRecord activity = createTestActivityRecord(mDisplayContent);
         mDisplayContent.mUnknownAppVisibilityController.notifyLaunched(activity);
         mDisplayContent.mUnknownAppVisibilityController.notifyAppResumedFinished(activity);
         mDisplayContent.mUnknownAppVisibilityController.notifyRelayouted(activity);
@@ -56,8 +56,8 @@
 
     @Test
     public void testMultiple() {
-        final ActivityRecord activity1 = WindowTestUtils.createTestActivityRecord(mDisplayContent);
-        final ActivityRecord activity2 = WindowTestUtils.createTestActivityRecord(mDisplayContent);
+        final ActivityRecord activity1 = createTestActivityRecord(mDisplayContent);
+        final ActivityRecord activity2 = createTestActivityRecord(mDisplayContent);
         mDisplayContent.mUnknownAppVisibilityController.notifyLaunched(activity1);
         mDisplayContent.mUnknownAppVisibilityController.notifyAppResumedFinished(activity1);
         mDisplayContent.mUnknownAppVisibilityController.notifyLaunched(activity2);
@@ -72,7 +72,7 @@
 
     @Test
     public void testClear() {
-        final ActivityRecord activity = WindowTestUtils.createTestActivityRecord(mDisplayContent);
+        final ActivityRecord activity = createTestActivityRecord(mDisplayContent);
         mDisplayContent.mUnknownAppVisibilityController.notifyLaunched(activity);
         mDisplayContent.mUnknownAppVisibilityController.clear();
         assertTrue(mDisplayContent.mUnknownAppVisibilityController.allResolved());
@@ -80,7 +80,7 @@
 
     @Test
     public void testRemoveFinishingInvisibleActivityFromUnknown() {
-        final ActivityRecord activity = WindowTestUtils.createTestActivityRecord(mDisplayContent);
+        final ActivityRecord activity = createTestActivityRecord(mDisplayContent);
         mDisplayContent.mUnknownAppVisibilityController.notifyLaunched(activity);
         activity.finishing = true;
         activity.mVisibleRequested = true;
@@ -90,7 +90,7 @@
 
     @Test
     public void testAppRemoved() {
-        final ActivityRecord activity = WindowTestUtils.createTestActivityRecord(mDisplayContent);
+        final ActivityRecord activity = createTestActivityRecord(mDisplayContent);
         mDisplayContent.mUnknownAppVisibilityController.notifyLaunched(activity);
         mDisplayContent.mUnknownAppVisibilityController.appRemovedOrHidden(activity);
         assertTrue(mDisplayContent.mUnknownAppVisibilityController.allResolved());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index 8ac44f2..4163a9a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -810,8 +810,7 @@
     public void testOnDisplayChanged() {
         final Task stack = createTaskStackOnDisplay(mDisplayContent);
         final Task task = createTaskInStack(stack, 0 /* userId */);
-        final ActivityRecord activity =
-                WindowTestUtils.createActivityRecordInTask(mDisplayContent, task);
+        final ActivityRecord activity = createActivityRecordInTask(mDisplayContent, task);
 
         final DisplayContent newDc = createNewDisplay();
         stack.getDisplayArea().removeStack(stack);
@@ -854,19 +853,17 @@
     public void testTaskCanApplyAnimation() {
         final Task stack = createTaskStackOnDisplay(mDisplayContent);
         final Task task = createTaskInStack(stack, 0 /* userId */);
-        final ActivityRecord activity2 =
-                WindowTestUtils.createActivityRecordInTask(mDisplayContent, task);
-        final ActivityRecord activity1 =
-                WindowTestUtils.createActivityRecordInTask(mDisplayContent, task);
+        final ActivityRecord activity2 = createActivityRecordInTask(mDisplayContent, task);
+        final ActivityRecord activity1 = createActivityRecordInTask(mDisplayContent, task);
         verifyWindowContainerApplyAnimation(task, activity1, activity2);
     }
 
     @Test
     public void testStackCanApplyAnimation() {
         final Task stack = createTaskStackOnDisplay(mDisplayContent);
-        final ActivityRecord activity2 = WindowTestUtils.createActivityRecordInTask(mDisplayContent,
+        final ActivityRecord activity2 = createActivityRecordInTask(mDisplayContent,
                 createTaskInStack(stack, 0 /* userId */));
-        final ActivityRecord activity1 = WindowTestUtils.createActivityRecordInTask(mDisplayContent,
+        final ActivityRecord activity1 = createActivityRecordInTask(mDisplayContent,
                 createTaskInStack(stack, 0 /* userId */));
         verifyWindowContainerApplyAnimation(stack, activity1, activity2);
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 8cfa4f0..9135297 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -931,8 +931,7 @@
 
         final Task stack = createStack();
         final Task task = createTask(stack);
-        final ActivityRecord record = WindowTestUtils.createActivityRecordInTask(
-                stack.mDisplayContent, task);
+        final ActivityRecord record = createActivityRecordInTask(stack.mDisplayContent, task);
 
         stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
         record.setTaskDescription(new ActivityManager.TaskDescription("TestDescription"));
@@ -966,8 +965,7 @@
     public void testInterceptBackPressedOnTaskRoot() throws RemoteException {
         final Task stack = createStack();
         final Task task = createTask(stack);
-        final ActivityRecord activity = WindowTestUtils.createActivityRecordInTask(
-                stack.mDisplayContent, task);
+        final ActivityRecord activity = createActivityRecordInTask(stack.mDisplayContent, task);
         final ITaskOrganizer organizer = registerMockOrganizer(WINDOWING_MODE_MULTI_WINDOW);
 
         // Setup the task to be controlled by the MW mode organizer
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/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 3894a2e..f095fd4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -357,8 +357,7 @@
 
         // Call prepareWindowToDisplayDuringRelayout for a windows that are not children of an
         // activity. Both windows have the FLAG_TURNS_SCREEN_ON so both should call wakeup
-        final WindowToken windowToken = WindowTestUtils.createTestWindowToken(FIRST_SUB_WINDOW,
-                mDisplayContent);
+        final WindowToken windowToken = createTestWindowToken(FIRST_SUB_WINDOW, mDisplayContent);
         final WindowState firstWindow = createWindow(null, TYPE_APPLICATION, windowToken,
                 "firstWindow");
         final WindowState secondWindow = createWindow(null, TYPE_APPLICATION, windowToken,
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
deleted file mode 100644
index 0180d87..0000000
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
+++ /dev/null
@@ -1,134 +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.server.wm;
-
-import static android.app.AppOpsManager.OP_NONE;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
-import static com.android.server.wm.WindowContainer.POSITION_TOP;
-
-import android.os.IBinder;
-import android.view.IWindow;
-import android.view.WindowManager;
-
-import com.android.server.wm.WindowTestsBase.ActivityBuilder;
-
-/**
- * A collection of static functions that provide access to WindowManager related test functionality.
- */
-class WindowTestUtils {
-
-    /** Creates a {@link Task} and adds it to the specified {@link Task}. */
-    static Task createTaskInStack(WindowManagerService service, Task stack, int userId) {
-        final Task task = new WindowTestsBase.TaskBuilder(stack.mStackSupervisor)
-                .setUserId(userId)
-                .setStack(stack)
-                .build();
-        return task;
-    }
-
-    /** Creates an {@link ActivityRecord} and adds it to the specified {@link Task}. */
-    static ActivityRecord createActivityRecordInTask(DisplayContent dc, Task task) {
-        final ActivityRecord activity = createTestActivityRecord(dc);
-        task.addChild(activity, POSITION_TOP);
-        return activity;
-    }
-
-    static ActivityRecord createTestActivityRecord(Task stack) {
-        final ActivityRecord activity = new ActivityBuilder(stack.mAtmService)
-                .setStack(stack)
-                .setCreateTask(true)
-                .build();
-        postCreateActivitySetup(activity, stack.getDisplayContent());
-        return activity;
-    }
-
-    static ActivityRecord createTestActivityRecord(DisplayContent dc) {
-        final ActivityRecord activity = new ActivityBuilder(dc.mWmService.mAtmService).build();
-        postCreateActivitySetup(activity, dc);
-        return activity;
-    }
-
-    private static void postCreateActivitySetup(ActivityRecord activity, DisplayContent dc) {
-        activity.onDisplayChanged(dc);
-        activity.setOccludesParent(true);
-        activity.setVisible(true);
-        activity.mVisibleRequested = true;
-    }
-
-    static TestWindowToken createTestWindowToken(int type, DisplayContent dc) {
-        return createTestWindowToken(type, dc, false /* persistOnEmpty */);
-    }
-
-    static TestWindowToken createTestWindowToken(int type, DisplayContent dc,
-            boolean persistOnEmpty) {
-        SystemServicesTestRule.checkHoldsLock(dc.mWmService.mGlobalLock);
-
-        return new TestWindowToken(type, dc, persistOnEmpty);
-    }
-
-    /* Used so we can gain access to some protected members of the {@link WindowToken} class */
-    static class TestWindowToken extends WindowToken {
-
-        private TestWindowToken(int type, DisplayContent dc, boolean persistOnEmpty) {
-            super(dc.mWmService, mock(IBinder.class), type, persistOnEmpty, dc,
-                    false /* ownerCanManageAppTokens */);
-        }
-
-        int getWindowsCount() {
-            return mChildren.size();
-        }
-
-        boolean hasWindow(WindowState w) {
-            return mChildren.contains(w);
-        }
-    }
-
-    /** Used to track resize reports. */
-    static class TestWindowState extends WindowState {
-        boolean mResizeReported;
-
-        TestWindowState(WindowManagerService service, Session session, IWindow window,
-                WindowManager.LayoutParams attrs, WindowToken token) {
-            super(service, session, window, token, null, OP_NONE, 0, attrs, 0, 0, 0,
-                    false /* ownerCanAddInternalSystemWindow */);
-        }
-
-        @Override
-        void reportResized() {
-            super.reportResized();
-            mResizeReported = true;
-        }
-
-        @Override
-        public boolean isGoneForLayoutLw() {
-            return false;
-        }
-
-        @Override
-        void updateResizingWindowIfNeeded() {
-            // Used in AppWindowTokenTests#testLandscapeSeascapeRotationRelayout to deceive
-            // the system that it can actually update the window.
-            boolean hadSurface = mHasSurface;
-            mHasSurface = true;
-
-            super.updateResizingWindowIfNeeded();
-
-            mHasSurface = hadSurface;
-        }
-    }
-}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index ec19a58..5ce61b4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -47,6 +47,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
+import static com.android.server.wm.WindowContainer.POSITION_TOP;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -66,6 +67,7 @@
 import android.hardware.display.DisplayManager;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.service.voice.IVoiceInteractionSession;
@@ -239,7 +241,7 @@
     private WindowToken createWindowToken(
             DisplayContent dc, int windowingMode, int activityType, int type) {
         if (type < FIRST_APPLICATION_WINDOW || type > LAST_APPLICATION_WINDOW) {
-            return WindowTestUtils.createTestWindowToken(type, dc);
+            return createTestWindowToken(type, dc);
         }
 
         return createActivityRecord(dc, windowingMode, activityType);
@@ -249,10 +251,39 @@
         return createTestActivityRecord(dc, windowingMode, activityType);
     }
 
-    ActivityRecord createTestActivityRecord(DisplayContent dc, int
-            windowingMode, int activityType) {
+    ActivityRecord createTestActivityRecord(DisplayContent dc, int windowingMode,
+            int activityType) {
         final Task stack = createTaskStackOnDisplay(windowingMode, activityType, dc);
-        return WindowTestUtils.createTestActivityRecord(stack);
+        return createTestActivityRecord(stack);
+    }
+
+    /** Creates an {@link ActivityRecord} and adds it to the specified {@link Task}. */
+    static ActivityRecord createActivityRecordInTask(DisplayContent dc, Task task) {
+        final ActivityRecord activity = createTestActivityRecord(dc);
+        task.addChild(activity, POSITION_TOP);
+        return activity;
+    }
+
+    static ActivityRecord createTestActivityRecord(DisplayContent dc) {
+        final ActivityRecord activity = new ActivityBuilder(dc.mWmService.mAtmService).build();
+        postCreateActivitySetup(activity, dc);
+        return activity;
+    }
+
+    static ActivityRecord createTestActivityRecord(Task stack) {
+        final ActivityRecord activity = new ActivityBuilder(stack.mAtmService)
+                .setStack(stack)
+                .setCreateTask(true)
+                .build();
+        postCreateActivitySetup(activity, stack.getDisplayContent());
+        return activity;
+    }
+
+    private static void postCreateActivitySetup(ActivityRecord activity, DisplayContent dc) {
+        activity.onDisplayChanged(dc);
+        activity.setOccludesParent(true);
+        activity.setVisible(true);
+        activity.mVisibleRequested = true;
     }
 
     WindowState createWindow(WindowState parent, int type, String name) {
@@ -274,8 +305,7 @@
     }
 
     WindowState createAppWindow(Task task, int type, String name) {
-        final ActivityRecord activity =
-                WindowTestUtils.createTestActivityRecord(task.getDisplayContent());
+        final ActivityRecord activity = createTestActivityRecord(task.getDisplayContent());
         task.addChild(activity, 0);
         return createWindow(null, type, activity, name);
     }
@@ -390,7 +420,11 @@
 
     /** Creates a {@link Task} and adds it to the specified {@link Task}. */
     Task createTaskInStack(Task stack, int userId) {
-        return WindowTestUtils.createTaskInStack(mWm, stack, userId);
+        final Task task = new TaskBuilder(stack.mStackSupervisor)
+                .setUserId(userId)
+                .setStack(stack)
+                .build();
+        return task;
     }
 
     /** Creates a {@link DisplayContent} that supports IME and adds it to the system. */
@@ -432,12 +466,11 @@
         return createNewDisplay(displayInfo, true /* supportIme */);
     }
 
-    /** Creates a {@link com.android.server.wm.WindowTestUtils.TestWindowState} */
-    WindowTestUtils.TestWindowState createWindowState(WindowManager.LayoutParams attrs,
-            WindowToken token) {
+    /** Creates a {@link TestWindowState} */
+    TestWindowState createWindowState(WindowManager.LayoutParams attrs, WindowToken token) {
         SystemServicesTestRule.checkHoldsLock(mWm.mGlobalLock);
 
-        return new WindowTestUtils.TestWindowState(mWm, mMockSession, mIWindow, attrs, token);
+        return new TestWindowState(mWm, mMockSession, mIWindow, attrs, token);
     }
 
     /** Creates a {@link DisplayContent} as parts of simulate display info for test. */
@@ -1055,4 +1088,66 @@
         public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) {
         }
     }
+
+    static TestWindowToken createTestWindowToken(int type, DisplayContent dc) {
+        return createTestWindowToken(type, dc, false /* persistOnEmpty */);
+    }
+
+    static TestWindowToken createTestWindowToken(int type, DisplayContent dc,
+            boolean persistOnEmpty) {
+        SystemServicesTestRule.checkHoldsLock(dc.mWmService.mGlobalLock);
+
+        return new TestWindowToken(type, dc, persistOnEmpty);
+    }
+
+    /** Used so we can gain access to some protected members of the {@link WindowToken} class */
+    static class TestWindowToken extends WindowToken {
+
+        private TestWindowToken(int type, DisplayContent dc, boolean persistOnEmpty) {
+            super(dc.mWmService, mock(IBinder.class), type, persistOnEmpty, dc,
+                    false /* ownerCanManageAppTokens */);
+        }
+
+        int getWindowsCount() {
+            return mChildren.size();
+        }
+
+        boolean hasWindow(WindowState w) {
+            return mChildren.contains(w);
+        }
+    }
+
+    /** Used to track resize reports. */
+    static class TestWindowState extends WindowState {
+        boolean mResizeReported;
+
+        TestWindowState(WindowManagerService service, Session session, IWindow window,
+                WindowManager.LayoutParams attrs, WindowToken token) {
+            super(service, session, window, token, null, OP_NONE, 0, attrs, 0, 0, 0,
+                    false /* ownerCanAddInternalSystemWindow */);
+        }
+
+        @Override
+        void reportResized() {
+            super.reportResized();
+            mResizeReported = true;
+        }
+
+        @Override
+        public boolean isGoneForLayoutLw() {
+            return false;
+        }
+
+        @Override
+        void updateResizingWindowIfNeeded() {
+            // Used in AppWindowTokenTests#testLandscapeSeascapeRotationRelayout to deceive
+            // the system that it can actually update the window.
+            boolean hadSurface = mHasSurface;
+            mHasSurface = true;
+
+            super.updateResizingWindowIfNeeded();
+
+            mHasSurface = hadSurface;
+        }
+    }
 }
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 f185da3..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;
@@ -58,8 +59,7 @@
 
     @Test
     public void testAddWindow() {
-        final WindowTestUtils.TestWindowToken token =
-                WindowTestUtils.createTestWindowToken(0, mDisplayContent);
+        final TestWindowToken token = createTestWindowToken(0, mDisplayContent);
 
         assertEquals(0, token.getWindowsCount());
 
@@ -93,7 +93,7 @@
     @Test
     public void testChildRemoval() {
         final DisplayContent dc = mDisplayContent;
-        final WindowTestUtils.TestWindowToken token = WindowTestUtils.createTestWindowToken(0, dc);
+        final TestWindowToken token = createTestWindowToken(0, dc);
 
         assertEquals(token, dc.getWindowToken(token.token));
 
@@ -116,7 +116,7 @@
      */
     @Test
     public void testTokenRemovalProcess() {
-        final WindowTestUtils.TestWindowToken token = WindowTestUtils.createTestWindowToken(
+        final TestWindowToken token = createTestWindowToken(
                 TYPE_TOAST, mDisplayContent, true /* persistOnEmpty */);
 
         // Verify that the token is on the display
@@ -146,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/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/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/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/tests/Internal/Android.bp b/tests/Internal/Android.bp
index e233fed..9da17db 100644
--- a/tests/Internal/Android.bp
+++ b/tests/Internal/Android.bp
@@ -11,6 +11,7 @@
         "androidx.test.rules",
         "mockito-target-minus-junit4",
         "truth-prebuilt",
+        "platform-test-annotations",
     ],
     java_resource_dirs: ["res"],
     certificate: "platform",
diff --git a/services/tests/servicestests/src/com/android/server/protolog/ProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java
similarity index 98%
rename from services/tests/servicestests/src/com/android/server/protolog/ProtoLogImplTest.java
rename to tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java
index 3e9f625..3db0116 100644
--- a/services/tests/servicestests/src/com/android/server/protolog/ProtoLogImplTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.server.protolog;
+package com.android.internal.protolog;
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
-import static com.android.server.protolog.ProtoLogImpl.PROTOLOG_VERSION;
+import static com.android.internal.protolog.ProtoLogImpl.PROTOLOG_VERSION;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
@@ -42,7 +42,7 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.server.protolog.common.IProtoLogGroup;
+import com.android.internal.protolog.common.IProtoLogGroup;
 
 import org.junit.After;
 import org.junit.Before;
diff --git a/services/tests/servicestests/src/com/android/server/protolog/ProtoLogViewerConfigReaderTest.java b/tests/Internal/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java
similarity index 98%
rename from services/tests/servicestests/src/com/android/server/protolog/ProtoLogViewerConfigReaderTest.java
rename to tests/Internal/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java
index 0254055..ae50216 100644
--- a/services/tests/servicestests/src/com/android/server/protolog/ProtoLogViewerConfigReaderTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.protolog;
+package com.android.internal.protolog;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
diff --git a/services/tests/servicestests/src/com/android/server/protolog/common/LogDataTypeTest.java b/tests/Internal/src/com/android/internal/protolog/common/LogDataTypeTest.java
similarity index 97%
rename from services/tests/servicestests/src/com/android/server/protolog/common/LogDataTypeTest.java
rename to tests/Internal/src/com/android/internal/protolog/common/LogDataTypeTest.java
index 4c7f5fd..e20ca3d 100644
--- a/services/tests/servicestests/src/com/android/server/protolog/common/LogDataTypeTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/common/LogDataTypeTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.protolog.common;
+package com.android.internal.protolog.common;
 
 import static org.junit.Assert.assertEquals;
 
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..12f1750 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);
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 5285b04..792e29d 100644
--- a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
+++ b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
@@ -22,12 +22,16 @@
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
-import com.android.cts.install.lib.host.InstallUtilsHost;
+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;
@@ -48,6 +52,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);
@@ -73,6 +78,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
@@ -85,6 +92,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");
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/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/tools/protologtool/Android.bp b/tools/protologtool/Android.bp
index ce551bd..0be80d3 100644
--- a/tools/protologtool/Android.bp
+++ b/tools/protologtool/Android.bp
@@ -2,9 +2,9 @@
     name: "protologtool-lib",
     srcs: [
         "src/com/android/protolog/tool/**/*.kt",
+        ":protolog-common-src",
     ],
     static_libs: [
-        "protolog-common",
         "javaparser",
         "platformprotos",
         "jsonlib",
diff --git a/tools/protologtool/src/com/android/protolog/tool/LogParser.kt b/tools/protologtool/src/com/android/protolog/tool/LogParser.kt
index a59038f..645c567 100644
--- a/tools/protologtool/src/com/android/protolog/tool/LogParser.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/LogParser.kt
@@ -16,16 +16,15 @@
 
 package com.android.protolog.tool
 
+import com.android.internal.protolog.ProtoLogFileProto
+import com.android.internal.protolog.ProtoLogMessage
+import com.android.internal.protolog.common.InvalidFormatStringException
+import com.android.internal.protolog.common.LogDataType
 import com.android.json.stream.JsonReader
-import com.android.server.protolog.common.InvalidFormatStringException
-import com.android.server.protolog.common.LogDataType
-import com.android.server.protolog.ProtoLogMessage
-import com.android.server.protolog.ProtoLogFileProto
 import java.io.BufferedReader
 import java.io.InputStream
 import java.io.InputStreamReader
 import java.io.PrintStream
-import java.lang.Exception
 import java.text.SimpleDateFormat
 import java.util.Date
 import java.util.Locale
diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogGroupReader.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogGroupReader.kt
index 75493b6..42b628b 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogGroupReader.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogGroupReader.kt
@@ -17,7 +17,7 @@
 package com.android.protolog.tool
 
 import com.android.protolog.tool.Constants.ENUM_VALUES_METHOD
-import com.android.server.protolog.common.IProtoLogGroup
+import com.android.internal.protolog.common.IProtoLogGroup
 import java.io.File
 import java.net.URLClassLoader
 
diff --git a/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt b/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
index 36ea411..27e61a1 100644
--- a/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
@@ -16,7 +16,7 @@
 
 package com.android.protolog.tool
 
-import com.android.server.protolog.common.LogDataType
+import com.android.internal.protolog.common.LogDataType
 import com.github.javaparser.StaticJavaParser
 import com.github.javaparser.ast.CompilationUnit
 import com.github.javaparser.ast.NodeList
@@ -89,7 +89,7 @@
             // Out: ProtoLog.e(GROUP, 1234, 0, null, arg)
             newCall.arguments.add(2, IntegerLiteralExpr(typeMask))
             // Replace call to a stub method with an actual implementation.
-            // Out: com.android.server.protolog.ProtoLogImpl.e(GROUP, 1234, null, arg)
+            // Out: ProtoLogImpl.e(GROUP, 1234, null, arg)
             newCall.setScope(protoLogImplClassNode)
             // Create a call to ProtoLog$Cache.GROUP_enabled
             // Out: com.android.server.protolog.ProtoLog$Cache.GROUP_enabled
@@ -119,9 +119,9 @@
             }
             blockStmt.addStatement(ExpressionStmt(newCall))
             // Create an IF-statement with the previously created condition.
-            // Out: if (com.android.server.protolog.ProtoLogImpl.isEnabled(GROUP)) {
+            // Out: if (ProtoLogImpl.isEnabled(GROUP)) {
             //          long protoLogParam0 = arg;
-            //          com.android.server.protolog.ProtoLogImpl.e(GROUP, 1234, 0, null, protoLogParam0);
+            //          ProtoLogImpl.e(GROUP, 1234, 0, null, protoLogParam0);
             //      }
             ifStmt = IfStmt(isLogEnabled, blockStmt, null)
         } else {
diff --git a/tools/protologtool/tests/com/android/protolog/tool/CommandOptionsTest.kt b/tools/protologtool/tests/com/android/protolog/tool/CommandOptionsTest.kt
index cf36651c..3cfbb43 100644
--- a/tools/protologtool/tests/com/android/protolog/tool/CommandOptionsTest.kt
+++ b/tools/protologtool/tests/com/android/protolog/tool/CommandOptionsTest.kt
@@ -31,7 +31,7 @@
         private const val TEST_PROTOLOG_CLASS = "com.android.server.wm.ProtoLog"
         private const val TEST_PROTOLOGIMPL_CLASS = "com.android.server.wm.ProtoLogImpl"
         private const val TEST_PROTOLOGCACHE_CLASS = "com.android.server.wm.ProtoLog\$Cache"
-        private const val TEST_PROTOLOGGROUP_CLASS = "com.android.server.wm.ProtoLogGroup"
+        private const val TEST_PROTOLOGGROUP_CLASS = "com.android.internal.protolog.ProtoLogGroup"
         private const val TEST_PROTOLOGGROUP_JAR = "out/soong/.intermediates/frameworks/base/" +
                 "services/core/services.core.wm.protologgroups/android_common/javac/" +
                 "services.core.wm.protologgroups.jar"
diff --git a/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt b/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt
index dd8a0b1..0d2b91d 100644
--- a/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt
+++ b/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt
@@ -33,8 +33,8 @@
         val output = run(
                 src = "frameworks/base/org/example/Example.java" to """
                     package org.example;
-                    import com.android.server.protolog.common.ProtoLog;
-                    import static com.android.server.wm.ProtoLogGroup.GROUP;
+                    import com.android.internal.protolog.common.ProtoLog;
+                    import static com.android.internal.protolog.ProtoLogGroup.GROUP;
 
                     class Example {
                         void method() {
@@ -46,11 +46,11 @@
                 """.trimIndent(),
                 logGroup = LogGroup("GROUP", true, false, "TAG_GROUP"),
                 commandOptions = CommandOptions(arrayOf("transform-protolog-calls",
-                        "--protolog-class", "com.android.server.protolog.common.ProtoLog",
-                        "--protolog-impl-class", "com.android.server.protolog.ProtoLogImpl",
+                        "--protolog-class", "com.android.internal.protolog.common.ProtoLog",
+                        "--protolog-impl-class", "com.android.internal.protolog.ProtoLogImpl",
                         "--protolog-cache-class",
-                        "com.android.server.protolog.ProtoLog${"\$\$"}Cache",
-                        "--loggroups-class", "com.android.server.wm.ProtoLogGroup",
+                        "com.android.server.wm.ProtoLogCache",
+                        "--loggroups-class", "com.android.internal.protolog.ProtoLogGroup",
                         "--loggroups-jar", "not_required.jar",
                         "--output-srcjar", "out.srcjar",
                         "frameworks/base/org/example/Example.java"))
@@ -64,8 +64,8 @@
         val output = run(
                 src = "frameworks/base/org/example/Example.java" to """
                     package org.example;
-                    import com.android.server.protolog.common.ProtoLog;
-                    import static com.android.server.wm.ProtoLogGroup.GROUP;
+                    import com.android.internal.protolog.common.ProtoLog;
+                    import static com.android.internal.protolog.ProtoLogGroup.GROUP;
 
                     class Example {
                         void method() {
@@ -77,8 +77,8 @@
                 """.trimIndent(),
                 logGroup = LogGroup("GROUP", true, false, "TAG_GROUP"),
                 commandOptions = CommandOptions(arrayOf("generate-viewer-config",
-                        "--protolog-class", "com.android.server.protolog.common.ProtoLog",
-                        "--loggroups-class", "com.android.server.wm.ProtoLogGroup",
+                        "--protolog-class", "com.android.internal.protolog.common.ProtoLog",
+                        "--loggroups-class", "com.android.internal.protolog.ProtoLogGroup",
                         "--loggroups-jar", "not_required.jar",
                         "--viewer-conf", "out.json",
                         "frameworks/base/org/example/Example.java"))
diff --git a/tools/protologtool/tests/com/android/protolog/tool/LogParserTest.kt b/tools/protologtool/tests/com/android/protolog/tool/LogParserTest.kt
index 04a3bfa..67a31da 100644
--- a/tools/protologtool/tests/com/android/protolog/tool/LogParserTest.kt
+++ b/tools/protologtool/tests/com/android/protolog/tool/LogParserTest.kt
@@ -17,8 +17,8 @@
 package com.android.protolog.tool
 
 import com.android.json.stream.JsonReader
-import com.android.server.protolog.ProtoLogMessage
-import com.android.server.protolog.ProtoLogFileProto
+import com.android.internal.protolog.ProtoLogMessage
+import com.android.internal.protolog.ProtoLogFileProto
 import org.junit.Assert.assertEquals
 import org.junit.Before
 import org.junit.Test
diff --git a/wifi/api/system-current.txt b/wifi/api/system-current.txt
index 53c69c4..eff64a3 100644
--- a/wifi/api/system-current.txt
+++ b/wifi/api/system-current.txt
@@ -326,6 +326,8 @@
     field @Deprecated public static final int METERED_OVERRIDE_METERED = 1; // 0x1
     field @Deprecated public static final int METERED_OVERRIDE_NONE = 0; // 0x0
     field @Deprecated public static final int METERED_OVERRIDE_NOT_METERED = 2; // 0x2
+    field @Deprecated public static final int RANDOMIZATION_AUTO = 3; // 0x3
+    field @Deprecated public static final int RANDOMIZATION_ENHANCED = 2; // 0x2
     field @Deprecated public static final int RANDOMIZATION_NONE = 0; // 0x0
     field @Deprecated public static final int RANDOMIZATION_PERSISTENT = 1; // 0x1
     field @Deprecated public static final int RECENT_FAILURE_AP_UNABLE_TO_HANDLE_NEW_STA = 17; // 0x11
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/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index 71f0ab8..1588bf7 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -1130,7 +1130,9 @@
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"RANDOMIZATION_"}, value = {
             RANDOMIZATION_NONE,
-            RANDOMIZATION_PERSISTENT})
+            RANDOMIZATION_PERSISTENT,
+            RANDOMIZATION_ENHANCED,
+            RANDOMIZATION_AUTO})
     public @interface MacRandomizationSetting {}
 
     /**
@@ -1147,14 +1149,30 @@
     public static final int RANDOMIZATION_PERSISTENT = 1;
 
     /**
+     * Use a randomly generated MAC address for connections to this network.
+     * This option does not persist the randomized MAC address.
+     * @hide
+     */
+    @SystemApi
+    public static final int RANDOMIZATION_ENHANCED = 2;
+
+    /**
+     * Let the wifi framework automatically decide the MAC randomization strategy.
+     * @hide
+     */
+    @SystemApi
+    public static final int RANDOMIZATION_AUTO = 3;
+
+    /**
      * Level of MAC randomization for this network.
-     * One of {@link #RANDOMIZATION_NONE} or {@link #RANDOMIZATION_PERSISTENT}.
-     * By default this field is set to {@link #RANDOMIZATION_PERSISTENT}.
+     * One of {@link #RANDOMIZATION_NONE}, {@link #RANDOMIZATION_AUTO},
+     * {@link #RANDOMIZATION_PERSISTENT} or {@link #RANDOMIZATION_ENHANCED}.
+     * By default this field is set to {@link #RANDOMIZATION_AUTO}.
      * @hide
      */
     @SystemApi
     @MacRandomizationSetting
-    public int macRandomizationSetting = RANDOMIZATION_PERSISTENT;
+    public int macRandomizationSetting = RANDOMIZATION_AUTO;
 
     /**
      * @hide
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 {